Scruffy Berry Ape
High
The fixed slot limit 256 will cause a DOS for high value vouchers as a Malicious Voucher can fill all slots with minimum value vouches.
The lack of value based slot allocation in EthosVouch.sol will cause a complete denial of service for high-value vouchers as an attacker can fill all available slots with minimum value vouches (0.0001 ETH).
In EthosVouch.sol there is a fixed maximum of 256 slots per profile with no consideration of vouch value: https://github.com/sherlock-audit/2024-11-ethos-network-ii/blob/main/ethos/packages/contracts/contracts/EthosVouch.sol?plain=1#L29
The current implementation allows a malicious voucher to block high value vouches by filling slots with minimum value vouches (0.0001 ETH), which directly contradicts the protocol's core philosophy that "Credibility is based on stake value, not popularity".
- Profile must exist in EthosProfile contract
- Malicious Voucher needs enough ETH to fill slots (at least 0.0256 ETH = 256 * 0.0001 ETH minimum) + Gas Fees
- Target profile must have available vouch slots (not already at 256)
- Contract must not be paused
None. attack can be executed independently
1- Malicious Voucher identifies valuable profile to target
2- Malicious Voucher calls vouchByProfileId()
repeatedly with minimum amount (0.0001 ETH) to fill slots
3- When high value voucher attempts to vouch, their transaction reverts due to MaximumVouchesExceeded
4- Malicious Voucher can then:
- Demand payment to release slots through
unvouch()
- Maintain control to collect pool fees
- Selectively allow vouches by controlling slot availability
An attacker can: 1- Fill all 256 slots with minimum vouches (total cost 0.0256 ETH + Gas fees) 2- Block legitimate high value vouches (eg 1 ETH) 3- Extract value through slot release extortion 4- Capture vouching pool fees 5- Control who can vouch for a profile
No response
An "Accept Vouch" mechanism where profile owners must accept vouches before they count against slot limits is optimal but requires major changes in the current implementation. Here's a simpler mitigation that focuses on preventing rapid consecutive vouches:
contract EthosVouch is AccessControl, UUPSUpgradeable, ITargetStatus, ReentrancyGuard {
// ... existing code ...
+ // Cooldown period between vouches for same profile
+ uint256 public constant VOUCH_COOLDOWN = 1 hours;
+
+ // Track last vouch timestamp per profile
+ mapping(uint256 => uint256) public lastVouchTimestamp;
+
+ // Track number of vouches in cooldown window
+ mapping(uint256 => uint256) public recentVouchCount;
+
+ // Maximum vouches allowed in cooldown window
+ uint256 public constant MAX_VOUCHES_IN_WINDOW = 5;
function vouchByProfileId(
uint256 subjectProfileId,
string calldata comment,
string calldata metadata
) public payable whenNotPaused nonReentrant {
+ // Check cooldown and rate limiting
+ _enforceVouchRateLimit(subjectProfileId);
// ... existing checks ...
// store vouch details
uint256 count = vouchCount;
vouchIdsByAuthor[authorProfileId].push(count);
vouchIdsByAuthorIndex[authorProfileId][count] = vouchIdsByAuthor[authorProfileId].length - 1;
vouchIdsForSubjectProfileId[subjectProfileId].push(count);
vouchIdsForSubjectProfileIdIndex[subjectProfileId][count] =
vouchIdsForSubjectProfileId[subjectProfileId].length -
1;
vouchIdByAuthorForSubjectProfileId[authorProfileId][subjectProfileId] = count;
vouches[count] = Vouch({
archived: false,
unhealthy: false,
authorProfileId: authorProfileId,
authorAddress: msg.sender,
vouchId: count,
balance: toDeposit,
subjectProfileId: subjectProfileId,
comment: comment,
metadata: metadata,
activityCheckpoints: ActivityCheckpoints({
vouchedAt: block.timestamp,
unvouchedAt: 0,
unhealthyAt: 0
})
});
emit Vouched(count, authorProfileId, subjectProfileId, msg.value);
vouchCount++;
+ // Update tracking
+ lastVouchTimestamp[subjectProfileId] = block.timestamp;
+ recentVouchCount[subjectProfileId]++;
}
+ function _enforceVouchRateLimit(uint256 profileId) internal {
+ // Reset counter if cooldown window has passed
+ if (block.timestamp > lastVouchTimestamp[profileId] + VOUCH_COOLDOWN) {
+ recentVouchCount[profileId] = 0;
+ }
+
+ // Check rate limits
+ require(
+ recentVouchCount[profileId] < MAX_VOUCHES_IN_WINDOW,
+ "Too many vouches in time window"
+ );
+
+ // For first vouch in new window
+ if (recentVouchCount[profileId] == 0) {
+ lastVouchTimestamp[profileId] = block.timestamp;
+ }
+ }
}