IOP _ ThunderNFT 34585 - [Smart Contract - High] Permanent freezing of NFTS that seller deposit into
Submitted on Fri Aug 16 2024 15:11:08 GMT-0400 (Atlantic Standard Time) by @zeroK for IOP | ThunderNFT
Report ID: #34585
Report type: Smart Contract
Report severity: High
Target: https://github.com/ThunderFuel/smart-contracts/tree/main/contracts-v1/thunder_exchange
Impacts:
Permanent freezing of NFTs
Description
Brief/Intro
the cancle_function in thunderNFT_exchange allow users with active orders to cancel their orders, the most important thing is that the cancle_order allow seller to cancel and take back their NFT, however this is not always the case when the strategy whitelist address get updated for any reason(new strategy supporting or previous one have an issue) since there is a check that prevent using previous strategies that removed from whitelist, this way the seller NFT will be stuck forever and since the execute_order depends on the strategy the NFT itself can not be purchased or accepting any offers.
NOTE: this report is combine of report id 34567 which prevent updating the payment asset too, this way user can not update the strategy because the update_order revert too.
Vulnerability Details
when seller want to cancel its order the function below should get called which transfer the NFT back to the owner:
/// Cancels MakerOrder
#[storage(read)]
fn cancel_order(
strategy: ContractId,
nonce: u64,
side: Side
) {
let caller = get_msg_sender_address_or_panic();
let execution_manager_addr = storage.execution_manager.read().unwrap().bits();
let execution_manager = abi(ExecutionManager, execution_manager_addr);
require(strategy != ZERO_CONTRACT_ID, ThunderExchangeErrors::StrategyMustBeNonZeroContract);
require(execution_manager.is_strategy_whitelisted(strategy), ThunderExchangeErrors::StrategyNotWhitelisted); //@audit
let strategy_caller = abi(ExecutionStrategy, strategy.bits());
let order = strategy_caller.get_maker_order_of_user(caller, nonce, side); // get the order for the caller
match side {
Side::Buy => {
// Cancels buy MakerOrder (e.g. offer)
strategy_caller.cancel_order(caller, nonce, side);
},
Side::Sell => {
// Cancel sell MakerOrder (e.g. listing)
if (order.is_some()) {
// If order is valid, then transfers the asset back to the user
let unwrapped_order = order.unwrap();
strategy_caller.cancel_order(caller, nonce, side);
transfer(
Identity::Address(unwrapped_order.maker),
AssetId::new(unwrapped_order.collection, unwrapped_order.token_id),
unwrapped_order.amount
);
}
},
}
log(OrderCanceled {
user: caller,
strategy,
side,
nonce,
});
}
if the strategy address got updated, then its impossible to user to transfer back their NFTs, the codebase itself not seem like its upgradable because of missing the upgradability requirement which mean even if the seller set the whitelisted strategy this won't allow him to get back his NFT since the seller have no valid order in the new strategy address.
combining this with the report id 34567, the seller will never be able to cancel or execute his order and updating the sell order won't sent back the NFT.
Impact Details
Permanent freezing of NFT can occur when new strategy listed as whitelist, this is possible when the payment asset removed from whitelist which prevent calling update_order and when the strategy removed from whitelist which prevent calling cancel or execute order.
References
checking for the strategy whitelist is not very important for canceling orders since the protocol allow deposit and create order only when the strategy is whitelisted.
we prefer setting the contract to upgradable since this way the data will be copied to the new implemented strategy.
Proof of concept
Proof of Concept
tIMPORTANT: he notes from report id 34567 should be taken in mind before running this POC
Last updated
Was this helpful?