Guardian's review of Vouching and Slashing for Ethos Network, published July 2026. The report records 56 findings, including 5 medium and 17 low.
- Published
- Language
- Solidity
- Chains
- Base
- Sector
- Infrastructure
- 0 Critical
- 0 High
- 5 Medium
- 17 Low
- 34 Informational
Findings 56
-
M-01 Medium Slashing Can Be Avoided Frontrunning L O C A T I O N src/EthosVouchV2.sol#L544-L574 R E V I E W Main Review Acknowledged
Description
Users can avoid being slashed if they already know that their behavior has been publicly exposed.
For example:
Eve vouches on Ethos.
Eve performs a malicious action for personal gain, hoping that it will never be discovered.
Alice discovers Eve’s activity and publicly reveals it on X.
Many people, including Bob and Eve, see the statement, and Bob tries to slash Eve on Ethos.
Eve now still has time until Ethos signs the slash and Bob executes it. In the meantime, she can call
unvouchto avoid penalties.Recommendation
Be aware and consider to make
unvouchanddecreaseVoucha 2-Step flow with a time delay to reduce the risk of users exiting the system before they can be slashed.Resolution
Ethos Network Team - Acknowledged.
-
M-02 Medium Frozen Vouches Can Still Grow Logical Error L O C A T I O N src/EthosVouchV2.sol R E V I E W Main Review Acknowledged
Description
Financial slash creation snapshots and freezes the subject/author addresses, but EthosVouchV2 only blocks frozen accounts from
decreaseVouchandunvouch. Frozen accounts can still callvouch,vouchWithPermit,vouchForvia EthosReview, andincreaseVouch.At resolution execution, EthosSlash does not burn a snapshotted balance or set of vouch IDs. Instead it calls
EthosVouchV2.slash(addr, bps)for each snapshotted address, and EthosVouchV2 iterates over that address's current active vouches. As a result, vouches created or increased after the account was frozen but beforeexecuteResolution()are included in the financial slash.This will encourage gaming (to vouch with other not connected accounts instead) and decentivize users from vouching.
Recommendation
Consider to reject
vouch,vouchWithPermit,vouchFor,increaseVouch, andincreaseVouchWithPermitwhen the author is frozen, or to document this behavior.Resolution
Ethos Network Team - Acknowledged.
-
M-03 Medium ceil totalAccruedNotPaid increment to prevent claim() DoS L O C A T I O N Not specified R E V I E W Main Review Resolved
Description
EthosRewards.claim()can permanently revert for a dominant committed-balance holder, locking their accrued rewards with no recovery path. No theft / over-emission but legitimate rewards become permanently unclaimable.Per-interval floor rounding in
updateRewardcaused accumulated residual such that a dominant holder'searned()could exceedtotalAccruedNotPaidby a few wei (confirmed: 5 wei over 10 daily intervals). The checked subtractiontotalAccruedNotPaid -= rewardthen panicked0x11and permanently locked that user's rewards - a claim-blocking DoS.Recommendation
Switch the global
mulDivinupdateRewardfrom floor to ceil (Math.Rounding.Ceil) sototalAccruedNotPaid>=sum ofclaimableat every snapshot. Solvency in the other direction (balance>=totalAccruedNotPaid) is preserved: the increment is capped atmaxIncrement = floor(available * WAD / totalCommitted), soceil(increment *totalCommitted / WAD)<=available.Defense-in-depth: Clamp the claim debit to
Math.min(reward, totalAccruedNotPaid)so a future rounding regression cannot re-introduce the revert.Resolution
Ethos Network Team - Resolved.
-
M-04 Medium Composite vouch blocked during lock Unexpected Behavior L O C A T I O N src/legacy/EthosReview.sol#L349-L350 R E V I E W Main Review Resolved
Description
EthosWhuffie._update()blocks all locked-period transfers unless the transfer is a mint, a burn, or eitherfromortois the registeredETHOS_VOUCH_V2address. This allows directEthosVouchV2.vouch()flows during the initial non-transferable period, because the user transfersWHUFdirectly toEthosVouchV2.However,
EthosReview.reviewAndVouchWithPermit()first pulls the combined review fee and vouch gross amount from the user intoEthosReviewwithsafeTransferFrom(). During the lock period, this first transfer isuser-> EthosReview, so neither side isETHOS_VOUCH_V2andEthosWhuffie._update()reverts withTransfersLocked. As a result, the atomic review-and-vouch EOA flow is unusable until transfers are unlocked, despite the project ADR describing the function as the launch-window path for EOA review + vouch.Recommendation
Update the locked-transfer rules so the intended composite path is explicitly supported. For example,
EthosWhuffie._update()can also exempt the registeredETHOS_REVIEWcontract when transfers are lockedResolution
Ethos Network Team - Resolved.
-
M-05 Medium Authors can exit stake before slashing DoS L O C A T I O N EthosSlash.sol, EthosVouchV2.sol R E V I E W Main Review Resolved
Description
FINANCIALslashes rely on the slash author having economic skin in the game:createSlash()freezes the author's snapshotted profile addresses, and a laterDEFENDEDresolution burns the author's active VouchV2 stake. However, the signedcreateSlash()payload does not bind any minimum author stake or author address snapshot, andcreateSlash()does not verify that the frozen author addresses still hold a minimum slashable balance when the transaction is executed.This creates a time-of-check/time-of-use gap around Echo signing. An author can obtain a valid
createSlash()signature while they still have slashable stake, then callunvouch()ordecreaseVouch()before submittingcreateSlash(). Since the account is not frozen untilcreateSlash()executes, the withdrawal succeeds and reduces the author's active VouchV2 stake. The author can also remove a stake-holding secondary address from their profile before submitting the signed slash, causing_recordFinancialSlash()to snapshot only the remaining current profile addresses.After this,
createSlash()still succeeds and freezes the subject, but the author has little or no remaining stake that can be burned if the slash resolvesDEFENDED. This undermines the documented economic deterrent for frivolousFINANCIALslashes. Because the author no longer has meaningful downside, they do not need to cancel the slash duringcancelWindow; the subject can remain frozen until the full slashdurationelapses, and potentially longer ifresolveSlash()orexecuteResolution()is delayed. The only early release path for the subject is admin cancellation followed byexecuteResolution() .Recommendation
Bind and enforce an execution-time author stake requirement for
FINANCIALslashes. For example, add a signedminAuthorCommittedBalance fieldtothe createSlash() payload.During createSlash() ,after_recordFinancialSlash()snapshots the author addresses, sum the committed VouchV2 balance for the exact frozen author addresses and revert if the total is belowminAuthorCommittedBalance.EthosRewards.committedBalancecan be used as the efficient source of active vouch principal if it is kept in sync byvouch,increaseVouch,unvouch(),decreaseVouch(), and slash burns. Consider also adding an optional signedminSubjectCommittedBalancefield so Echo can require the subject-side frozen addresses to still hold enough slashable stake when a financial slash is intended to be economically enforceable. Both minimums can be set to zero for flows where Echo intentionally allows no-stake slashes.Resolution
Ethos Network Team - Resolved.
-
L-01 Low currentRewardRateBps Can Be Stale Unexpected Behavior L O C A T I O N src/EthosRewards.sol#L346-L352 R E V I E W Main Review Resolved
Description
currentRewardRateBps() reportstheeffectiverewardratefrom available = rewardToken.balanceOf(address(this)) -totalAccruedNotPaid. This only subtracts rewards that have already been written into storage by a checkpoint. It does not simulate pending global accrual sincelastUpdateTime.Recommendation
Consider to have
currentRewardRateBps()account for pending global accrual before computing the available balance.Resolution
Ethos Network Team - Resolved.
-
L-02 Low Reward Deposits Accrue Retroactively Logical Error L O C A T I O N src/EthosRewards.sol#L323-L324 R E V I E W Main Review Resolved
Description
EthosRewards is funded by plain ERC20 transfers instead of a deposit function.
Because a direct transfer into the contract does not update
lastUpdateTime, newly transferred reward tokens are treated as if they were available for the entire elapsed interval since the last checkpoint. For example, if no rewards were available for a week, a treasury/user transfers WHUF into EthosRewards, and then any credit/debit/claim/update path runs, the new WHUF is emitted using a one-weekdt. If an update had been called immediately before the transfer, the same deposit would only accrue from the post-transfer time. This makes reward distribution dependent on deposit/checkpoint ordering and can over-reward users who were committed before the reward tokens actually arrived.This can also impact the initial rewards if the
lastUpdateTimeis not refreshed first.Recommendation
Consider to add an explicit deposit rewards function that runs
updateReward, then pulls the tokens viasafeTransferFromand emits aRewardsDepositedevent. Or consider to document this behavior.If not, initialize
EthosRewardswithemissionRateBps = 0and change it to1980after the rewards are sent to the contract. This way reward accumulation starts from thesetEmissionRate()call.Resolution
Ethos Network Team - Resolved.
-
L-03 Low Zero Increase Emits Misleading Event Events L O C A T I O N src/EthosVouchV2.sol#L852-L875 R E V I E W Main Review Resolved
Description
EthosVouchV2._increaseVouch()does not rejectamount==0. The caller must be the vouch author, so this is not arbitrary-user event spam, but the author can callincreaseVouch(vouchId, 0).That path computes zero fee and zero gross transfer, calls
_checkTransferIn(0), leaves the balance unchanged, and still emits aVouchIncreasedevent.Frontends, indexers, or analytics that treat every VouchIncreased event as a real top-up can display misleading activity even though no stake was added or break in case the frontend doesn't handle this misleading event badly.
Recommendation
Consider to reject zero-amount increases or to avoid emitting an event in that case.
Resolution
Ethos Network Team - Resolved.
-
L-04 Low increaseVouch Skips Minimum Check Validation L O C A T I O N src/EthosVouchV2.sol#L852-L875 R E V I E W Main Review Resolved
Description
EthosVouchV2 enforces
configuredMinimumVouchAmountwhen creating a new vouch, but_increaseVouch()does not enforce that an active vouch's resulting balance is at least the minimum.This matters after slashing: a slash can reduce an active vouch to a sub-minimum or even zero balance while leaving it unarchived and still present in the author indexes/target uniqueness mapping. The author can then increase the vouch by a tiny amount while keeping the active balance below the configured minimum instead of having to satisfy the same minimum required for fresh vouches.
This breaks the minimum-balance invariant.
Recommendation
Consider to require the resulting balance to be at least
configuredMinimumVouchAmountwhenever an active vouch is increased. Alternatively, archive and remove vouches that reach (almost) zero during a slash.Resolution
Ethos Network Team - Resolved.
-
L-05 Low Paused Markets Allow Token Transfers Unexpected Behavior L O C A T I O N src/EthosMarket.sol#L441-L447 R E V I E W Main Review Acknowledged
Description
Pausing a market through EthosMarket only blocks protocol-level open and close operations. The trust and distrust PositionToken contracts remain normal transferable ERC20s, so holders can still transfer or sell those position tokens on third-party markets while the actual Ethos market is paused or frozen.
This means market pause does not fully freeze exposure ownership; it only freezes the protocol's own buy/sell entrypoints.
Recommendation
Consider to pause this too in that case or to document this behavior.
Resolution
Ethos Network Team - Acknowledged.
-
L-06 Low Attestation Review Index Split Documentation L O C A T I O N src/legacy/EthosProfile.sol#L311-L321 R E V I E W Main Review Acknowledged
Description
When an unresolved attestation is reviewed, EthosReview stores the review in reviewIdsByAttestationHash[attestationHash]. If that attestation is later linked to an existing profile, EthosProfile.assignExistingProfileToAttestation() only notifies legacy EthosVouch through handleAttestationClaim() before setting profileIdByAttestation. It does not notify EthosReview or migrate/alias the review indexes. As a result, reviews created before the attestation claim remain discoverable through the original attestation-hash index rather than being merged into profile/address-style lookup semantics after the claim.
Recommendation
Consider to either document that attestation-created reviews remain under the original attestation hash after the attestation is linked to a profile and handle it accordingly in the frontend or add merged lookup/alias behavior.
Resolution
Ethos Network Team - Acknowledged.
-
L-07 Low Identity Remap Enables Self Reviews Validation L O C A T I O N src/legacy/EthosProfile.sol R E V I E W Main Review Acknowledged
Description
EthosReview prevents self-reviews only against the identity mapping that exists when the review is created. If a user reviews an address that is not registered yet, the review path creates/uses a mock profile for that subject. Later, the author can register that reviewed address as a secondary address on their own real profile. After that registration, current identity resolution maps the reviewed subject back to the author's own profile, so the review violates the intended no-same-profile-review invariant.
Recommendation
Consider documenting that the no-self-review check can be broken and handle this accordingly in the frontend.
Resolution
Ethos Network Team - Acknowledged.
-
L-08 Low Wrong error for maximum below minimum Error Resolved
Description
EthosVouchV2.initialize() and EthosVouchV2.setMaximumVouchAmount() revertwithAmountAboveMaximum when theprovided maximum vouch amount is lower than the configured minimum. This is the inverse of the actual validation failure: the provided value is below the minimum acceptable value. The repository already defines
AmountBelowMinimum(uint256provided, uint256 minimum)for this condition, whileAmountAboveMaximumis documented for vouch creation or increases that would exceed the configured maximum.Recommendation
Use
AmountBelowMinimum(configuredMaximumVouchAmount_, configuredMinimumVouchAmount_)ininitialize()andAmountBelowMinimum(amount, configuredMinimumVouchAmount) in setMaximumVouchAmount() .Resolution
Ethos Network Team - Resolved.
-
L-09 Low Duplicate Profile Addresses Persist Unexpected Behavior L O C A T I O N src/legacy/EthosProfile.sol#L385-L388 R E V I E W Main Review Acknowledged
Description
EthosProfile.registerAddress()can append the same address to the same profile multiple times. The function only rejects an address that is already registered to a different profile; ifprofileStatusByAddress(addressStr)resolves to the sameprofileId, it still updates the index mapping and pushes another copy intoprofiles[profileId].addresses.Because
deleteAddress()/deleteAddressAtIndex()removes only one array entry and then clearsprofileIdByAddress[addressStr], a duplicate can leave a stale copy inaddressesForProfile(profileId)even after the address has been unclaimed. Which can lead to weird unexpected behavior in the system.This requires the trusted signer/backend to issue multiple authorizations for the same address/profile pair, so users cannot create it unilaterally, but the contract does not enforce the uniqueness invariant on-chain.
Recommendation
Consider to enforce address uniqueness within each profile on-chain. Or document this and be aware to not issue signatures in case the address was already added to the profil.
Resolution
Ethos Network Team - Acknowledged.
-
L-10 Low Asymmetric reviewPrice check Validation L O C A T I O N EthosReview.sol R E V I E W Main Review Acknowledged
Description
EthosReview.reviewAndVouchWithPermit()protects the user against unexpectedreviewPricechanges, by requiring thatpermit.value == actualTotal .However, there is no such check in
addReview()andaddReviewWithPermit(). If a user has given a large approval to the contract and their review creation transaction lands after an increase ofreviewCost, they will end up paying more funds than they initially expected.Recommendation
Consider adding such check in
addReview()andaddReviewWithPermit()as well.Resolution
Ethos Network Team - Acknowledged.
-
L-11 Low Compromised authors strand review management Unexpected Behavior L O C A T I O N src/legacy/EthosReview.sol#L691 R E V I E W Main Review Acknowledged
Description
The review management functions execute
_onlyReviewAuthor(), which validates themsg.sender's current profile against the reviewauthor's current profile, as they also have to be verified profiles. This allows the system to keep the intended model where review management authorization follows the current profile of the author. However, if the author address is later deleted from the profile and marked as compromised,_onlyReviewAuthor()will keep reverting. As a result, the rest of the addresses in the same profile will not be able toedit,archiveorrestorethe review anymore.Recommendation
Consider whether this is intentional behavior. A possible fix may be to add a reassignment function that can be invoked when the author of a review is compromised in order to attach the author to another participant of the same profile.
Resolution
Ethos Network Team - Acknowledged.
-
L-12 Low Cancelled slashes can shield subjects DoS L O C A T I O N src/legacy/EthosSlash.sol#L983-L995 R E V I E W Main Review Acknowledged
Description
The documentation already acknowledges that a junk slash can shield a subject by occupying the one-open-slash cap slot. This is an accepted tradeoff of the type-agnostic subject cap:
_assertNoOpenSlashes()rejects a new slash whenever the subject profile, address, or attestation key points to a slash that is stillisOpen(), regardless of whether the existing slash isSCORE,XP ,or FINANCIAL .The under-documented edge is cancellation. The documented deterrent assumes the shielding slash exposes its author to reputational or financial consequences. However, the shielding author can call
cancelSlash()duringcancelWindow. OncecancelledAtis set,isOpen()returns false, the subject cap slot is freed, and the cancelled slash avoids a terminal negative outcome. The author, or another cooperating author, can then request a new signed slash and occupy the subject slot again.This creates a cancel-and-replace shielding pattern. A signed low-impact
SCOREorXPslash can temporarily block a later legitimateFINANCIALslash without freezing the subject's VouchV2 stake. A signedFINANCIALshielding slash can also be cancelled intoCANCELLED, which has no burn, before being replaced. The pattern still requires Echo to sign each slash, and Echo can detect repeated cancellations or same-subject replacement loops, but the on-chain lifecycle lets cancellation avoid the downside that the documented shield-griefing discussion relies on.Recommendation
Consider adding a small fee for cancelling a slash so repeated cancel-and-replace shielding has an on-chain cost.
Resolution
Ethos Network Team - Acknowledged.
-
L-13 Low Adaptive LMSR Dust Profit Math L O C A T I O N src/AdaptiveLMSRPricing.sol R E V I E W Main Review Acknowledged
Description
When an
AdaptiveLMSRmarket has zero protocol fees, a user can extract a small amount ofWHUFby buying the minimum allowed position and then selling nearly the full position while leaving a tiny amount of position-token dust behind.The issue comes from precision loss in the fixed-point
ln()andexp()operations used by theAdaptiveLMSRcost function.EthosMarket.openPosition()enforcesMIN_BUY, but it does not prevent the user from later selling almost all minted position tokens. For the tested production-scale constants, buyingMIN_BUYand selling all but256wei of position tokens returnsMIN_BUY + 1,765wei, leaving the attacker with both a tiny residual position and a smallWHUFprofit.The profit is small per execution and is state-dependent. The first sell-down-to-dust moves the market away from its initial supply state, so repeating the exact same sequence against the same market may produce a different residual.
In production this should be offset by the entrance and exit fees.
Recommendation
Make sure to enable fees for the markets and be aware of this issue.
Resolution
Ethos Network Team - Acknowledged.
-
L-14 Low Gas bombing via large metadata Gas Griefing Resolved
Description
editSlashhas no length cap for replacementcomment/metadata, and status helpers copy the wholeSlashstruct from storage to memory even when they only need scalar fields. An author can therefore store large edited strings and make subsequent status/cancel helper paths for that slash much more expensive.This will make more expensive not only view functions, but
cancelSlashas well because it copies the struct as well. The same issue exists inEthosReviewas well.Recommendation
Consider capping slash comment/metadata length on create and edit and doing that in
EthosReviewas well.Resolution
Ethos Network Team - Resolved.
-
L-15 Low Reverting valid buys close to the MAX_SAFE_SUPPLY_SUM DoS L O C A T I O N src/AdaptiveLMSRPricing.sol#L262-L275 R E V I E W Main Review Acknowledged
Description
AdaptiveLMSRPricing.getTokensForBudget()depends on_findUpperBoundForBudget()to find an upper token amount for its binary search. In the committed implementation,_findUpperBoundForBudget()starts probing atWADand then doubles the probe up toMAX_TOKENS_PER_TRADE, without bounding probes by remainingMAX_SAFE_SUPPLY_SUMheadroom. WhentrustSupply + distrustSupplyis close toMAX_SAFE_SUPPLY_SUM, a buy can still be valid throughgetCost(), butgetTokensForBudget()can revert before considering that amount because the first or a later doubling probe crosses the remaining supply headroom. This is not limited to sub-WADbuys. The general failure condition is that there exists a valid buy amountx<=headroom, but_findUpperBoundForBudget()probes an amounty > headroombefore finding a bracket. The sub-WADcase is only the smallest repro because the first hardcoded probe isWAD; larger headroom values can fail when a later doubling step skips past the remaining headroom. This affectsEthosMarket.quoteBuy()andEthosMarket.openPosition(), because both usegetTokensForBudget()to convert a WHUF budget into position tokens.Recommendation
A: preserve strict unaffordable upper bound This is the minimal semantic change. Keep the current postcondition that
_findUpperBoundForBudget()returns an upper bound whose cost is strictly greater thanbudget:1 _buyCostCeil(hi) > budgetTo do that safely, bound all probes by remaining supply headroom. If the maximum valid probe is still affordable, revert instead of returning
headroom, because the currentEthosMarketbuy path charges the full budget. Example patch shape:function _findUpperBoundForBudget( uint256 trustSupply, uint256 distrustSupply, bool isPositive, uint256 budget ) internal view returns (uint256) { if (distrustSupply > type(uint256).max - trustSupply) { revert SuppliesExceedArithmeticLimit(trustSupply, distrustSupply, MAX_SAFE_SUPPLY_SUM); } uint256 supplySum = trustSupply + distrustSupply; if (supplySum >= MAX_SAFE_SUPPLY_SUM) { revert SuppliesExceedArithmeticLimit(trustSupply, distrustSupply, MAX_SAFE_SUPPLY_SUM); } uint256 headroom = MAX_SAFE_SUPPLY_SUM - supplySum; uint256 maxProbe = Math.min(MAX_TOKENS_PER_TRADE, headroom); uint256 hi = Math.min(WAD, maxProbe); if (_buyCostCeil(trustSupply, distrustSupply, isPositive, hi) > budget) { return hi; } while (hi < maxProbe) { uint256 next = hi * 2; if (next > maxProbe) next = maxProbe; if (_buyCostCeil(trustSupply, distrustSupply, isPositive, next) > budget) { return next; } hi = next; } revert BudgetUpperBoundNotFound(budget); }Tradeoff: if
budget==cost(headroom), this conservative version still reverts because there is no strictly unaffordable upper bound inside the safe supply domain. This preserves the existing binary-search postcondition but prevents exact final-headroom buys through the budget inverse. B: allow exact final headroom Alternatively, allow_findUpperBoundForBudget()to returnmaxProbewhen it is exactly affordable:_buyCostCeil(hi) > budget OR hi == maxProbe && _buyCostCeil(hi) == budgetThis lets users buy exactly the final safe headroom when their budget exactly matches
cost(headroom), while still reverting whenbudget >cost(headroom)to avoid market overcharge. Example patch shape:function _findUpperBoundForBudget( uint256 trustSupply, uint256 distrustSupply, bool isPositive, uint256 budget ) internal view returns (uint256) { if (distrustSupply > type(uint256).max - trustSupply) { revert SuppliesExceedArithmeticLimit(trustSupply, distrustSupply, MAX_SAFE_SUPPLY_SUM); } uint256 supplySum = trustSupply + distrustSupply; if (supplySum >= MAX_SAFE_SUPPLY_SUM) { revert SuppliesExceedArithmeticLimit(trustSupply, distrustSupply, MAX_SAFE_SUPPLY_SUM); } uint256 headroom = MAX_SAFE_SUPPLY_SUM - supplySum; uint256 maxProbe = Math.min(MAX_TOKENS_PER_TRADE, headroom); uint256 hi = Math.min(WAD, maxProbe); uint256 hiCost = _buyCostCeil(trustSupply, distrustSupply, isPositive, hi); if (hiCost > budget) { return hi; } if (hi == maxProbe) { if (hiCost == budget) return hi; revert BudgetUpperBoundNotFound(budget); } while (hi < maxProbe) { uint256 next = hi * 2; if (next > maxProbe) next = maxProbe; uint256 nextCost = _buyCostCeil(trustSupply, distrustSupply, isPositive, next); if (nextCost > budget) { return next; } if (next == maxProbe) { if (nextCost == budget) return next; revert BudgetUpperBoundNotFound(budget); } hi = next; } revert BudgetUpperBoundNotFound(budget); }If this option is chosen, update the comment in
getTokensForBudget():SOLIDITY
1 /
/ _findUpperBoundForBudget returns either an unaffordable upper bound,2 // or the exact maximum safe-domain buy when its cost equals the budget.Resolution
Ethos Network Team - Acknowledged.
-
L-16 Low VouchMetadataUpdated not emitted Events L O C A T I O N src/EthosVouchV2.sol#L84 R E V I E W Main Review Resolved
Description
The behavior of the
VouchMetadataUpdated()event is documented as firedon a non-empty metadata at creation timeand on every successful setVouchMetadata call.However,
_vouchAs()sets the metadata when it creates the vouch without emitting the event.if (bytes(metadata).length > 0) { vouchMetadata[vouchId] = metadata; }Recommendation
if (bytes(metadata).length > 0) { vouchMetadata[vouchId] = metadata; + emit VouchMetadataUpdated(vouchId, metadata); }Resolution
Ethos Network Team - Resolved.
-
I-01 Informational Misleading _addReview Name Informational L O C A T I O N src/legacy/EthosReview.sol#L429-L442 R E V I E W Main Review Acknowledged
Description
EthosReview._addReviewis misleadingly named. The function does not add or persist a review, emitReviewCreated, or incrementreviewCount; it only resolves an existing subject profile/mock id or creates a mock profile id viaEthosProfile.incrementProfileCount .Recommendation
Consider to rename
_addReviewto a name that describes its actual behavior to follow best practices.Resolution
Ethos Network Team - Acknowledged.
-
I-02 Informational Delegated EOA signer needs ERC-1271 handling Informational Acknowledged
Description
The new audit FAQ documents that expectedSigner is currently a backend-controlled EOA, Base is the only deployment scope, and there is one authorized CAM-registered Slash contract. Under those assumptions, OpenZeppelin SignatureChecker verifies the signer through the EOA recovery path and raw signatureUsed[signature] tracking is accepted as a documented deployment assumption. This is therefore not an active vulnerability in the current model. It remains a future-model warning: if expectedSigner is later changed to an ERC-1271 contract, delegated EOA with code, multi-chain signer, or non-deterministic signing system, signature validation and replay protection must be revisited.
Recommendation
Keep this as an informational future-model note. If expectedSigner ever moves away from the current EOA-only model, require ERC-1271 behavior that rejects alternate encodings of the same authorization, bind signatures to the intended domain where appropriate, and track used message digests or structured nonces instead of raw signature bytes.
Resolution
Ethos Network Team - Acknowledged.
-
I-03 Informational Unused Code Superfluous Code L O C A T I O N GLOBAL R E V I E W Main Review Resolved
Description
Unused errors:
src/errors/WhuffieErrors.sol => ZeroAmount(address account); src/legacy/EthosVouch.sol => InvalidFeeMultiplier(uint256 newFee); src/legacy/EthosVouch.sol => InsufficientProtocolFeeBalance(); src/legacy/errors/ProfileErrors.sol => error ProfileNotMock(uint256 profileId); src/legacy/errors/ProfileErrors.sol => error ZeroAddress(); src/legacy/errors/ReviewErrors.sol => error MustCreateAttestationFirst();Unused State Variables:
src/legacy/EthosProfile.sol => mapping(uint256 => mapping(uint256 => uint256)) private acceptedIdIndexByProfileIdAndId;Unused Enums:
src/legacy/EthosReview.sol => enum ReviewsByUnused Functions:
src/legacy/EthosReview.sol => _reviewsInRange()Recommendation
Consider to remove unused code to follow best practices.
Resolution
Ethos Network Team - Resolved.
-
I-04 Informational Legacy increaseVouch Bypasses Pause Access Control L O C A T I O N src/legacy/EthosVouch.sol#L509-L541 R E V I E W Main Review Acknowledged
Description
The legacy
EthosVouch.increaseVouch()function does not include thewhenNotPausedmodifier, that is used all over the system.This weakens the pause mechanism used for emergency situations.
Recommendation
Consider to add the
whenNotPausedmodifier to follow best practices.Resolution
Ethos Network Team - Acknowledged.
-
I-05 Informational Market Skips Transfer Delta Checks Best Practices L O C A T I O N src/EthosMarket.sol#L620 R E V I E W Main Review Acknowledged
Description
EthosMarket records market creation backing and buy payments using nominal transfer amounts and does not perform the balance-delta receive check used by EthosVouchV2 and the legacy review fee pull path. The new audit docs explicitly narrow the Market threat model: EthosMarket.token is always the canonical EthosWhuffie proxy, and non-WHUF, fee-on-transfer, rebasing, or modified WHUF-like tokens are out of scope. Under that documented deployment invariant this is not an active accounting vulnerability; it remains a defensive-consistency / future-hardening note because the Market pull paths diverge from comparable receive-delta checks elsewhere in the codebase.
Recommendation
Consider to add the same balance-delta check pattern used by EthosVouchV2 and other contracts to stick to the own practices.
Resolution
Ethos Network Team - Acknowledged.
-
I-06 Informational Renounce can finalize unusable WHUF state Best Practices Acknowledged
Description
EthosWhuffieinherits OpenZeppelin ownership behavior, so the owner can callrenounceOwnership()at any time. If ownership is renounced whiletransfersUnlockedis still false, no account can later callunlockTransfers(), leaving ordinary WHUF transfers permanently locked. Similarly, if ownership is renounced while the token is paused, no account can callunpause(), and_update()remains blocked bywhenNotPaused. Because_authorizeUpgrade()is also protected byonlyOwner, renouncing in either state also removes the ability to upgrade the token implementation to recover.Recommendation
Override
renounceOwnership()inEthosWhuffieand allow it only oncetransfersUnlocked==trueandpaused()==false.Resolution
Ethos Network Team - Acknowledged.
-
I-07 Informational Zero-payout sells can quote as executable Informational L O C A T I O N src/EthosMarket.sol#L504 R E V I E W Main Review Acknowledged
Description
quoteSell()can return a positivecurveRevenuewhilenetCreditsOutis zero. This happens when_computeExitFee()rounds the fee up to the fullcurveRevenue, for example a 1 weicurveRevenuewith any nonzeroexitFeeBps. The executing path is protected becauseclosePosition()reverts withZeroPayout()whennetCreditsOut==0, but the quote path still returns the positivecurveRevenueandexitFeeinstead of zeroing the result like it already does for zero amounts or insufficient seller balance. This can mislead frontends, indexers, or off-chain routing code into treating a dust sell as quoted even though it is not executable.Recommendation
Update
quoteSell()to mirror the executable sell conditions. After_computePayoutBreakdown()returns, ifnetCreditsOut==0, return(0, 0, 0, 0).Resolution
Ethos Network Team - Acknowledged.
-
I-08 Informational Inaccurate _requireActiveMarket() NatSpec Documentation L O C A T I O N src/EthosMarket.sol#L719 R E V I E W Main Review Acknowledged
Description
The NatSpec of
EthosMarket._requireActiveMarket()says it's called byopenPosition(), but the actual function that calls it is_openPosition()(with underscore), whileopenPosition()is a wrapper around it.Recommendation
- /// @dev Reverts if the market is paused or sell-only. Used by openPosition. + /// @dev Reverts if the market is paused or sell-only. Used by _openPosition.Resolution
Ethos Network Team - Acknowledged.
-
I-09 Informational MIN_BUY purpose is misstated Documentation L O C A T I O N src/EthosMarket.sol#L68 R E V I E W Main Review Acknowledged
Description
The comment above the
MIN_BUYconstant inEthosMarketsays it's purpose is to prevent attacks where fees round to 0./// @notice Minimum buy amount to prevent dust attacks where fees round to zero. uint256 public constant MIN_BUY = 1e15; // 0.001 WHUFEven without
MIN_BUYthe fees won't round to 0 because both functions useCeilwhen calculating them.function _computeEntryFee(uint256 paymentAmount) internal view returns (uint256) { return Math.mulDiv(paymentAmount, entryFeeBps, BPS_DENOMINATOR, Math.Rounding.Ceil); } /// @dev Computes the exit protocol fee from curve revenue. function _computeExitFee(uint256 curveRevenue) internal view returns (uint256) { return Math.mulDiv(curveRevenue, exitFeeBps, BPS_DENOMINATOR, Math.Rounding.Ceil); }This means the comment is not correct about the goal of the constant.
Recommendation
Consider changing the comment.
Resolution
Ethos Network Team - Acknowledged.
-
I-10 Informational Buy trades overchage Informational L O C A T I O N src/EthosMarket.sol#L519-L526 R E V I E W Main Review Acknowledged
Description
_openPosition()computestokensMintedfrom the full post-feecurveAmountbudget and transfers the entirecurveAmounteven when the exact cost oftokensMintedis lower than the budget. This means unused buy budget is intentionally retained as market backing instead of refunded. The behavior is conservative for solvency and can provide a buffer against rounding and fixed-point approximation effects in the pricing contract, but it is not clearly documented for users or integrators. As a result, callers may incorrectly assumequoteBuy()oropenPosition()charge only the exact bonding-curve cost of the minted tokens.Recommendation
Consider documenting this overcharging behavior.
Resolution
Ethos Network Team - Acknowledged.
-
I-11 Informational Frozen event is emitted without state change Informational L O C A T I O N src/utils/SlashFreezable.sol#L59-L67 R E V I E W Main Review Acknowledged
Description
IFreezable.Frozenis documented as emitted when an account's frozen state changes, butSlashFreezable.freeze()andSlashFreezable.unfreeze()emit the event every time the status is set. Repeated calls such asfreeze(account)when the account is already frozen, orunfreeze(account)when the account is already unfrozen, emitFrozenwithout an actual state transition.Recommendation
Either update the NatSpec to describe
Frozenas a status-set event rather than a state-change event, or makefreeze()andunfreeze()emit only when_frozenAccounts[account]actually changes.Resolution
Ethos Network Team - Acknowledged.
-
I-12 Informational Missing gap in signature control Upgradeability L O C A T I O N src/utils/SignatureControl.sol#L13-L17 R E V I E W Main Review Acknowledged
Description
SignatureControlis an upgradeable storage-bearing base contract, but it does not reserve a storage gap after its state variables. It currently storesexpectedSigner,signatureVerifier, andsignatureUsed, and child contracts such asAccessControlV2begin their own storage immediately after those slots. As a result, adding new storage variables toSignatureControlin a future upgrade would shift child storage and collide with existing variables such ascontractAddressManagerandpendingOwner. This limits safe upgradeability of contracts inheritingSignatureControl. However, theSignatureControlis currently in use by the deployed legacy contracts, so adding a gap to it would shift the current storage layout.Recommendation
Acknowledge the finding and be aware of it in case you need to add a new variable to
SignatureControlin a future upgrade.Resolution
Ethos Network Team - Acknowledged.
-
I-13 Informational Archived Targets Stay Allowed Unexpected Behavior L O C A T I O N src/legacy/EthosReview.sol#L535-L540 R E V I E W Main Review Acknowledged
Description
ITargetStatus.targetExistsAndAllowedForId()is used by many contracts to implement a function that checks if a given object exists and it is allowed to be used.EthosSlashuses both parts of the status: an existing slash is onlyallowedwhile it is still open (allowed = exists&&isOpen(_targetId)).By contrast,
EthosReviewreturnsallowed = existsand does not account forreview.archived. The vouch implementations follow the same existence-implies-allowed pattern for archived vouches, butEthosVouchV2documents that archived vouches remain valid discussion targets for continuity.EthosReviewhas no equivalent documentation.Recommendation
Clarify the intended lifecycle semantics for archived reviews. If archived reviews should remain valid discussion targets, document that, otherwise update the
targetExistsAndAllowedForIdflow accordingly.Resolution
Ethos Network Team - Acknowledged.
-
I-14 Informational Inconsistent review existence checks Error L O C A T I O N src/legacy/EthosReview.sol#L459-L463 R E V I E W Main Review Acknowledged
Description
EthosReview.editReview()does not perform an existence check before calling_onlyReviewAuthor(). For a nonexistent review,reviews[reviewId].authorisaddress(0), so_onlyReviewAuthor()callsverifiedProfileIdForAddress(address(0)) and revertsfrom EthosProfile w ithProfileNotFoundForAddress(address(0))instead of the expectedReviewNotFound()error used by the other review-management functions.This does not allow nonexistent reviews to be edited, but it creates inconsistent error semantics across related review-management functions and makes failed calls harder to diagnose.
Recommendation
Add an explicit existence check to
editReview()before_onlyReviewAuthor(), using the sameReviewNotFound()error as the other review-management functions.Resolution
Ethos Network Team - Acknowledged.
-
I-15 Informational Comment misstates permit allowance Documentation L O C A T I O N src/legacy/EthosReview.sol#L360-L363 R E V I E W Main Review Resolved
Description
The comment before the
EthosVouchV2approval inEthosReview.reviewAndVouchWithPermit()states that thepermitvalue lock guarantees no residual allowance. This is misleading because the consumedpermitapplies to the user-to-EthosReviewallowance, while the laterforceApprove()creates a separateEthosReview-to-EthosVouchV2allowance. The absence of residualEthosVouchV2allowance in the current implementation comes from approving exactlyvouchGross,EthosVouchV2._checkTransferIn()pulling exactly that amount, and the explicitforceApprove(..., 0)cleanup, not from the user’spermitvalue lock.Recommendation
Update the comment to describe the actual allowance relationship. For example: “Approve
EthosVouchV2for exactlyvouchGross, then callvouchFor(). The currentEthosVouchV2._checkTransferIn()pulls the full approved amount, and the allowance is reset to zero defensively in case a futureEthosVouchV2implementation pulls less than approved.”Resolution
Ethos Network Team - Resolved.
-
I-16 Informational Inconsistent delta check Validation L O C A T I O N src/legacy/EthosReview.sol#L350 R E V I E W Main Review Acknowledged
Description
When a review is created,
_pullAndBurnReviewFee()reverts if the used token has fee on transfers enabled by comparing the transferred delta against the expected amount.However,
reviewAndVouchWithPermit()pulls the user funds without calling_pullAndBurnReviewFee(), effectively skipping the delta check.Recommendation
Consider applying the same delta check in
reviewAndVouchWithPermit()Resolution
Ethos Network Team - Acknowledged.
-
I-17 Informational Missing authorProfileId in Review struct Documentation L O C A T I O N src/legacy/EthosReview.sol#L98 R E V I E W Main Review Resolved
Description
The NatSpec of the
Reviewstruct inEthosReview.soldocuments anauthorProfileIdfield which is not present in the struct.Recommendation
Remove the field from the NatSpec.
Resolution
Ethos Network Team - Resolved.
-
I-18 Informational vouchIdsByAuthorIndex has ambiguous name Best Practices L O C A T I O N src/EthosVouchV2.sol#L245 R E V I E W Main Review Acknowledged
Description
The
vouchIdsByAuthorIndexvariable holds the index of eachvouchIdin thevouchIdsByAuthor[address]array, but its name suggests that it is holding vouch IDs.Recommendation
Consider renaming the variable. For example,
indexesByUserVouchIds.Resolution
Ethos Network Team - Acknowledged.
-
I-19 Informational Zero increments can suppress rewards Rounding L O C A T I O N src/EthosRewards.sol#L316-L324 R E V I E W Main Review Acknowledged
Description
When
totalCommittedis very large relative to the remaining reward pool,rewardPerToken()can compute anincrementof0after integer division. TheupdateReward()modifier still updateslastUpdateTimewhennewRpt==oldRpt, so each zero-increment checkpoint permanently discards the elapsed time instead of allowing it to accumulate into a later nonzero increment.A user with an existing vouch can trigger these checkpoints through tiny nonzero
increaseVouch()calls, which callcredit()onEthosRewards. Under sufficiently hightotalCommitted / availableratios, repeated small checkpoints can suppress rewards for all users. The PoC testtest_audit_dailyTinyCreditsCanSuppressYearlyRewardsWhenCommittedDwarfsPool() show sdaily1-wei credit() callsfor one year leaving
rewardPerTokenStored,totalAccruedNotPaid, andearned()at0, while the same state with a sparse one-year checkpoint accrues approximately the expected annual rewards.The required ratio is extreme. At the documented
1980bps emission rate, daily checkpoints requiretotalCommitted /availableabove roughly5.42e14. This makes the issue highly dependent on the production WHUF supply, reward pool size, and expected committed balance.Recommendation
A possible mitigation is to avoid advancing
lastUpdateTimeonly whentotalCommitted > 0,dt > 0,emissionRateBps >0,available > 0, andincrement==0.This mitigation has a tradeoff: the next nonzero checkpoint may allocate the carried interval across the then-current committed set, so users who joined during the carried interval can receive a small portion of delayed rewards. The carried interval is bounded by the time needed for
incrementto become nonzero.Either acknowledge the current behavior or implement the suggested fix, depending on which behavior is acceptable.
Resolution
Ethos Network Team - Acknowledged.
-
I-20 Informational Slash accepts partial attestations Validation L O C A T I O N src/legacy/EthosSlash.sol#L874-L879 R E V I E W Main Review Acknowledged
Description
EthosSlash._validateSubjectOrAttestationSet()treats an attestation as present when eitherattestationDetails.accountorattestationDetails.serviceis non-empty. This is more permissive thanEthosReview._validateReviewDetails(), which requires both fields when the subject address is unset. The malformed slash is later rejected indirectly becauseEthosAttestation.getServiceAndAccountHash()reverts when either field is empty, so a slash is not recorded with a partial attestation. However, the slash contract's own validation accepts an invalid attestation shape and relies on a downstream external contract for the final rejection, producing inconsistent validation behavior and revert sources across review and slash flows.Recommendation
Validate attestation details directly in
EthosSlash._validateSubjectOrAttestationSet()using the same rule asEthosReview :when subject is address(0) ,requirebothattestationDetails.account andattestationDetails.serviceto be non-empty; whensubjectis set, require both fields to be empty. This keeps malformed slash inputs rejected at the slash validation layer withInvalidSlashDetails.Resolution
Ethos Network Team - Acknowledged.
-
I-21 Informational VouchRequiresPositiveReview semantics Validation L O C A T I O N src/legacy/EthosReview.sol#L325 R E V I E W Main Review Acknowledged
Description
The
VouchRequiresPositiveReviewis thrown when areviewAndVouchWithPermit()call tries to create a non-positive review while vouching. However, sinceThe composite intentionally does not enforcevouchUserkey↔(subject, attestationDetails)`, the review target may be different than the vouch recipient.The check successfully guards against non-positive reviews, even though these reviews may not have been given to the same vouch target.
Recommendation
Consider whether this positive check is needed.
Resolution
Ethos Network Team - Acknowledged.
-
I-22 Informational Lifecycle events omit attestation target Events L O C A T I O N src/legacy/EthosReview.sol#L454-L508 R E V I E W Main Review Acknowledged
Description
ReviewCreatedincludes both target forms by emittingattestationHashandsubject, butReviewEdited,ReviewArchived, andReviewRestoredonly emitsubject. When a review targets an attestation,review.subjectisaddress(0), so lifecycle events emitted byeditReview(),archiveReview(), andrestoreReview()do not identify the reviewed attestation. Event-only consumers must reconstruct the target from priorReviewCreatedlogs or callreviews()and recompute the hash fromattestationDetails, which can make attestation review updates unattributable if lifecycle events are processed independently.Recommendation
Consider including the attestation target in review lifecycle logs
Resolution
Ethos Network Team - Acknowledged.
-
I-23 Informational Edit slash uses generic expiry error Error L O C A T I O N src/legacy/EthosSlash.sol#L503-L508 R E V I E W Main Review Acknowledged
Description
editSlash()reverts withEditWindowExpiredwheneverisEditable()returns false. However,isEditable()returns false for multiple distinct states, including cancelled slashes, slashes closed by duration, and slashes whose edit window has elapsed.This differs from
cancelSlash(), which disambiguates the same categories and can returnSlashIsCancelled,SlashIsClosed, orCancelWindowExpired. As a result, callers and integrators receive a generic edit-window error even when the slash is no longer editable because it was cancelled or closed, not because the edit window alone expired.Recommendation
Mirror the explicit branching used by
cancelSlash()soeditSlash()returns state-specific errors such asSlashIsCancelled , SlashIsClosed,an d EditWindowExpired .Resolution
Ethos Network Team - Acknowledged.
-
I-24 Informational Author stake can back multiple slashes Trust Assumptions L O C A T I O N src/legacy/EthosSlash.sol#L983-L985 R E V I E W Main Review Acknowledged
Description
EthosSlashenforces the author-side concurrent slash cap by the caller's currentauthorProfileId._assertNoOpenSlashes() checks lastSlashIdByAuthorProfile[authorProfileId] ,and createSlash() latersnapshotsthe current addresses of that profile for
FINANCIALauthor-side freezing.This profile-level cap does not explicitly account for address-level stake already encumbered by another financial slash. If an address that was included in one financial slash's author snapshot is later moved to another profile, that address can be included in another profile's author snapshot for a different slash.
_freezeRefCountkeeps freeze/unfreeze accounting correct, but the same address-level VouchV2 stake can still serve as author-side economic backing for multiple concurrent slashes.For example, profile
P1contains addressesAandB, andAcreates a financial slash. The author snapshot includes bothAandB, so both addresses are frozen as author-side backing. IfBis later removed fromP1and added to profileP2, a new slash created fromP2can snapshotBagain as author-side backing for a different subject, even thoughBis already frozen by the first slash.Recommendation
Consider this case and add the necessary guards in the offchain signing flow if not already present.
Resolution
Ethos Network Team - Acknowledged.
-
I-25 Informational Global slash windows affect open slashes Configuration L O C A T I O N src/legacy/EthosSlash.sol#L323-L346 R E V I E W Main Review Resolved
Description
EthosSlashsnapshotsdurationin eachSlash, butisEditable()andisCancellable()use the current globaleditWindowandcancelWindowinstead of per-slash values. As a result, admin calls tosetEditWindow()orsetCancelWindow()retroactively affect every still-open slash: increasing a window can make an older slash editable or cancellable again after its previous window elapsed, while decreasing a window can remove author rights earlier than expected.The setters only validate the new windows against the current
defaultDuration, not against already-created slashes' storedduration.isClosed()still bounds both operations by each slash's storedduration, so this cannot extend editing or author cancellation beyond slash expiry. The issue is that slash window semantics are live global policy rather than per-slash snapshots, which may surprise integrators and users expecting timing terms to be fixed when the slash is created.Recommendation
If slash timing terms are expected to be immutable after creation, snapshot
editWindowandcancelWindowinto eachSlashduringcreateSlash()and haveisEditable()andisCancellable()read the per-slash values. If the live-global behavior is intentional, document thatsetEditWindow()andsetCancelWindow()retroactively affect all still-open slashes.Resolution
Ethos Network Team - Resolved.
-
I-26 Informational Slash cap can be violated during migration Upgradeability L O C A T I O N EthosSlash.sol R E V I E W Main Review Acknowledged
Description
The newly added slash cap may be temporarily violated during the upgrade of the previous
EthosSlashto the new one. This can happen becauseXP/SCOREslashes created before the upgrade won't be populated in the slash cap mappings and_assertNoOpenSlashes()will allow opening a new slash even if a previousXP/SCOREone already exists. The issue exists forFINANCIALtype as well, but these were never supported before the upgrade.This shouldn't be big of a problem, given that the codebase was working without caps until now. After the old slashes resolve or are cancelled, the system caps should continue working as intended.
Recommendation
Keep this behavior in mind while upgrading the system.
Resolution
Ethos Network Team - Acknowledged.
-
I-27 Informational Misleading slash signature comment Documentation Resolved
Description
The comment before
validateAndSaveSignature()says that signature verification is performed before the slash cap check so unauthenticated callers cannot probe open-slash state by observingSubjectHasOpenSlashversusInvalidSignature. This privacy rationale is misleading because the cap state is already publicly observable through the publiclastSlashIdByAuthorProfile , lastSlashIdBySubjectProfile, lastSlashIdBySubjectAddress ,and lastSlashIdBySubjectAttestationHash m appingscom binedw ithisOpen() .The comment also says that a cap-conflicted submission burns the signature. However, if
_assertNoOpenSlashes()reverts aftervalidateAndSaveSignature()markssignatureUsed[signature], the entire transaction reverts, including the signature usage write. Therefore the signature is not consumed by a cap-conflict revert and remains reusable until it expires or is successfully consumed.Recommendation
Update or remove the comment so it accurately reflects the behavior of
createSlash().Resolution
Ethos Network Team - Resolved.
-
I-28 Informational Uninitialized proxies can be hijacked Upgradeability L O C A T I O N Global R E V I E W Main Review Acknowledged
Description
If the deployment of the V2 contracts and the new versions of the legacy contracts is not atomic, a malicious actor can call their initialize() functions and initialize them with values controlled by them. If unnoticed, this can lead to stolen funds, especially if the malicious user has ownership rights.
Recommendation
Execute the deployment + initialization in one tx.
Resolution
Ethos Network Team - Acknowledged.
-
I-29 Informational Address deletion can shed review history from live profile membership Unexpected Behavior L O C A T I O N EthosReview.sol R E V I E W Main Review Acknowledged
Description
EthosReview resolves or creates a subject profile/mock id when writing a review, but the Review row and subject lookup are stored only by the mutable subject address. EthosProfile can later remove that address from the profile's live address set by clearing profileIdByAddress, without updating any review index or retaining a profile-level review binding.
A profile can delete a negatively reviewed secondary address and continue with its remaining addresses, while consumers using the protocol's live addressesForProfile plus reviewIdsBySubjectAddress surfaces no longer see that review as part of the profile
Recommendation
**Keep than in mind when handling reviews offchain.
Resolution
Ethos Network Team - Acknowledged.
-
I-30 Informational One-way WHUF transfer unlock emits no event Events L O C A T I O N EthosWhuffie.sol R E V I E W Main Review Resolved
Description
EthosWhuffie.unlockTransfers() permanently flips transfersUnlocked from false to true but emits no event for that irreversible configuration change. The only on-chain signal is the storage getter/state diff rather than a standard log subscription.
Impact
Event-driven indexers, monitoring, frontends, and other off-chain integrations can miss or lag the WHUF transferability transition unless they poll storage or inspect every transaction trace.
Recommendation
Emit a dedicated event such as TransfersUnlocked(address indexed owner) immediately after setting transfersUnlocked = true.
Resolution
Ethos Network Team - Resolved.
-
I-31 Informational Market buys lack an explicit trade deadline Validation L O C A T I O N EthosMarket.sol R E V I E W Main Review Acknowledged
Description
EthosMarket buy paths only bound execution with paymentAmount and minTokensOut. They do not accept a separate orderDeadline, and the deadline in openPositionWithPermit is only the ERC-20 permit expiry. A still-valid user transaction can therefore execute later than the user's intended trading window if the output amount remains above minTokensOut.
Impact
This is a Low-severity freshness/UX risk: it does not let third parties spend a victim's permit or bypass msg.sender ownership, but a delayed user transaction can buy at a materially different market state than the wallet quote or off-chain intent assumed.
Recommendation
Add an explicit orderDeadline parameter to buy paths and revert when block.timestamp exceeds it, or document clearly that minTokensOut is only an amount-slippage bound and does not expire market orders.
Resolution
Ethos Network Team - Acknowledged.
-
L-01 Low Reward Rate Rounding Mismatch Rounding L O C A T I O N src/EthosRewards.sol#L383 R E V I E W Remediation Review Resolved
Description
EthosRewards.updateReward()incrementstotalAccruedNotPaidusingMath.Rounding.Ceilnow. However,currentRewardRateBps()still simulates pendingaccruedNotPaidwith the default floor rounding.Recommendation
Use the same
Math.Rounding.Ceilmode incurrentRewardRateBps()when simulating pendingaccruedNotPaid, so the view function matches the actual accounting path.Resolution
Ethos Network Team - Resolved.
-
I-01 Informational L-14 Not Fully Fixed Validation L O C A T I O N src/legacy/EthosSlash.sol#L520-L521 R E V I E W Remediation Review Acknowledged
Description
The status/cancel helper gas issue appears mitigated because slash helpers now avoid copying the full Slash struct, but the contracts still do not cap comment / metadata sizes.
Recommendation
Consider to add explicit maximum length checks for slash/review comment and metadata on both create and edit paths.
Resolution
Ethos Network Team - Acknowledged.
-
I-02 Informational Vouch ID gap due to initial offset Error L O C A T I O N EthosVouchV2.sol R E V I E W Remediation Review Resolved
Description
EthosVouchV2.initialize()allowsvouchCountto start frominitialVouchCount_, which is used as an ID offset. When this value is nonzero, IDs below or equal to the initial offset are holes: they satisfyvouchId<=vouchCount, but noVouchwas ever written for them.Several mutating paths use only
vouchId==0||vouchId > vouchCountas the existence check before readingvouches[vouchId]. For hole IDs, the defaultVouchhasauthor==address(0), so calls such asdecreaseVouch(),setVouchMetadata() , _unvouch(),an d _increaseVouch() pass the VouchNotFound check and thenrevertwithUnauthorizedVouchAccessinstead. This weakens error semantics and can confuse callers/indexers, but no direct state corruption or authorization bypass is apparent becausemsg.sendercannot equaladdress(0).Recommendation
Either acknowledge or add an existence helper that also checks
vouches[vouchId].author!= address(0)and use it in the affected mutating paths.Resolution
Ethos Network Team - Resolved.
-
I-03 Informational Locked whuffie can be used to pay review fees Best Practices L O C A T I O N EthosWhuffie.sol R E V I E W Remediation Review Resolved
Description
EthosWhuffieaims to whitelist transfers for the composite review-vouch composite flow, so its_update()function was updated as it follows:bool isReviewCompositePull = to == review && _msgSender() == review; if (!isVouchTransfer && !isReviewCompositePull) revert TransfersLocked(from, to);The flag is named
isReviewCompositePulland the contract NatSpec saysEthosReview.reviewAndVouchWithPermit arepermitted through ContractAddressManager-resolved exemptions. However, the check cannot differentiate between the composite flow and any other transfer to theReviewcontract where it's themsg.senderas well. As a result,WHUFFIEcan be used to pay foraddReviewandaddReviewWithPermitfees whentransfersUnlockedis stillfalse. Previously, these calls would have reverted, which required 0 fees in order to create reviews in this lock period.Recommendation
If the current code behavior is correct, clarify it in the comments and consider renaming the
boolflag.Resolution
Ethos Network Team - Resolved.
No findings match.
More from Ethos Network
Put your code through the same review.
This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.
