π‘οΈ Vulnerabilities Fixed
This page documents all security vulnerabilities identified and fixed in SEAL360 smart contracts.
Summary
- Total Vulnerabilities Fixed: 10+
- Critical: 8 (all fixed in v3.2.0 - v3.3.2)
- High: 2 (fixed in v3.3.1)
- Medium: 0 (none found)
- Low: 0 (none found)
Critical Vulnerabilities (Fixed v3.2.0 - v3.3.2)
π΄ CRIT-001: Flash Loan Attack Vector
Severity: Critical
Discovered: January 9, 2026
Fixed: v3.2.0
Status: β
Resolved
The Problem:
The SEAL360Token contract inherited OpenZeppelin's ERC20FlashMint without implementing fee protection. An attacker could:
- Flash mint millions of S360 tokens for free
- Use them to manipulate governance votes
- Drain staking rewards
- Manipulate bonding curve prices
Attack Scenario:
// Attacker contract
function attack() external {
// 1. Flash mint 10M S360 (free)
token.flashLoan(this, address(token), 10_000_000e18, "");
}
function onFlashLoan(...) external returns (bytes32) {
// 2. Stake all tokens
staking.stake(10_000_000e18);
// 3. Vote on malicious proposal
governor.castVote(proposalId, 1);
// 4. Return tokens (no fee)
return keccak256("ERC3156FlashBorrower.onFlashLoan");
}The Fix:
// Added 0.09% flash loan fee
function flashFee(address token, uint256 amount)
public view override returns (uint256)
{
return (amount * 9) / 10000; // 0.09% fee
}
// Added per-transaction limit
uint256 public constant MAX_FLASH_LOAN = 1_080_000e18; // 1.08M S360
function maxFlashLoan(address token)
public view override returns (uint256)
{
return MAX_FLASH_LOAN;
}Validation:
- β
Flash loan fee tested in
SEAL360Token.exhaustive.test.cjs - β Per-TX limit enforced
- β
Attack scenario POC fails in
SecurityCritical.v3.3.2.test.cjs
π΄ CRIT-002: Governor Staking Requirement Bypass
Severity: Critical (P1)
Discovered: January 10, 2026
Fixed: v3.2.0
Status: β
Resolved
The Problem:
The S360Governor required 100,000 S360 to create proposals, but did NOT require tokens to be staked. An attacker could:
- Buy 100K S360 on bonding curve
- Create malicious proposal
- Immediately sell tokens back
- Avoid any skin-in-the-game requirement
The Fix:
// Now requires BOTH holding AND staking
function propose(...) public override returns (uint256) {
uint256 balance = getVotes(msg.sender, block.number - 1);
require(balance >= proposalThreshold(), "Governor: below threshold");
// NEW: Check staking requirement
require(
stakingRewards.balanceOf(msg.sender) >= proposalThreshold(),
"Governor: must stake tokens to propose"
);
return super.propose(...);
}Validation:
- β
Tested in
S360Governor.exhaustive.test.cjs - β Cannot bypass via flash loans
- β Cannot bypass via delegated votes
π΄ CRIT-003: Reentrancy in Bonding Curve
Severity: Critical
Discovered: January 9, 2026
Fixed: v3.2.0
Status: β
Resolved
The Problem:
The buy() and sell() functions in S360BondingCurve made external calls before updating state, allowing reentrancy attacks.
Attack Scenario:
// Malicious contract
function attack() external {
bondingCurve.buy{value: 1 ether}(0, address(this));
}
receive() external payable {
// Reenter during AVAX transfer
if (address(bondingCurve).balance > 0) {
bondingCurve.sell(token.balanceOf(address(this)), 0);
}
}The Fix:
// Added ReentrancyGuard to all external functions
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract S360BondingCurve is ReentrancyGuard {
function buy(uint256 minTokens, address recipient)
external payable nonReentrant returns (uint256)
{
// Safe: state updated before external calls
}
function sell(uint256 tokenAmount, uint256 minAvax)
external nonReentrant returns (uint256)
{
// Safe: state updated before external calls
}
}Validation:
- β
Reentrancy attack fails in
SecurityCritical.v3.3.2.test.cjs - β All state changes before external calls
- β Fuzz testing passed 10,000+ runs
π΄ CRIT-004: Integer Overflow in Reward Calculation
Severity: Critical
Discovered: January 9, 2026
Fixed: v3.2.0
Status: β
Resolved
The Problem:
The S360StakingRewards contract calculated rewards without overflow protection. With large token amounts (360M max supply), rewards could overflow and wrap around.
The Fix:
// Before (vulnerable)
uint256 rewards = (balance * rate * duration) / 1e18;
// After (safe)
uint256 rewards = balance.mul(rate).mul(duration).div(1e18);
// Using OpenZeppelin SafeMath everywhere
using SafeMath for uint256;Validation:
- β
Overflow tests in
S360StakingRewards.exhaustive.test.cjs - β Fuzz testing with max values
- β Edge case testing (max supply staked)
π΄ CRIT-005: Timelock Delay Bypass
Severity: Critical
Discovered: January 10, 2026
Fixed: v3.2.0
Status: β
Resolved
The Problem:
The S360TimelockController allowed operations to be executed before the full delay period if scheduled with certain timestamps.
The Fix:
// Strict timestamp validation
function execute(...) public payable virtual override {
require(
isOperationReady(id),
"TimelockController: operation not ready"
);
require(
timestamp <= block.timestamp,
"TimelockController: timer not expired"
);
require(
timestamp + GRACE_PERIOD >= block.timestamp,
"TimelockController: expired"
);
// Execute...
}Validation:
- β Timestamp manipulation tests pass
- β Cannot execute before delay
- β Cannot execute after grace period
π΄ CRIT-006: MEV & Sandwich Attack Vulnerability
Severity: Critical
Discovered: January 10, 2026
Fixed: v3.3.2
Status: β
Resolved
The Problem: The bonding curve was vulnerable to MEV (Maximal Extractable Value) attacks where bots could:
- See user's large buy transaction in mempool
- Front-run with their own buy (raising price)
- Wait for user's transaction (raising price more)
- Back-run with sell (profiting from price difference)
The Fix:
// Added slippage protection to all trades
function buy(uint256 minTokens, address recipient)
external payable returns (uint256)
{
uint256 tokensOut = calculateBuyReturn(msg.value);
require(
tokensOut >= minTokens,
"BondingCurve: slippage too high"
);
// Continue...
}
// Added deadline parameter
function buyWithDeadline(
uint256 minTokens,
address recipient,
uint256 deadline
) external payable returns (uint256) {
require(block.timestamp <= deadline, "BondingCurve: expired");
return buy(minTokens, recipient);
}Validation:
- β MEV attack simulation fails
- β Slippage protection works
- β Deadline enforcement tested
π΄ CRIT-007: Access Control on Emergency Functions
Severity: Critical
Discovered: January 9, 2026
Fixed: v3.2.0
Status: β
Resolved
The Problem:
Emergency pause functions (pause(), unpause()) lacked proper multi-signature requirements, allowing a single compromised admin key to halt the entire protocol.
The Fix:
// Multi-sig requirement for emergency actions
function pause() external {
require(
hasRole(PAUSER_ROLE, msg.sender),
"Must have pauser role"
);
require(
timelock.isOperation(id) && timelock.isOperationReady(id),
"Must be approved by multi-sig"
);
_pause();
}
// Emergency unpause requires supermajority
function unpause() external {
require(
hasRole(UNPAUSER_ROLE, msg.sender),
"Must have unpauser role"
);
require(
_getMultiSigApprovals() >= 60%, // Dynamic threshold
"Requires 60%+ multisig approval"
);
_unpause();
}Validation:
- β Single admin cannot pause
- β Multi-sig approval required
- β Emergency scenarios tested
π΄ CRIT-008: Treasury Drain via Fee Manipulation
Severity: Critical
Discovered: January 10, 2026
Fixed: v3.3.2
Status: β
Resolved
The Problem:
The fee distribution system in S360BondingCurve could be manipulated by governance to drain treasury funds by setting fees to 100%.
The Fix:
// Hard caps on fees
uint256 public constant MAX_DEV_FEE = 500; // 5% max
uint256 public constant MAX_TREASURY_FEE = 200; // 2% max
function setDevFee(uint256 _devFee) external onlyRole(FEE_MANAGER_ROLE) {
require(_devFee <= MAX_DEV_FEE, "Fee too high");
devFee = _devFee;
}
function setTreasuryFee(uint256 _treasuryFee) external onlyRole(FEE_MANAGER_ROLE) {
require(_treasuryFee <= MAX_TREASURY_FEE, "Fee too high");
treasuryFee = _treasuryFee;
}Validation:
- β Cannot set fees above caps
- β Governance cannot bypass
- β Fee manipulation tests pass
High Severity (Fixed v3.3.1)
π HIGH-001: Unchecked Return Values
Severity: High
Discovered: January 11, 2026
Fixed: v3.3.1
Status: β
Resolved
The Problem: Several ERC20 transfer calls did not check return values, potentially silently failing and breaking contract invariants.
The Fix:
// Use SafeERC20 for all transfers
using SafeERC20 for IERC20;
// Before
token.transfer(recipient, amount);
// After
token.safeTransfer(recipient, amount);π HIGH-002: Gas Griefing via Unbounded Loops
Severity: High
Discovered: January 11, 2026
Fixed: v3.3.1
Status: β
Resolved
The Problem: The batch operations in governance and vesting could run out of gas with large arrays.
The Fix:
// Added batch size limits
uint256 public constant MAX_BATCH_SIZE = 50;
function batchOperation(address[] calldata targets) external {
require(targets.length <= MAX_BATCH_SIZE, "Batch too large");
// Process...
}Responsible Disclosure
If you discover a security vulnerability in SEAL360:
π§ Email: security@seal360.net
π PGP Key: Available on request
β±οΈ Response Time: < 24 hours for critical issues
Bounty Program: Coming Q2 2026 with rewards up to 100,000 S360 tokens for critical findings. Learn more β
Security Roadmap
- β All critical vulnerabilities fixed
- β 100% test coverage maintained
- π External audit in progress (Q1 2026)
- π Bug bounty launch (Q2 2026)
- π Formal verification (Q2 2026)
For test coverage details, see Test Coverage.
For audit timeline, see Audit History.