xanoutas/solidity-security-findings huggingface dataset:
# `setbidtobuy` allows token purchase even when sale is no longer listed  
Audit: 2024-10-coded-estate-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-10-coded-estate/blob/main/contracts/codedestate/src/execute.rs#L646-L720


# Vulnerability details

## Impact  
The bug allows buyers to purchase tokens that have been delisted by the seller, bypassing the seller’s intent to halt the sale. This can result in tokens being sold against the seller's wishes.  

## Description  
The `setbidtobuy` function is responsible for allowing buyers to submit bids to purchase a token listed for sale. A seller can invoke `setlistforsell` to list a token, specifying the price, payment token (denom), and whether the sale is auto-approved. If auto-approve is set to `true`, any buyer who calls `setbidtobuy` can acquire the token without further input from the seller, while a manual approval is required when auto-approve is set to `false`.  

However, there is a flaw in the logic of `setbidtobuy`—it does not check the `sell.islisted` flag, which is supposed to indicate whether a token is still available for sale. Even if the seller later decides to delist the token by setting `sell.islisted` to `false`, buyers can still invoke `setbidtobuy` and proceed with the purchase if auto-approve is enabled. This creates a scenario where sellers lose control over the sale, allowing unintended buyers to purchase delisted tokens.

### Example Scenario:  
1. A seller lists a token using `setlistforsell`, specifying the sale details including price, payment token, and setting `auto-approve` to `true`.  
2. After some time, the seller receives no bids and decides to delist the token, changing `sell.islisted` to `false` while leaving other parameters unchanged.  
3. A buyer invokes `setbidtobuy`, and because the function does not respect the `islisted` flag and auto-approve is `true`, the token is sold despite the seller’s intent to delist it. This results in an unintended sale, leading to potential loss or misuse of assets by the seller.  

### Additional note  
An action of delisting the token on sale in this manner is justified because there is no other functions serving this purpose as in short-term rental and long-term rental where there is a specific function to unlist the token from rental service.  

### Code Snippet  
The following snippet shows that the `islisted` flag is not verified in `setbidtobuy`, which allows unintended purchases:  
```rust
pub fn setlistforsell(
    &self,
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    islisted:bool,
    token_id: String,
    denom: String,
    price: u64,
    auto_approve: bool,
) -> Result<Response<C>, ContractError> {
    let mut token = self.tokens.load(deps.storage, &token_id)?;
    // ensure we have permissions
    self.check_can_approve(deps.as_ref(), &env, &info, &token)?;
    // @c4-contest islisted indicates whether token is available for sale or not
    token.sell.islisted = Some(islisted);
    token.sell.price = price;
    token.sell.auto_approve = auto_approve;
    token.sell.denom = denom;
    self.tokens.
Logic flaw in `check_can_edit_long` allows editing long-term rental before finalization enabling theft of users' deposited funds  
Audit: 2024-10-coded-estate-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-10-coded-estate/blob/main/contracts/codedestate/src/execute.rs#L1974-L1992


# Vulnerability details

## Impact
This vulnerability allows a malicious property owner (landlord) to change the token denomination (`denom`) of an active rental after the rental period has ended but before finalization. By doing so, the attacker can settle a rental in a different, more valuable token, causing a direct and significant loss of funds for other users.  

## Description
In the CodedEstate contract, the `finalizelongtermrental` function is used by property owners to settle payments for long-term rentals. When this function is called, it loads the token denomination (`denom`) from the `longterm_rental` configuration, which is initially set during the property listing through the `setlistforlongtermrental` function.  

A safety check is implemented in the `check_can_edit_long` function, ensuring that no active rentals are in place before allowing changes to the `longterm_rental` settings, including the `denom`. This check ensures that the last recorded rental's check-out time has passed, and thus no ongoing rentals should be active. However, it fails to account for whether all rentals have been finalized.  

An attacker can exploit this flaw by changing the `denom` after the rental period has ended but before the rental is finalized. They can then finalize the rental and receive a refund in a different, more valuable token.  

### Code Snippet:
```rust
pub fn finalizelongtermrental(
    // function arguments
) -> Result<Response<C>, ContractError> {
    ...
    let amount = item.deposit_amount;
    if item.cancelled {
        target = token.owner.to_string();
        self.increase_balance(deps.storage, token.longterm_rental.denom.clone(), Uint128::new((u128::from(amount) * u128::from(fee_percentage)) / 10000))?;
        amount -= Uint128::new((u128::from(amount) * u128::from(fee_percentage)) / 10000);
    }
    ...
    if amount > Uint128::new(0) {
        Ok(Response::new()
            .add_message(BankMsg::Send {
                to_address: target.clone(),
                amount: vec![Coin {
                    denom: token.longterm_rental.denom, // @c4-contest: denom is loaded from long-term rental agreement
                    amount: amount,
                }],
            }))
    }
}
```

The problem arises when the `denom` is changed in the `setlistforlongtermrental` function, without verifying whether all rentals have been finalized:
```rust
pub fn check_can_edit_long(
    &self,
    env: &Env,
    token: &TokenInfo<T>,
) -> Result<(), ContractError> {
    if token.rentals.len() == 0 {
        return Ok(());
    }
    let last_check_out_time = token.rentals[token.rentals.len() - 1].renting_period[1];
    if last_check_out_time < env.block.time.seconds() {
        return Ok(());
    } else {
        return Err(ContractError::RentalActive {});
    }
}
```

### Attack scenario:  
1. Attacker creates two a
Insufficient price validation in `transfer_nft` function enables theft of listed tokens
Audit: 2024-10-coded-estate-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-10-coded-estate/blob/main/contracts/codedestate/src/execute.rs#L365-L417


# Vulnerability details

## Impact
This vulnerability allows malicious buyers to acquire listed NFTs without payment to sellers.  
## Description
Users can list their tokens for sale by calling `setlistosell` and specifying a price and payment token (denom). Buyers can then purchase the token by calling `setbidtobuy` and transferring the payment into the contract.

The trade is finalized when `transfer_nft` is invoked and the recipient is the buyer. The caller can be the seller, or, if `auto_approve` is set to true, the caller can also be the buyer as they're given approval upon calling `setbidtobuy`.  

However, `transfer_nft` function lacks a proper validation during the transfer. This vulnerability stems from two key oversights:  

1. The function doesn't verify if the offer bid amount matches the listed price of the token.  
2. It allows caller to freely specify recipient and transfer to recipients with no active bids, defaulting to a zero payment.  

These oversights enable malicious buyers to acquire NFTs without paying the listed price, effectively stealing them from sellers.  
`transfer_nft` implementation:  
```rust
fn transfer_nft(
    &self,
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    recipient: String, // @c4-contest caller of this function can freely specify `recipient` address
    token_id: String,
) -> Result<Response<C>, ContractError> {
    let mut token = self.tokens.load(deps.storage, &token_id)?;
    // ensure we have permissions
    self.check_can_send(deps.as_ref(), &env, &info, &token)?;
    // set owner and remove existing approvals
    let prev_owner = token.owner;
    token.owner = deps.api.addr_validate(&recipient)?; // @c4-contest ownerhsip is transferred to recipient
    token.approvals = vec![];
    let fee_percentage = self.get_fee(deps.storage)?;

    let mut position: i32 = -1;
    let mut amount = Uint128::from(0u64); // @c4-contest: amount is default to zero
    for (i, item) in token.bids.iter().enumerate() {
        if item.address == recipient.to_string()
        {
            position = i as i32;
            amount = item.offer.into();
            break;  
        }
    }
    // @c4-contest: if recipient doesn't have bid on this token, amount is default to zero
    if position != -1 && amount > Uint128::new(0) {
        self.increase_balance(deps.storage, token.sell.denom.clone(), Uint128::new((u128::from(amount) * u128::from(fee_percentage)) / 10000))?;
    }
    let amount_after_fee = amount.checked_sub(Uint128::new((u128::from(amount) * u128::from(fee_percentage)) / 10000)).unwrap_or_default();
    token.bids.retain(|bid| bid.address != recipient);
    self.tokens.save(deps.storage, &token_id, &token)?;
    // @c4-contest: no validation whether the bid amount mathces with the listed price.  
    if amount > Uint128::new(0) {
        Ok(Response::new()
        .add_
possible to bid for tokens that are not for sale
Audit: 2024-10-coded-estate-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-10-coded-estate/blob/97efb35fd3734676f33598e6dff70119e41c7032/contracts/codedestate/src/execute.rs#L646


# Vulnerability details

## Impact
possible to bid for tokens that are not listed for sale. This is because `setbidtobuy()` does not check for if the property `token.sell.isListed` is true when bidding. 

## Proof Of Concept 
For a token to be sold, the token owner must call [setlListForSell()](https://github.com/code-423n4/2024-10-coded-estate/blob/97efb35fd3734676f33598e6dff70119e41c7032/contracts/codedestate/src/execute.rs#L619) to set the `price`, `denomination`, `auto-approve` and the `isListed` value which indicates if a token/rental token is now listed on the market. Because the values for all these are provided by the user, a user may call `setlListForSell()` to set `price`, `denomination` and `auto_approve` but not set `isListed` yet or set it to false because rental token owner is not just ready to sell yet but wants to give other buyers in the market an idea of the pricing or for any other reason the token owner sees fit. 

Now in this case, if this is done, if pricing and denom is set but `isListed` is false, buyers will still be able to bid via `setbidtobuy() ` even though the rental token/property is not on the market. This means buyers are bidding for a property that is not for sale. 
`setbidtobuy()` only checks for if the `token.sell.denom` isd set and equal to demonination provided by the caller/buyer  and `token.sell.price` is  lower than  or equal to the amount provided by the caller/buyer. It does not check the most important property of the `token.sell` struct which is `token.sell.isListed`. 

https://github.com/code-423n4/2024-10-coded-estate/blob/97efb35fd3734676f33598e6dff70119e41c7032/contracts/codedestate/src/execute.rs#L666-L691
```rust
        if position == -1 {
            if info.funds[0].denom != token.sell.denom {
                return Err(ContractError::InvalidDeposit {});
            }
            if info.funds[0].amount
                < Uint128::from(token.sell.price)
            {
                return Err(ContractError::InsufficientDeposit {});
            }

            if token.sell.auto_approve {
                // update the approval list (remove any for the same spender before adding)
                let expires = Expiration::Never {  };
                token.approvals.retain(|apr| apr.spender != info.sender);
                let approval = Approval {
                    spender: info.sender.clone(),
                    expires,
                };
                token.approvals.push(approval);
                
            }
            let bid = Bid {
                address: info.sender.to_string(),
                offer:info.funds[0].amount,
            };
            token.bids.push(bid);
        }
```

in the snippet above we can see that the logic does not check for if the token is listed or not before adding a new bid to the `token.bids` a
lack of access restriction for important functions that change storage variables. 
Audit: 2024-10-coded-estate-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-10-coded-estate/blob/97efb35fd3734676f33598e6dff70119e41c7032/contracts/codedestate/src/state.rs#L134-L145
https://github.com/code-423n4/2024-10-coded-estate/blob/97efb35fd3734676f33598e6dff70119e41c7032/contracts/codedestate/src/state.rs#L97-L100
https://github.com/code-423n4/2024-10-coded-estate/blob/97efb35fd3734676f33598e6dff70119e41c7032/contracts/codedestate/src/execute.rs#L325-L343
https://github.com/code-423n4/2024-10-coded-estate/blob/97efb35fd3734676f33598e6dff70119e41c7032/contracts/codedestate/src/state.rs#L122-L132
https://github.com/code-423n4/2024-10-coded-estate/blob/97efb35fd3734676f33598e6dff70119e41c7032/contracts/codedestate/src/state.rs#L110-L120


# Vulnerability details

## Impact
It is still possible to modify sensitive storage variables of the contract by directly calling the public functions in  `state.rs` and `execute.rs`.

## Proof Of Concept 
### 1.) **anyone can increment/decrement the token balance by calliing public functions  `decrement_tokens()` and `increment_tokens()`.**

https://github.com/code-423n4/2024-10-coded-estate/blob/97efb35fd3734676f33598e6dff70119e41c7032/contracts/codedestate/src/state.rs#L134C1-L145C2
```rust
    pub fn increment_tokens(&self, storage: &mut dyn Storage) -> StdResult<u64> {
        let val = self.token_count(storage)? + 1;
        self.token_count.save(storage, &val)?;
        Ok(val)
    }

    pub fn decrement_tokens(&self, storage: &mut dyn Storage) -> StdResult<u64> {
        let val = self.token_count(storage)? - 1;
        self.token_count.save(storage, &val)?;
        Ok(val)
    } 
```

These functions are callable by anyone via the contract instance and they are important functions used in [mint()](https://github.com/code-423n4/2024-10-coded-estate/blob/97efb35fd3734676f33598e6dff70119e41c7032/contracts/codedestate/src/execute.rs#L309) and [burn()](https://github.com/code-423n4/2024-10-coded-estate/blob/97efb35fd3734676f33598e6dff70119e41c7032/contracts/codedestate/src/execute.rs#L534) when a user is minting/burning a new nft. It is used to track the total number of tokens minted. So after a few users have minted, lets say 4 tokens, and the token count is 4, a malicious user can go ahead and change the token count to 5 or 0. even though token count should be 4. 



### 2.) **anyone can change the `fee_value` by calling  `contract.set_fee()`**

rental fees are meant to be set by the [contract owner](https://github.com/code-423n4/2024-10-coded-estate/blob/97efb35fd3734676f33598e6dff70119e41c7032/contracts/codedestate/src/execute.rs#L318-L319) via `set_fee_value()` function only but another user can set it by calling the public function `set_fee()` in `state.rs`. 

https://github.com/code-423n4/2024-10-coded-estate/blob/97efb35fd3734676f33598e6dff70119e41c7032/contracts/codedestate/src/state.rs#L97C1-L100C6
```rust 
    pub fn set_fee(&self, storage: &mut dyn Storage, fee: u64) -> StdResult<u64> {
        self.fee.save(stora
Lack of differentiation between rental types leads to loss of funds
Audit: 2024-10-coded-estate-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-10-coded-estate/blob/main/contracts/codedestate/src/execute.rs#L1413
https://github.com/code-423n4/2024-10-coded-estate/blob/main/contracts/codedestate/src/execute.rs#L870


# Vulnerability details

## Impact
This vulnerability allows an attacker to exploit the lack of distinction between short-term and long-term rental types to withdraw funds in a different, more valuable token than the one initially used for payment, effectively steal other users' funds deposited in the contract.  

## Description
In the CodedEstate system, a property (token) can be listed for both **short-term** and **long-term** rentals, with each rental type having separate configurations, including the denomination (`denom`) of the token used for payments. The rental information for both types of rentals is stored in the same vector, `rentals`, and a `rental_type` flag is used within the `Rental` struct to differentiate between short-term (`false`) and long-term (`true`) rentals.  

```rust
File: packages/cw721/src/query.rs
pub struct Rental {
    pub denom: String,
    pub deposit_amount: Uint128,
    pub rental_type: bool,  // @c4-contest: differentiates between short-term (false) and long-term (true) rentals
    pub cancelled: bool,
    pub renting_period: Vec<u64>,
    pub address: Option<Addr>,
    pub approved: bool,
    pub approved_date: Option<String>,
    pub guests: usize,
}

File: contracts/codedestate/src/execute.rs
pub struct TokenInfo<T> {
    pub owner: Addr,
    pub approvals: Vec<Approval>,
    pub longterm_rental: LongTermRental, // long-term rental agreemnt
    pub shortterm_rental: ShortTermRental, // short-term rental agreement
    pub rentals: Vec<Rental>, // @c4-contest: both types of rental are saved in this vector
    pub bids: Vec<Bid>,
    pub sell: Sell,
    pub token_uri: Option<String>,
    pub extension: T,
}
```

However, the contract does not make use of the `rental_type` flag in any function that handles rental operations. As a result, functions designated for short-term rentals can be used for long-term rentals, and vice versa, without proper validation of the rental type. This becomes problematic, especially since short-term and long-term rentals may use different `denom` tokens.  
```rust
File: contracts/codedestate/src/execute.rs
pub fn setlistforshorttermrental(
// function arguments
) -> Result<Response<C>, ContractError> {
    let mut token = self.tokens.load(deps.storage, &token_id)?;
    // ensure we have permissions
    self.check_can_approve(deps.as_ref(), &env, &info, &token)?;
    self.check_can_edit_short(&env, &token)?;

    token.shortterm_rental.islisted = Some(true);
    token.shortterm_rental.price_per_day = price_per_day;
    token.shortterm_rental.available_period = available_period;
    token.shortterm_rental.auto_approve = auto_approve;
    token.shortterm_rental.denom = denom; // @c4-contest <-- can be a different denom from long-term rental
    token.shortterm_ren
Cancelling bid doesn't clear token approval of bidder allows malicious bidder to steal any tokens listing for sale with auto-approve enabled
Audit: 2024-10-coded-estate-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-10-coded-estate/blob/main/contracts/codedestate/src/execute.rs#L646-L720


# Vulnerability details

## Impact
This vulnerability allows malicious actors to steal tokens on sell with auto-approve enabled without payment to sellers.  

## Description
The bug arises from an oversight in the token approval management within the bidding and cancellation process. When a seller sets `auto_approve` to true for their token, a bidder is granted approval upon calling the `setbidtobuy` function. This approval is intended to allow the buyer to call the `transfer_nft` function themselves to complete the trade.  

The `transfer_nft` function performs the following actions:  
1. Clears all approvals  
2. Transfers ownership to the buyer  
3. Transfers funds to the seller  

However, a flaw exists in the bid cancellation process. When a buyer cancels their bid by calling `setbidtobuy` again, the function removes their bid and returns the deposited funds, but it fails to revoke the previously granted approval.  

This oversight allows a malicious buyer to exploit the system through the following steps:  
1. Bid on a token with `auto_approve` set to true, gaining approval.  
2. Immediately cancel the bid, receiving a refund while retaining the approval.  
3. Call `transfer_nft` to transfer the token to themselves without payment, as their bid has been deleted from cancelling process.  

This bug effectively allows the attacker to steal the token from the seller without providing any payment to seller.  

The severity is set as high because the token (property) listing for sell must have an intrinsic monetary value or else it would not make sense to list it for sale. For example, it could be a property that already has a long-term renter and is receiving a stable income from said renter.  

### Relevant code snippet
```rust
pub fn setbidtobuy(
    &self,
    deps: DepsMut,
    _env: Env,
    info: MessageInfo,
    token_id: String,
) -> Result<Response<C>, ContractError> {
    
    ...snipped...
    // @c4-contest cancellation case
    else {
        // update the approval list (remove any for the same spender before adding)
        token.bids.retain(|item| item.address != info.sender); // @c4-contest <-- remove bid but doesn't clear approvals
    }

    self.tokens.save(deps.storage, &token_id, &token)?;
    // @c4-contest cancellation case refunds the bidder
    if position != -1 && (amount > Uint128::from(0u64)) {
        Ok(Response::new()
        .add_attribute("action", "setbidtobuy")
        .add_attribute("sender", info.sender.clone())
        .add_attribute("token_id", token_id)
        .add_message(BankMsg::Send {
            to_address: info.sender.to_string(),
            amount: vec![Coin {
                denom: token.sell.denom,
                amount: amount,
            }],
        }))
    }
    ...snipped...

}
```
## Proof-of-Concept
The following test demonstrate the described scenario.  

Boile