Security Model
Trust boundaries, threat model, and concrete security properties of every deployed contract.
Trust Boundary Overview
┌──────────────────────────────────────────────────┐
│ TRUSTED (Game server) │
│ │
│ • Game server decides match outcomes │
│ • Server calculates payout amounts │
│ • Server manages in-game inventory state │
│ • Server signs or submits on-chain transactions │
│ │
│ Players trust the game operator for GAMEPLAY │
│ correctness — the same trust model as any │
│ existing competitive online game. │
│ │
└───────────────────────┬──────────────────────────┘
│
economic settlement boundary
│
┌───────────────────────▼──────────────────────────┐
│ TRUSTLESS (On-chain) │
│ │
│ • Smart contracts hold all prize funds │
│ • Only verified winners can withdraw prizes │
│ • Game operator CANNOT redirect prize money │
│ • Bridge requires oracle threshold signatures │
│ • Replay attacks blocked at contract level │
│ • No admin minting of THC token │
│ │
│ Players do NOT need to trust the game operator │
│ for fund custody, payouts, or token ownership. │
│ │
└──────────────────────────────────────────────────┘
The critical insight: The game server is trusted for gameplay. The blockchain removes the need to trust any human for money. These are different concerns requiring different guarantees.
Threat Model
Threat 1: Game Server Manipulation
| Threat | Compromised server submits false match results |
| Impact | Incorrect prize distributions |
| Mitigation | Server-side result validation, match replay logging, audit trails |
| Residual risk | The game operator must be trusted for gameplay correctness — this is intentional and identical to every existing online game |
Threat 2: Prize Pool Admin Rug
| Threat | Developer drains tournament prize pool |
| Impact | Players and sponsors lose funds |
| Mitigation | PrizePool.sol has no admin withdrawal for prize funds. Only claim() by verified winners or refund() on cancellation. |
| Contract ref | sweepUnclaimed() exists only after claim deadline expires and sends to treasury, not arbitrary addresses |
Threat 3: Oracle Collusion
| Threat | Oracle operators collude to forge bridge attestations |
| Impact | Unauthorized THC minting on Avalanche |
| Mitigation | Multi-oracle threshold (require(valid >= threshold)) — production requires threshold ≥ 3 |
| Contract ref | FadedMonsutaTHC.sol lines 322–330 |
Threat 4: Bridge Replay Attack
| Threat | Attacker resubmits a previously valid claim() call |
| Impact | Double-minting of bridged tokens |
| Mitigation | claimed[claimHash] mapping — claimHash = keccak256(DOMAIN_SEPARATOR, td.id). Checked and set atomically. |
| Additional | signed[td.id][signer] prevents each oracle from approving the same teleport twice |
Threat 5: Cross-Chain Replay (Domain Separation)
| Threat | Signatures valid on one network replayed on another |
| Impact | Token minting on unintended chain |
| Mitigation | DOMAIN_SEPARATOR = keccak256(FADEDMONSUTA_THC_BRIDGE, block.chainid, address(this)) — unique per chain + contract address |
Threat 6: Wrong Chain ID Submission
| Threat | WAX teleport data for BSC submitted to Avalanche contract |
| Impact | Token minting on wrong contract |
| Mitigation | require(thisChainId == td.chainId, "wrong chain") — thisChainId = 2 on Avalanche deployment |
Threat 7: Expired Claim
| Threat | Old signed packet used after intended window |
| Impact | Stale or unintended claims |
| Mitigation | require(block.timestamp < td.ts + 30 days, "expired") — enforced in verifySigData() |
Threat 8: Reentrancy on Prize Claim
| Threat | Malicious winner contract re-enters claim() to drain pool |
| Impact | Pool drained beyond winner's allocation |
| Mitigation | ReentrancyGuardUpgradeable on all fund movement functions: fund(), fundERC20(), register(), claim(), refund() |
Threat 9: NFT Theft Through Staking
| Threat | Staker sells/transfers NFT while keeping staking weight |
| Impact | Illegitimate reward accumulation |
| Mitigation | syncPosition() and syncAccount() detect ownership mismatch via IERC721.ownerOf() and remove invalidated positions |
Threat 10: Client-Side Cheating
| Threat | Modified game client sends fabricated game actions |
| Impact | Unfair match outcomes |
| Mitigation | Server-authoritative architecture. Client sends inputs only. Server resolves all state transitions. |
Contract-Level Security Properties
FadedMonsutaTHC.sol
| Property | Implementation |
|---|---|
| No admin minting | Tokens only created via claim() with valid oracle signatures |
| Oracle threshold | Configurable threshold — requires N valid unique oracle sigs |
| Domain separation | DOMAIN_SEPARATOR derived from chainid + address(this) |
| Cannot withdraw THC | transferAnyERC20Token() explicitly blocks withdrawal of THC itself |
| Two-step ownership | transferOwnership() + acceptOwnership() |
PrizePool.sol
| Property | Implementation |
|---|---|
| No admin prize withdrawal | No such function exists. Owner can only cancel (trigger refunds) or extend deadline. |
| Winner funds are separate | _winnerAmounts mapping is separate from _deposits. Winners and depositors use different claim paths. |
| No double claim | _hasClaimed[winner] checked and set atomically in claim() |
| Results are final | Once submitResults() transitions pool to Finalized, no further results can be submitted |
| Reentrancy protection | nonReentrant on all fund-moving functions |
| Emergency pause | pause() / unpause() — owner only |
| ERC-20 rescue | recoverERC20() — cannot recover settlement token while pool is active |
| Upgrade authorization | _authorizeUpgrade() — owner only |
PrizePoolFactory.sol
| Property | Implementation |
|---|---|
| Deployer whitelist | Only _authorizedDeployers can create pools — prevents pool spam |
| Implementation ownership | Factory owner controls which implementation new proxies use |
| Registry immutability | _isPool mapping is append-only — pools cannot be removed |
| Upgrade protection | Factory is itself UUPS — owner-only upgrades |
NFTStaking.sol
| Property | Implementation |
|---|---|
| Fund theft prevention | fundPool() only adds to rewardPool. No withdrawal function. |
| Reward calculation precision | ACC_PRECISION = 1e18 to avoid rounding errors |
| Lock enforcement | lockUntil checked in unstake() — no early exit |
| NFT ownership verification | IERC721.ownerOf(tokenId) == msg.sender checked at stake time |
| Stale position cleanup | syncPosition() and syncAccount() detect transferred NFTs |
| Distributor gating | distribute() restricted to _distributors mapping |
teleporteos (WAX)
| Property | Implementation |
|---|---|
| Deposit-then-teleport | Users must first transfer tokens before initiating teleport |
| Oracle deduplication | Each oracle can sign each teleport ID only once |
ORACLE_CONFIRMATIONS | Minimum approvals before token release |
| Cancel mechanism | Teleports can be cancelled by owner after expiry |
Oracle Network Security
Requirements
| Requirement | Rationale |
|---|---|
threshold ≥ 3 | Single oracle compromise cannot mint tokens |
| Hardware wallet or KMS | Oracle private keys must not be in plaintext on disk |
| Separate infrastructure | Oracles on different cloud providers / regions |
| Alert on unusual volumes | Detect abnormal bridge activity early |
| Rate cap per epoch | Limit maximum single-period damage from compromise |
Oracle Fault Tolerance
oracle-eth.js handles oracle failure gracefully:
- Multiple RPC endpoints with automatic failover
- Block progress persisted to disk (crash-safe)
- Worker sharding for parallel redundancy
- Incomplete bridge events tracked in
incomplete-eth.js
For Integrating Games
Games adopting Monsuta Core inherit these properties automatically:
- ✅ Prize pool funds cannot be stolen by the game developer
- ✅ Replay attacks blocked on all bridge claims
- ✅ Oracle collusion requires N-of-M compromise
- ✅ Reentrancy protection on all fund movements
- ✅ NFT staking validates real-time ownership
Games are responsible for:
- ⚠️ Game server security (hardened infra, access control, secret management)
- ⚠️ Match result integrity (anti-cheat, replay validation)
- ⚠️ Oracle key management (in production: KMS, not env vars)
- ⚠️ Setting correct production thresholds (bridge, oracle count)