Author: Shivanageshwarrao
Date: November 8, 2025
This audit covers two Solidity contracts:
BriVault — an ERC4626-based tournament vault where participants deposit an ERC20 asset, pick a team/country, and winners share the vault after an event.
BriTechToken — an ERC20 token contract for the ecosystem.
This report represents the auditor’s best effort to identify vulnerabilities.
It does not guarantee the absence of undiscovered issues or future exploit safety.
No liability is assumed by the auditor or organization.
| Impact ↓ / Likelihood → | Low | Medium | High |
|---|---|---|---|
| High | Medium | High | Critical |
| Medium | Low | Medium | High |
| Low | Info | Low | Medium |
Static review of BriVault.sol and BriTechToken.sol
Solidity version: 0.8.24
Based on OpenZeppelin ERC4626 & ERC20 standards.
Manual static analysis
Slither / Mythril checks
ERC4626 compliance validation
Gas and DoS analysis
The contracts implement the intended functionality but suffer from critical vulnerabilities and centralization risks:
Owner can arbitrarily decide winners or mint tokens.
Missing reentrancy protection and unbounded loops create DoS and theft risk.
ERC4626 compliance issues could break integrations.
| Severity | Count |
|---|---|
| Critical | 3 |
| High | 5 |
| Medium | 3 |
| Low | 4 |
| Info | 6 |
_getWinnerShares) — DoS & gas exhaustionDescription: _getWinnerShares() iterates over the entire usersAddress dynamic array to compute totalWinnerShares. As the number of users grows, this function becomes arbitrarily expensive and may run out of gas. Because setWinner() calls _getWinnerShares(), the owner (or any privileged flow) may be unable to finalize the vault if gas exceeds block limits.
Impact: Denial-of-service: event finalization may fail; attacker can bloat usersAddress to prevent setWinner() from succeeding.
Proof of Code
Proof of Concept
Recommendation: Maintain per-country aggregated share counters during joinEvent() (increment and decrement on join/cancel) so setWinner() uses O(1) reads instead of iterating. Alternatively, perform aggregation off-chain and accept a merkle-proof-based claim system.
deposit, cancelParticipation, withdraw)Description: Several user-facing functions perform external ERC20 transfers after updating or reading state without reentrancy guards. Although Solidity 0.8.x provides safe arithmetic, token callbacks (malicious ERC777/ERC20 with hooks) or reentrant safeTransfer implementations could re-enter contract functions.
Impact: Funds could be stolen, users could withdraw multiple times, or state could be corrupted.
Proof of Code
Proof of Concept
Recommendation: Use ReentrancyGuard and mark external state-changing functions (deposit, cancelParticipation, withdraw) nonReentrant. Additionally, follow the Checks-Effects-Interactions pattern and minimize external calls.
BriTechToken::mint() allows unlimited owner-controlled minting (centralized inflation / rug)Description: mint() is callable by owner repeatedly and mints 10,000,000 tokens each call to the owner. There is no cap or one-time restriction.
Impact: Owner can arbitrarily inflate supply and drain value from token holders.
Proof of Code
Proof of Concept
Recommendation: Remove public mint or restrict it to a capped, controlled mechanism (e.g., ERC20Capped) or require timelock/multi-sig for mint operations.
withdraw() (totalWinnerShares == 0)Description: withdraw() calculates assetToWithdraw = shares * vaultAsset / totalWinnerShares without checking that totalWinnerShares > 0.
Impact: If totalWinnerShares == 0, this reverts (division by zero) or behaves unexpectedly, blocking withdrawals.
Proof of Code
Proof of Concept
Recommendation: Require totalWinnerShares > 0 before allowing withdrawal. Ensure totalWinnerShares is computed correctly and cannot be zero if winners exist.
setWinner) — centralization and rug riskDescription: setWinner() is onlyOwner and entirely on-chain; owner selects the winning country and finalizes distribution.
Impact: Owner can set a malicious winner, redirect funds, or collude with an address. Users have no verifiable path to trust the outcome.
Proof of Code
Proof of Concept
Recommendation: Integrate an oracle, multisig approval, or a delay and challenge period: e.g., a timelocked proposeWinner that can be challenged or requires multisig to finalize.
msg.sender)Description: deposit implements non-standard behavior: it uses two safeTransferFrom calls (fee and stake) and mints vault shares to msg.sender instead of receiver. It calls IERC20(asset()).safeTransferFrom(msg.sender, participationFeeAddress, fee) then again for stake. It also fails to call the ERC4626 _deposit() internal function.
Impact: Violates ERC4626 expectations — tooling and integrations may misbehave. Splitting transfers increases reentrancy exposure and reduces atomicity. Minting to msg.sender instead of receiver breaks transfer semantics.
Proof of Code
Proof of Concept
Recommendation: Align with ERC4626: collect the full assets transfer to the vault first, perform internal accounting, then transfer fee to the fee address from the vault balance (not via safeTransferFrom). Mint shares to receiver. Use Checks-Effects-Interactions.
Description: Constructor accepts _eventStartDate, _eventEndDate, _asset, and _participationFeeAddress without validation (except fee bsp bound). No guard for zero addresses or start < end.
Impact: Misconfiguration risks at deploy time (broken timeline, funds locked, or fee address set to zero).
Proof of Code
Proof of Concept
Recommendation: Add validation for zero addresses and event timeline as described.
joinEvent allows multiple joins and stale accounting (double-join, active users not cleaned)Description: Users can call joinEvent multiple times for different countryId values, overwriting userToCountry and pushing duplicates into usersAddress. cancelParticipation does not remove the user from usersAddress or decrement counters.
Impact: Inflation of participants array (DoS), incorrect winner share calculation, and unfair distribution.
Proof of Code
Proof of Concept
Recommendation: Prevent double-join by checking an isJoined mapping or requiring bytes(userToCountry[msg.sender]).length == 0.
deposit transfers split across two external calls — ordering & reentrancyProof of Code
Proof of Concept
hasWithdrawn tracking — double-withdraw riskProof of Code
Proof of Concept
cancelParticipation keeps users in usersAddress and does not adjust totalsProof of Code
Proof of Concept
_setWinner boolean uses leading underscore and breaks naming conventionRecommendation: Rename to winnerSet or isWinnerSet.
Recommendation: Index up to 3 fields on commonly queried events (deposited, joinedEvent, Withdraw). Add CancelParticipation and FeeTaken events.
getCountry uses non-robust validation (no explicit bounds check)Recommendation: Use require(countryId < teams.length, "Invalid index"); and check bytes(teams[countryId]).length > 0.
_deposit/_mint helpersRecommendation: Rework to delegate to OpenZeppelin ERC4626's _deposit and _withdraw helpers where appropriate.
Consider making participationFeeAddress public or add a getter so users can verify the fee receiver.
Consider making event timestamps immutable or exposed in events for off-chain verification.
Gas optimization: consider immutable for constructor arguments that never change.
Consider using ERC20Capped for BriTechToken or remove the mint function entirely.
Consider adopting a pausable or timelock for owner actions such as setWinner.
Replace magic numbers and hard-coded BASIS points logic with named constants and tests.
Immediate (blocker fixes)
Add ReentrancyGuard and mark deposit, withdraw, cancelParticipation nonReentrant.
Fix unlimited mint in BriTechToken immediately (remove public mint or add caps/timelock/multisig).
Prevent division by zero in withdraw().
High-priority logic fixes
Remove the unbounded loop by tracking per-country aggregated shares on joinEvent and cancelParticipation.
Make setWinner() less centralized (multisig or timelock + oracle).
Rework deposit() to follow ERC4626: pull funds once, then distribute fee from the vault balance and mint to receiver.
Medium-term improvements
Harden input validation in constructor and public setters.
Add events and index fields for observability and tooling.
Add active user tracking & safe removal on cancel.
Long-term architectural suggestions
Consider a claim-based model where winners claim their share via proofs to avoid on-chain aggregation.
Add unit tests and fuzz tests for edge cases: many users, tiny shares, and last-share rounding.
Consider a governance or multisig flow for critical owner operations.
Note: These are illustrative patches; integrate and test thoroughly before deployment.
Constructor checks
Deposit: single transfer, mint to receiver
Prevent double-join
BriTechToken: one-time mint or capped
End of report.
When no one bet on the winning team, making totalWinnerShares = 0, causing division by zero in withdraw and preventing any withdrawals.
CancelParticipation burns shares but leaves the address inside usersAddress and keeps userSharesToCountry populated.
The _getWinnerShares() function is intended to iterate through all users and sum their shares for the winning country, returning the total.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.