Omniscia Evergon Labs Audit

ComplexRewardDistributionFacetStorage Manual Review Findings

ComplexRewardDistributionFacetStorage Manual Review Findings

CRF-01M: Discrepant Input Sanitization

Description:

The referenced input sanitization code block appears to be invalid as the condition it evaluates differs from what the error code implies.

Impact:

The severity of this exhibit will be re-assessed once the desired behaviour is set in place.

Example:

packages/contracts/contracts/rewardsDistribution/complexRewardDistribution/ComplexRewardDistributionFacetStorage.sol
108if (rewardDistributor == address(this)) {
109 revert InvalidZeroAddressForRewardDistributor(campaignId);
110}

Recommendation:

We advise either the error code or the conditional to be corrected, either of which we consider an acceptable alleviation for this exhibit.

Alleviation (b64b659786cf3c84bea52feb3a69f546ba3601f0):

The error name was updated to indicate that an invalid address was specified for the rewardDistributor and the code itself was updated to also evaluate whether the distributor is the zero-address, thereby addressing this exhibit in full.

CRF-02M: Incorrect Order of Reward Update

Description:

The ComplexRewardDistributionFacetStorage::applyStake function will update the rewards of a campaign after the virtual accounting system of the campaign has been updated thereby permitting a new stake to tap into any unclaimed rewards of a campaign, incentivizing users to perform significant instantaneous deposits and withdrawals to capture free rewards.

Impact:

Any pending rewards of a campaign will be distributed to a user's newly deposited stake which is incorrect.

Example:

packages/contracts/contracts/rewardsDistribution/complexRewardDistribution/ComplexRewardDistributionFacetStorage.sol
122/**
123 * @notice Creates a new staking position in the specified campaign.
124 * @dev If no amount multiplier is applied, `virtualPacketsStaked` will be equal to `packetsStaked`.
125 * @param l A reference to the Layout struct in storage.
126 * @param campaignId The unique identifier of the targeted staking campaign.
127 * @param nftId The unique identifier of the NFT associated with the position.
128 * @param virtualPacketsStaked The number of staked (input) packets adjusted by the applicable amount multiplier.
129 * @param packetsStaked The raw number of staked packets, without accounting for amount multipliers.
130 */
131function applyStake(
132 Layout storage l,
133 uint256 campaignId,
134 uint256 nftId,
135 uint256 virtualPacketsStaked,
136 uint256 packetsStaked
137) internal {
138 GeneralStorage.Campaign storage campaign = GeneralStorage.layout().campaignsInfo[campaignId];
139 GeneralStorage.NftInfo storage nftInfo = GeneralStorage.layout().nftInfo[nftId];
140
141 campaign.totalPacketsStaked += packetsStaked;
142 campaign.virtualTotalPacketsStaked += virtualPacketsStaked;
143 nftInfo.packetsStaked = packetsStaked;
144 nftInfo.virtualPacketsStaked = virtualPacketsStaked;
145
146 updateReward(l, campaignId, nftId);
147}

Recommendation:

We advise the code to update the rewards of the campaign prior to the stake being applied to the campaign's state, ensuring that the code behaves identically to Synthetix-like systems and preventing arbitrage opportunities from arising.

Alleviation (b64b659786cf3c84bea52feb3a69f546ba3601f0):

The code was adjusted per our recommendation, updating the rewards involved for a particular NFT ID before its packet accounting as well as the NFT's packet accounting is updated.