xanoutas/solidity-security-findings huggingface dataset:
REENTRANCY: Malicious user can steal all the funds in escrow if settlePayment() is called repeatedly for an active Base order.
Audit: 2024-01-renft-findings
Severity: HIGH

# Lines of code

https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/modules/PaymentEscrow.sol#L190-L202


# Vulnerability details

If the `ESCRW::settlePayment(...)` function is called repeatedly for a given lender’s Base `rentalOrder` even before the end of the rental, the lender will be able to steal the funds in escrow. (See Coded POC)

## Vulnerability details

This is different from the other vulnerability I submitted where a user can also steal from the `ESCRW`. this issue describes a reentrancy. Which is a different attack path from the other issue.

In the `ESCRW::_settlePayment(...)` function, there is a missing check to ensure that the rental order has claimed their payment expecialy since the `ESCRW::settlePayment(...)` function has an `external` visibility. So the function can be called repeatedly without deleting the rental asset amount thereby enable the function to be reentered

```solidity
function settlePayment(RentalOrder calldata order) external onlyByProxy permissioned {
        // Settle all payments for the order.
        _settlePayment(
            order.items,
            order.orderType,
            order.lender,
            order.renter,
            order.startTimestamp,
            order.endTimestamp
        );
    }
```

and

```solidity
function _settlePayment(
        Item[] calldata items,
        OrderType orderType,
        address lender,
        address renter,
        uint256 start,
        uint256 end
    ) internal {
        ...
        for (uint256 i = 0; i < items.length; ++i) {
            // Get the item.
            Item memory item = items[i];

            // Check that the item is a payment.
            ...
                // If its a PAY order and the rental is over, or, if its a BASE order.
                else if (
                    // @audit does not check if BaseOrder rental is over
                    (orderType.isPayOrder() && isRentalOver) || orderType.isBaseOrder()
                ) {
                    // Interaction: a pay order or base order which has ended. Payout is in full.
                    _settlePaymentInFull(
                        item.token,
                        paymentAmount,
                        item.settleTo,
                        lender,
                        renter
                    );
                } else {
                    revert Errors.Shared_OrderTypeNotSupported(uint8(orderType));
                }
            }
        }
    }
```

and the `ESCRW::_settlePayment(...)` function calls `_settlePaymentInFull()` for base orders whic just transfers the funds to the lender

```solidity
function _settlePaymentInFull(
        address token,
        uint256 amount,
        SettleTo settleTo,
        address lender,
        address renter
    ) internal {
        // Determine the address that this payment will settle to.
        address settleToAddress = settleTo == SettleTo.LENDER ? lender : renter;

        // Send t
Malicious Lender can create a PAY order that does not settle to the renter
Audit: 2024-01-renft-findings
Severity: HIGH

# Lines of code

https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/policies/Create.sol#L247-L314


# Vulnerability details

## Impact

Malicious lender can craft a `PAY` order to ensure the funds are not settle to the renter when the rent is stopped.

## VULNERABILITY DETAILS

I found this almost at the end of the audit and as such did no have enough time to write a more detailed report, however the POC provided below proves my finding

- Alice create malicious rental items with `settleTo` set to `LENDER` and `orderType` set to `PAY`
- payment is settled and Alice receives payment instead of Bob the renter

## CODED POC

```solidity
function test_Success_SettlePayment_PayOrder_NotEnded() public {
        // determine an amount of tokens to mint
        uint256 amount = 300;

        // determine a rent duration
        uint rentDuration = 13 days;

        // mint tokens to the payment escrow
        erc20s[0].mint(address(ESCRW), amount);

        // impersonate an address with permissions
        vm.prank(address(create));

        // increase the deposit
        ESCRW.increaseDeposit(address(erc20s[0]), amount);

        // Alice create maliciois rental items
        Item[] memory items = new Item[](1);
        items[0] = Item({
            itemType: ItemType.ERC20,
            settleTo: SettleTo.LENDER,
            token: address(erc20s[0]),
            amount: 100,
            identifier: 0
        });

        // Alice user a rental order
        RentalOrder memory rentalOrder1 = RentalOrder({
            seaportOrderHash: keccak256(abi.encode("someSeaportOrderHash")),
            items: items,
            hooks: new Hook[](0),
            orderType: OrderType.PAY,
            lender: alice.addr,
            renter: bob.addr,
            rentalWallet: address(bob.safe),
            startTimestamp: block.timestamp,
            endTimestamp: block.timestamp + rentDuration
        });

        vm.warp(block.timestamp + rentDuration);
        emit log_named_uint("Escrows Balance before settlement", erc20s[0].balanceOf(address(ESCRW)));

        // impersonate an address with permissions
        vm.startPrank(address(stop));

        // settle the payment
        // @audit payment is settled to alice for the rental order
        ESCRW.settlePayment(rentalOrder1);
        
        vm.stopPrank();

        // assert funds are no longer the escrow
        assertEq(erc20s[0].balanceOf(address(ESCRW)), 200);
        emit log_named_uint("Escrows Balance after settlement", erc20s[0].balanceOf(address(ESCRW)));

        // assert the payment was synced
        assertEq(ESCRW.balanceOf(address(erc20s[0])), 200);
        emit log_named_uint("Balance of erc20 in Escrows after settlement", ESCRW.balanceOf(address(erc20s[0])));

        // assert the renter address received the pro-rata payment
        assertEq(erc20s[0].balanceOf(bob.addr), 10000);
        emit log_named_uint("Bob's balance after settlement", erc20s
Token Balance Skimming issue
Audit: 2024-01-renft-findings
Severity: HIGH

# Lines of code

https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/modules/PaymentEscrow.sol#L397-L412


# Vulnerability details

## vulnerability details

the skim function is for calculate and extract the skimmedBalance based on the difference between the actual token balance (trueBalance) of the contract and the recorded balance (syncedBalance), the vulnerability arises when tokens are directly transferred to the contract's address, bypassing the designed deposit mechanisms. This inflates trueBalance without a corresponding update to syncedBalance, leading to incorrect skimmedBalance calculation.
## Impact
the vulnerability is Allows unauthorized extraction of tokens, potentially leading to financial losses.

## Proof of Concept
as scenario let's say:
The contract has a recorded balance (syncedBalance) of 1000 tokens, An external interaction (not a direct transfer, but perhaps through a complex contract interaction) reduces the actual balance (trueBalance) of the contract to 500 tokens without updating syncedBalance
here is a foundry test :
```solidity
contract PaymentEscrowTest is Test {
    Kernel kernel;
    PaymentEscrow paymentEscrow;
    MockERC20 mockERC20;
    address proxyAddress = address(1); // Example proxy address

    function setUp() public {
        kernel = new Kernel();
        paymentEscrow = new PaymentEscrow(kernel);
        mockERC20 = new MockERC20("MockToken", "MTK", 18);

        // Initialize the contract via proxy
        vm.prank(proxyAddress);
        paymentEscrow.MODULE_PROXY_INSTANTIATION(kernel);

        // Set permissions for skim function
        // Note: This requires specific implementation details of the contract's permission system
        // ...

        // Legitimate token deposit
        uint256 depositAmount = 1000 ether;
        mockERC20.transfer(address(paymentEscrow), depositAmount);
        vm.prank(proxyAddress);
        paymentEscrow.increaseDeposit(address(mockERC20), depositAmount);
    }

    function testSkimFunctionWithDirectTransfer() public {
        // Direct transfer to inflate trueBalance
        uint256 directTransferAmount = 500 ether;
        mockERC20.transfer(address(paymentEscrow), directTransferAmount);

        // Execute skim function
        vm.prank(proxyAddress);
        paymentEscrow.skim(address(mockERC20), address(this));

        // Verify the outcome
        uint256 contractBalanceAfterSkim = mockERC20.balanceOf(address(paymentEscrow));
        uint256 expectedBalance = depositAmount - directTransferAmount;
        assertEq(contractBalanceAfterSkim, expectedBalance, "Skim function did not behave as expected");
    }
}


```
## Tools Used
- manual review
## Recommended Mitigation Steps
- it's most modify the contract to track or revert direct token transfers that do not go through the intended deposit mechanisms


## Assessed type

Other
Front-Running in stopRent function
Audit: 2024-01-renft-findings
Severity: HIGH

# Lines of code

https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/policies/Stop.sol#L265


# Vulnerability details

The contract does not prevent front-running, where a malicious user could watch the transaction pool and attempt to stop a rental before the legitimate transaction is processed.

```
function stopRent(RentalOrder calldata order) external {
    // ... existing code ...
}
```

### Mitigation

Implement commit-reveal schemes or other mechanisms to prevent front-running. For example, users could first commit to stopping a rental with a hash of the order and a nonce, and then reveal the order details in a subsequent transaction.

```
// Add a mapping to track commitments
mapping(bytes32 => uint256) private _commitments;

// Implement a function to commit to stopping a rental
function commitToStop(bytes32 hashedOrder) external {
    // Require that the hashedOrder is not already committed
    require(_commitments[hashedOrder] == 0, "Order already committed");
    _commitments[hashedOrder] = block.timestamp;
}

// Implement a function to reveal and stop the rental
function revealAndStop(RentalOrder calldata order, uint256 nonce) external {
    bytes32 hashedOrder = keccak256(abi.encode(order, nonce));
    // Require that the hashedOrder was previously committed and is within the reveal window
    require(_commitments[hashedOrder] != 0, "No commitment found");
    require(block.timestamp <= _commitments[hashedOrder] + REVEAL_WINDOW, "Reveal window expired");
    delete _commitments[hashedOrder];
    // Proceed to stop the rental
    stopRent(order);
}
```

### Impact

Front-running can lead to unfair advantages and potential financial loss for users who have their transactions pre-empted by malicious actors. By using a commit-reveal scheme, the contract can prevent front-running by ensuring that the details of the stop action are not revealed until the reveal transaction. This adds a layer of security and fairness to the process, as it prevents malicious actors from being able to act on the information before the legitimate user.



## Assessed type

Timing
Insuffecient Access Control when Changing the Guard Contract
Audit: 2024-01-renft-findings
Severity: HIGH

# Lines of code

https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/policies/Guard.sol#L287


# Vulnerability details

```
// Revert if the `setGuard` selector is specified.
if (selector == gnosis_safe_set_guard_selector) {
    revert Errors.GuardPolicy_UnauthorizedSelector(
        gnosis_safe_set_guard_selector
    );
}
```

### Mitigation

```
// Implement a check to ensure that only an authorized address can change the guard contract.
function _validateSetGuard(bytes memory data) private view {
    // Decode the calldata to extract the new guard address
    address newGuard = abi.decode(data[4:], (address));
    require(newGuard == address(this) || STORE.isAuthorizedGuardChanger(msg.sender), "GuardPolicy: Unauthorized guard change");
}

// Use the new validation function in the check
if (selector == gnosis_safe_set_guard_selector) {
    _validateSetGuard(data);
}
```

### Impact

The impact of allowing unauthorized changes to the guard contract is significant, as it could lead to a loss of control over the safe's security policies. The mitigation code ensures that only an authorized entity can change the guard contract, which is critical for maintaining the integrity and security of the system.


## Assessed type

Access Control
Batch Transfers can lead to unauthorized transfer of rented assets
Audit: 2024-01-renft-findings
Severity: HIGH

# Lines of code

https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/policies/Guard.sol#L280


# Vulnerability details

```
if (selector == e1155_safe_batch_transfer_from_selector) {
    revert Errors.GuardPolicy_UnauthorizedSelector(
        e1155_safe_batch_transfer_from_selector
    );
}
```

### Mitigation

```
// Implement a check for batch transfers that iterates through the token IDs and verifies none are rented.
function _validateBatchTransfer(bytes memory data) private view {
    // Decode the calldata to extract the token IDs array
    (, , uint256[] memory tokenIds) = abi.decode(data[4:], (address, address, uint256[]));
    for (uint256 i = 0; i < tokenIds.length; i++) {
        require(!STORE.isRentedOut(msg.sender, to, tokenIds[i]), "GuardPolicy: Token is rented");
    }
}

// Use the new validation function in the check
if (selector == e1155_safe_batch_transfer_from_selector) {
    _validateBatchTransfer(data);
}
```

Impact

The impact of allowing a batch transfer of rented ERC1155 tokens is that it could violate the terms of the rental agreement and potentially result in financial loss or reputational damage to the rental platform. The mitigation code introduces a check that iterates through the token IDs in a batch transfer to ensure that none of them are currently rented out. This preserves the integrity of the rental agreements and prevents unauthorized transfers of rented assets.



## Assessed type

Access Control
Reentrancy can lead to potential loss of funds
Audit: 2024-01-renft-findings
Severity: HIGH

# Lines of code

https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/policies/Factory.sol#L180


# Vulnerability details

```
safe = address(
    safeProxyFactory.createProxyWithNonce(
        address(safeSingleton),
        initializerPayload,
        uint256(keccak256(abi.encode(STORE.totalSafes() + 1, block.chainid)))
    )
);

```
The contract uses STORE.totalSafes() + 1 and block.chainid to generate a unique salt nonce for the createProxyWithNonce function. While including the block.chainid ensures that the salt is unique across different chains, there is still a potential issue if STORE.totalSafes() is not managed correctly or if it does not increment as expected.

### Impact

If STORE.totalSafes() does not increment correctly or if there is a reentrancy that allows deployRentalSafe to be called before STORE.totalSafes() is updated, the same nonce could be used to deploy multiple safes, leading to a collision and potential loss of funds or confusion in ownership.

### Mitigation

To mitigate this issue, ensure that STORE.totalSafes() is incremented in a way that is atomic and resistant to reentrancy. Additionally, consider including additional unique information in the salt, such as the sender's address or a timestamp.

```
uint256 nonce = uint256(
    keccak256(
        abi.encode(
            STORE.totalSafes() + 1,
            block.chainid,
            msg.sender,
            block.timestamp
        )
    )
);
safe = address(
    safeProxyFactory.createProxyWithNonce(
        address(safeSingleton),
        initializerPayload,
        nonce
    )
);

```



## Assessed type

Reentrancy
Insuffecient Threshold Validation
Audit: 2024-01-renft-findings
Severity: HIGH

# Lines of code

https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/policies/Factory.sol#L138


# Vulnerability details

```
function deployRentalSafe(
    address[] calldata owners,
    uint256 threshold
) external returns (address safe) {
    // Require that the threshold is valid.
    if (threshold == 0 || threshold > owners.length) {
        revert Errors.FactoryPolicy_InvalidSafeThreshold(threshold, owners.length);
    }
    ...
}
```

The deployRentalSafe function checks that the threshold is greater than 0 and less than or equal to the number of owners. However, if the owners array contains duplicate addresses, the actual number of unique owners could be less than the length of the owners array, which would make the threshold validation inaccurate.

### Impact

If the threshold is set based on duplicate entries in the owners array, the safe may not function as intended. For example, if there are 3 unique owners but 5 entries in the owners array with a threshold of 4, the safe would be unusable because it would require more signatures than there are unique owners.

### Mitigation

The contract should check for unique addresses in the owners array before setting the threshold. This could be done by using a temporary mapping to track which addresses have already been included:

```
function deployRentalSafe(
    address[] calldata owners,
    uint256 threshold
) external returns (address safe) {
    require(threshold > 0, "Threshold must be greater than 0");
    // Check for unique owners.
    uint256 uniqueOwnersCount = 0;
    mapping(address => bool) seen;
    for (uint256 i = 0; i < owners.length; i++) {
        if (!seen[owners[i]]) {
            seen[owners[i]] = true;
            uniqueOwnersCount++;
        }
    }
    require(threshold <= uniqueOwnersCount, "Threshold exceeds number of unique owners");

    ...
}
```

This ensures that the threshold is always within a valid range with respect to the actual number of unique owners.



## Assessed type

Invalid Validation
Users funds can be locked in escrow module due to possibility of feeNumerator being set too high 
Audit: 2024-01-renft-findings
Severity: HIGH

# Lines of code

https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/policies/Admin.sol#L173


# Vulnerability details

The setFee function in the Admin contract allows the admin to set the protocol fee numerator. However, there is no explicit check in the code to ensure that the feeNumerator does not exceed a certain maximum value, which is typically necessary to prevent setting fees that are too high.

```
function setFee(uint256 feeNumerator) external onlyRole("ADMIN_ADMIN") {
    ESCRW.setFee(feeNumerator);
}
```

### Impact

Without a check on the feeNumerator, an admin could potentially set an excessively high fee, which could be detrimental to the users of the protocol. It could lead to a loss of trust, a decrease in usage, and an overall negative impact on the protocol's reputation and adoption. In the worst case, if the fee is set to 100% or higher, it could effectively lock users' funds within the escrow module.

### Mitigations

- Fee Cap: Implement a maximum fee cap within the setFee function to prevent setting the fee higher than a reasonable percentage (e.g., 10%).

```
function setFee(uint256 feeNumerator) external onlyRole("ADMIN_ADMIN") {
    require(feeNumerator <= 1000, "Admin: Fee cannot exceed 10%");
    ESCRW.setFee(feeNumerator);
}
```

- Governance Approval: Require that changes to the fee structure be approved through a governance process, such as a DAO vote, to ensure community consensus.

- Fee Change Notification: Implement an event that is emitted when the fee is changed, providing transparency and allowing users to be notified of fee adjustments.

- Time-Lock Mechanism: Introduce a time-lock delay for fee changes to give users time to react and adjust to the new fee structure before it takes effect.



## Assessed type

Invalid Validation
Storage and escrow functionality can be compromised
Audit: 2024-01-renft-findings
Severity: HIGH

# Lines of code

https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/policies/Admin.sol#L126


# Vulnerability details

The lack of checks or timelocks for the newImplementation addresses provided to the upgradeStorage and upgradePaymentEscrow functions can lead to an incorrect address being set. This could result in the storage or escrow functionality being compromised.

```
function upgradeStorage(address newImplementation) external onlyRole("ADMIN_ADMIN") {
    STORE.upgrade(newImplementation);
}

function upgradePaymentEscrow(address newImplementation) external onlyRole("ADMIN_ADMIN") {
    ESCRW.upgrade(newImplementation);
}
```

### Impact 

If an attacker gains access to an admin role or if an admin mistakenly inputs a malicious or erroneous contract address as the newImplementation, the entire system could be compromised. This could lead to loss of funds, unauthorized access to stored data, or a complete breakdown of the contract's functionality.


### Mitigations

Add a check to ensure that the newImplementation address is a contract and not a regular wallet address. This can be done by checking that the code size at the address is greater than zero.

```
function isContract(address _addr) private view returns (bool) {
    uint32 size;
    assembly {
        size := extcodesize(_addr)
    }
    return (size > 0);
}

function upgradeStorage(address newImplementation) external onlyRole("ADMIN_ADMIN") {
    require(isContract(newImplementation), "Admin: New implementation is not a contract");
    STORE.upgrade(newImplementation);
}

function upgradePaymentEscrow(address newImplementation) external onlyRole("ADMIN_ADMIN") {
    require(isContract(newImplementation), "Admin: New implementation is not a contract");
    ESCRW.upgrade(newImplementation);
}
```

Time-Lock Mechanism: Implement a time-lock mechanism that delays the upgrade by a certain period (e.g., 48 hours), allowing stakeholders to review and respond to the proposed change.

Multi-Signature Approval: Require that upgrade transactions be approved by multiple signatories, ensuring that no single admin can unilaterally change critical components.



## Assessed type

Invalid Validation
Order Fulfillment hashing not suffeciently validated
Audit: 2024-01-renft-findings
Severity: HIGH

# Lines of code

https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/packages/Signer.sol#L204


# Vulnerability details

The _deriveOrderFulfillmentHash function is responsible for hashing the order fulfillment details. If the fulfillment.recipient is incorrect or manipulated, it could lead to the wrong hash being generated, which in turn could cause the wrong party to receive the benefits of the order fulfillment.

```
function _deriveOrderFulfillmentHash(
    OrderFulfillment memory fulfillment
) internal view returns (bytes32) {
    // Derive and return the fulfilmment hash as specified by EIP-712
    return keccak256(abi.encode(_ORDER_FULFILLMENT_TYPEHASH, fulfillment.recipient));
}
```

### Mitigation

Ensure that the fulfillment.recipient is verified and correct before hashing. Additionally, the function should ensure that the encoding of the fulfillment data strictly adheres to the EIP-712 standard.

```
function _deriveOrderFulfillmentHash(
    OrderFulfillment memory fulfillment
) internal view returns (bytes32) {
    // Validate the recipient address before hashing.
    require(fulfillment.recipient != address(0), "Invalid recipient address");

    // Derive and return the fulfillment hash as specified by EIP-712, ensuring correct encoding.
    return keccak256(
        abi.encode(
            _ORDER_FULFILLMENT_TYPEHASH,
            fulfillment.recipient
        )
    );
}
```

### Impact

An incorrect order fulfillment hash can lead to unauthorized parties claiming ownership or benefits, resulting in financial losses or disputes.


## Assessed type

Invalid Validation