<!-- canonical: https://0xsimao.com/findings/evro-finance-ii-trove-duplicates-erc721-enumerable -->

# TroveNFT Duplicates ERC721Enumerable Logic

Low/Info · Sherlock · EUR stablecoin · 26th December 2025

Finding L-3 of the Evro Collateral Onboarding security review.

- Report: /reports/evro-collateral-onboarding
- Source: https://github.com/0xsimao/audits/blob/main/Sherlock/private-audits/2025-12-26-evro-finance-ii.pdf

---

## Summary
The `TroveNFT` contract maintains a custom `_ownerToTroveIds` mapping that duplicates functionality already provided by the inherited `ERC721Enumerable` extension.

## Vulnerability Detail
`ERC721Enumerable` already maintains mappings to track all tokens owned by each address via `_ownedTokens` mapping and provides `tokenOfOwnerByIndex()` function. The custom `_ownerToTroveIds` array duplicates this logic, increasing gas costs and storage usage.

## Impact
Unnecessary gas costs for minting/transferring and storage overhead.

## Code Snippet
https://github.com/sherlock-audit/2025-12-evro-finance-dec-26th/blob/main/evro/contracts/src/TroveNFT.sol#L24

```solidity
mapping(address => uint256[]) private _ownerToTroveIds;  // @audit - duplicates ERC721Enumerable

function ownerToTroveIds(address owner) external view returns (uint256[] memory) {
    return _ownerToTroveIds[owner];
}
```

ERC721Enumerable already provides:
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/extensions/ERC721Enumerable.sol#L18-L19

```solidity
mapping(address => mapping(uint256 => uint256)) private _ownedTokens;
// Can iterate using tokenOfOwnerByIndex(owner, index) for index in [0, balanceOf(owner))
```

## Tool Used
Manual Review

## Recommendation
Remove the custom mapping and use ERC721Enumerable's built-in functionality:

```solidity
function ownerToTroveIds(address owner) external view returns (uint256[] memory) {
    uint256 balance = balanceOf(owner);
    uint256[] memory troveIds = new uint256[](balance);
    for (uint256 i = 0; i < balance; i++) {
        troveIds[i] = tokenOfOwnerByIndex(owner, i);
    }
    return troveIds;
}
```

Disclosed by 0xSimao (https://0xsimao.com/).

---

Related findings:

- [Major insolvency risk in LiquidationLogic::executeCrossLiquidateERC721() due to not setting a maximum liquidation price](https://0xsimao.com/findings/benddao-insolvency-liquidation-liquidate-erc721): BendDAO
