DeFiFoundry
60,000 USDC
View results
Submission Details
Severity: low
Invalid

Inconsistent Ownership Update During NFT Transfer Process

Summary

The NFT transfer process involves updating ownership in both the TradingAccount structure and the NFT token. However, a missing function call to _update results in inconsistent ownership updates. While the owner field of the TradingAccount structure is updated, the owner of the NFT token remains unchanged, leading to discrepancies in the protocol's internal data structures.

Vulnerability Details

  • Function Definition: The _update function is designed to handle the transfer of NFTs by updating the NFT token's owner.

  • Issue: The notifyAccountTransfer function updates the owner field of the TradingAccount structure but does not invoke _update, leaving the NFT token's ownership unchanged.

  • Impact: This oversight results in a mismatch between the on-chain ownership data and the protocol's internal data structures, leading to potential inconsistencies and operational issues.

Code Snippet

`https://github.com/Cyfrin/2024-07-zaros/blob/d687fe96bb7ace8652778797052a38763fbcbb1b/src/perpetuals/branches/TradingAccountBranch.sol#L400-L410`

function notifyAccountTransfer(address to, uint128 tradingAccountId , address cd ) external {
_onlyTradingAccountToken();
GlobalConfiguration.Data storage globalConfiguration = GlobalConfiguration.load();
IAccountNFT tradingAccountToken = IAccountNFT(globalConfiguration.tradingAccountToken);
TradingAccount.Data storage tradingAccount = TradingAccount.loadExisting(tradingAccountId);
// @audit : Missing _update() . So that actual nft token is not transferred to `to` address .
tradingAccount.owner = to;

Impact

The inconsistency in ownership updates can lead to significant issues, such as:

  • Mismatch between on-chain and protocol data: This discrepancy can cause confusion and potential disputes over the true ownership of NFTs.

  • Operational Risks: The protocol's functionality may be compromised, leading to errors in account management and transfer operations.

  • User Trust: Users may lose trust in the protocol's reliability and accuracy, potentially affecting user retention and adoption.

POC

Add this function in ERC721.sol

function _ownerOfNFT(uint256 tokenId) internal returns(address) {
return _owners[tokenId];
}

Add this external function in AccountNFT.sol

function ownerOfNFT(uint256 tokenId) external returns(address) {
address NFTowner = _ownerOfNFT(tokenId);
return NFTowner ;
}
function testFuzz_Fortis_OwnerOfNFTTokenIsNotChanged(uint256 marginValueUsd) external {
changePrank({ msgSender: users.naruto.account });
marginValueUsd = bound({
x: marginValueUsd,
min: WSTETH_MIN_DEPOSIT_MARGIN,
max: convertUd60x18ToTokenAmount(address(wstEth), WSTETH_DEPOSIT_CAP_X18)
});
deal({ token: address(wstEth), to: users.naruto.account, give: marginValueUsd });
uint128 tradingAccountId = createAccountAndDeposit(marginValueUsd, address(wstEth));
changePrank({ msgSender: address(tradingAccountToken) });
// @audit: The trading account token is not actually transferred even after transferring the
// account actual nft is not trasferred .
address previousowner = tradingAccountToken.ownerOfNFT(tradingAccountId);
perpsEngine.notifyAccountTransfer(users.madara.account, tradingAccountId );
address newowner = tradingAccountToken.ownerOfNFT(tradingAccountId);
assertEq(previousowner , newowner);
}

Logs

VM::startPrank(Trading Account NFT: [0x6E1734aC57e76fcD7fD66266Ae0C2547dB3A713a])
│ └─ ← [Return]
├─ [570] Trading Account NFT::ownerOfNFT(1)
│ └─ ← [Return] Naruto Uzumaki: [0xE9480E7b47FfAd4A512FabE731477A7565BC8229]
├─ [4993] Perps Engine::notifyAccountTransfer(Madara Uchiha: [0x61AAb9b793d25b8563df2dAAC6235627D972E34A], 1)
│ ├─ [2247] TradingAccountBranch::notifyAccountTransfer(Madara Uchiha: [0x61AAb9b793d25b8563df2dAAC6235627D972E34A], 1) [delegatecall]
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [570] Trading Account NFT::ownerOfNFT(1)
│ └─ ← [Return] Naruto Uzumaki: [0xE9480E7b47FfAd4A512FabE731477A7565BC8229]
└─ ← [Stop]

Tools Used

Manual Review .

Recommendations

To resolve this issue and ensure consistency between on-chain ownership and the protocol's internal data structures, the _update function should be properly invoked during the account transfer process.

  1. Modify Function Scope: Make an external update function in AccountNFT.sol

    function update(address to, uint256 tokenId, address auth) external returns (address) {
    address previousOwner = super._update(to, tokenId, auth);
    return previousOwner;
    }

    `

  2. Ensure Consistent Calls: Verify that notifyAccountTransfer includes a call to update to maintain consistency across all ownership data and add an extra argument auth` in `notifyAccountTransfer` .

    `

    ++ function notifyAccountTransfer(address to, uint128 tradingAccountId , address auth ) external {
    _onlyTradingAccountToken();
    ++ GlobalConfiguration.Data storage globalConfiguration = GlobalConfiguration.load();
    ++ IAccountNFT tradingAccountToken = IAccountNFT(globalConfiguration.tradingAccountToken);
    TradingAccount.Data storage tradingAccount = TradingAccount.loadExisting(tradingAccountId);
    tradingAccount.owner = to;
    ++ tradingAccountToken.update(to, tradingAccountId, auth );
    }

    `

Updates

Lead Judging Commences

inallhonesty Lead Judge
over 1 year ago
inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!