IOP _ ThunderNFT 34973 - [Smart Contract - Low] royalty_managerregister_royalty_info might not work

Submitted on Mon Sep 02 2024 03:53:58 GMT-0400 (Atlantic Standard Time) by @jasonxiale for IOP | ThunderNFT

Report ID: #34973

Report type: Smart Contract

Report severity: Low

Target: https://github.com/ThunderFuel/smart-contracts/tree/main/contracts-v1/royalty_manager

Impacts:

  • Contract fails to deliver promised returns, but doesn't lose value

Description

Brief/Intro

royalty_manager.register_royalty_info is used to set RoyaltyInfo by the owner/admin of NFT token, however, in current implementation, there is an issue that when a NFT has both owner and admin, the RoyaltyInfo can only be set by the owner.

Vulnerability Details

According to royalty_manager.register_royalty_info, the RoyaltyInfo can be set by NFT's owner or admin. But there is a flow in royalty_manager#L55-L65, if the NFT has an owner, and the msg_sender is not the owner, the function will revert even the msg_sender is the NFT's amdin

 48     fn register_royalty_info(
 49         collection: ContractId,
 50         receiver: Identity,
 51         fee: u64
 52     ) {
 53         let ownable = abi(Ownable, collection.into());
 54 
 55         if (ownable.owner().is_some()) { <<<--- In this if branch, if the msg_sender is not the NFT's owner, the function will revert.
 56             let caller = msg_sender().unwrap();
 57             let collection_owner = ownable.owner().unwrap();
 58             require(caller == collection_owner, RoyaltyManagerErrors::CallerMustBeOwnerOrAdmin);
 59         } else if (ownable.admin().is_some()) {
 60             let caller = msg_sender().unwrap();
 61             let collection_admin = ownable.admin().unwrap();
 62             require(caller == collection_admin, RoyaltyManagerErrors::CallerMustBeOwnerOrAdmin);
 63         } else {
 64             revert(111)
 65         }
 66 
 67         require(fee <= storage.fee_limit.read(), RoyaltyManagerErrors::FeeHigherThanLimit);
 68 
 69         let info = RoyaltyInfo {
 70             collection: collection,
 71             receiver: receiver,
 72             fee: fee
 73         };
 74 
 75         let option_info: Option<RoyaltyInfo> = Option::Some(info);
 76         storage.royalty_info.insert(collection, option_info);
 77 
 78         log(RoyaltyRegistryEvent {
 79             royalty_info: info
 80         });
 81     }

Impact Details

If a NFT has both owner and admin, the admin can't call royalty_manager.register_royalty_info.

References

Add any relevant links to documentation or code

Proof of concept

Proof of Concept

POC

  1. please use the follow code as NFT contract

  1. Please generate a Rust test template under thunder_exchange folder, and puts the following code in thunder_exchange/tests/harness.rs and run cargo test -- --nocapture

As shown above, tx will revert when the royalty_manager.register_royalty_info is called by wallet_3, which is set as admin for ERCOwnerAndAdmin. And if the royalty_manager.register_royalty_info is called by wallet_1 (which is set as owner of ERCOwnerAndAdmin), the function will not revert.

Last updated

Was this helpful?