Omniscia Evergon Labs Audit
UniformlyProvidedVestingFacetStorage Manual Review Findings
UniformlyProvidedVestingFacetStorage Manual Review Findings
UPF-01M: Potential Cascading Rounding Errors
| Type | Severity | Location |
|---|---|---|
| Mathematical Operations | ![]() | UniformlyProvidedVestingFacetStorage.sol:L127-L130, L132, L134-L140 |
Description:
The UniformlyProvidedVestingFacetStorage::doVestingActions function integrates with the Evergon Labs ODC system to permit a linear vesting system that makes use of an EIP-1155 fractionalized asset to utilize ID subsets as the vesting period that was last claimed.
The integration utilizes a percentage-based unlock ratio that would inevitably result in truncation errors, ultimately resulting in the FractionERC1155DataManager to retain funds in its wrapper incorrectly.
Impact:
The user will continuously forfeit a small percentage of funds they are owed on each partial redemption which they cannot presently control.
Example:
121uint256 periodAfterCliff;122unchecked {123 periodAfterCliff = block.timestamp - projectVestingDetails_.cliffTimestamp;124}125uint256 amountOfPortionPeriodsPassed = periodAfterCliff / projectVestingDetails_.portionPeriod;126uint256 portionsToBePaid = amountOfPortionPeriodsPassed - idToBurn;127uint256 proportionOfFractions = (amountOfFractions * 1 ether * 1 ether) /128 projectVestingDetails_.totalFractionsProportionRemaining;129projectVestingDetails_.totalFractionsProportionRemaining -= ((amountOfFractions * portionsToBePaid * 1 ether) /130 projectVestingDetails_.amountOfPortions);131
132uint256 percentageToProvide = (proportionOfFractions * portionsToBePaid) / projectVestingDetails_.amountOfPortions;133
134ISemiFungibleFraction(GeneralStorage.layout().infoForId[campaignId].fractionsContract).burnMintAndPartiallyUnwrap(135 amountOfFractions,136 idToBurn,137 amountOfPortionPeriodsPassed,138 account,139 percentageToProvide140);Recommendation:
We advise a mechanism to account for rounding errors to be introduced, potentially by permitting the caller to specify a margin of error they are willing to accept.
Alleviation (71cda4ccfdcfa25fb96a4565f1f8143b350dd246):
The overall system was revised to mandate wholly divisible fraction amounts and thus to prevent any truncation errors from manifesting, alleviating this exhibit.
UPF-02M: Incompatible Fraction EIP-1155 ODC Integration
| Type | Severity | Location |
|---|---|---|
| Logical Fault | ![]() | UniformlyProvidedVestingFacetStorage.sol:L134-L140 |
Description:
The referenced function integrates with the FractionERC1155DataManager to facilitate early redemptions during the vesting schedule, minting a new token ID to represent the fractions that are still owed funds.
This approach is presently an incorrect integration of the FractionERC1155DataManager as the FractionERC1155DataManager::fullyUnlockWrappedAssets function permits the caller to burn the total supply of the 0 token ID to acquire all funds within the system and the FractionERC1155DataManager::partiallyUnlockWrappedAssets function behaves in a similar manner.
As such, any users that have balances in a non-zero EIP-1155 ID might have their funds stolen via the two aforementioned functions.
Impact:
Any user that has partially vested their entries and has acquired fractions in a non-zero token ID may have the assets they are owed stolen via direct interaction with the fractionsContract.
Example:
79function doVestingActions(80 Layout storage l,81 uint256 campaignId,82 uint256 idToBurn,83 address account,84 uint256 amountOfFractions85) internal {86 ProjectVestingDetails storage projectVestingDetails_ = l.projectVestingDetails[campaignId];87
88 if (projectVestingDetails_.totalFractionsProportionRemaining == 0) {89 uint256 totalFractionsPurchased = FungibleAndSemiFungiblePurchaseStorage90 .layout()91 .infoForId[campaignId]92 .totalFractionsPurchased;93 if (totalFractionsPurchased == 0) {94 revert NonCalculatableVestingProportion();95 } else {96 projectVestingDetails_.totalFractionsProportionRemaining = totalFractionsPurchased * 1 ether;97 }98 }99
100 if (block.timestamp <= projectVestingDetails_.cliffTimestamp) revert NonReachedCliff();101
102 // Vesting has ended we just burn and calculate amount (no mint)103 if (projectVestingDetails_.vestingEndingTimestamp <= block.timestamp) {104 uint256 totalAmountOfPortions = projectVestingDetails_.amountOfPortions;105 uint256 portionsToBePaid = totalAmountOfPortions - idToBurn;106 uint256 proportionOfFractions = (amountOfFractions * 1 ether * 1 ether) /107 projectVestingDetails_.totalFractionsProportionRemaining;108 projectVestingDetails_.totalFractionsProportionRemaining -= ((amountOfFractions * portionsToBePaid * 1 ether) /109 totalAmountOfPortions);110
111 uint256 percentageToProvide = (proportionOfFractions * portionsToBePaid) / totalAmountOfPortions;112
113 ISemiFungibleFraction(GeneralStorage.layout().infoForId[campaignId].fractionsContract).burnMintAndPartiallyUnwrap(114 amountOfFractions,115 idToBurn,116 0,117 account,118 percentageToProvide119 );120 } else {121 uint256 periodAfterCliff;122 unchecked {123 periodAfterCliff = block.timestamp - projectVestingDetails_.cliffTimestamp;124 }125 uint256 amountOfPortionPeriodsPassed = periodAfterCliff / projectVestingDetails_.portionPeriod;126 uint256 portionsToBePaid = amountOfPortionPeriodsPassed - idToBurn;127 uint256 proportionOfFractions = (amountOfFractions * 1 ether * 1 ether) /128 projectVestingDetails_.totalFractionsProportionRemaining;129 projectVestingDetails_.totalFractionsProportionRemaining -= ((amountOfFractions * portionsToBePaid * 1 ether) /130 projectVestingDetails_.amountOfPortions);131
132 uint256 percentageToProvide = (proportionOfFractions * portionsToBePaid) / projectVestingDetails_.amountOfPortions;133
134 ISemiFungibleFraction(GeneralStorage.layout().infoForId[campaignId].fractionsContract).burnMintAndPartiallyUnwrap(135 amountOfFractions,136 idToBurn,137 amountOfPortionPeriodsPassed,138 account,139 percentageToProvide140 );141 }142}Recommendation:
We advise the system's integration to be revised, potentially by updating either the FractionERC1155DataManager itself in the ODC repository or by updating the UniformlyProvidedVestingFacetStorage::doVestingActions approach.
Alleviation (71cda4ccfdcfa25fb96a4565f1f8143b350dd246):
The Evergon Labs team evaluated this exhibit and proceeded to update the ODC implementations to permit the deployment of an EIP-1155 fraction to define whether user unlocks are enabled.
This mechanism permits user unlocks to be disabled and thus the vulnerability described to not be exploitable as the user would not be able to interact with the ODC implementation directly to withdraw funds.

