Smart Contract Security Scan

Date: 2026-05-17 - Brain task #8906 - Repo: myaitoken/myai-contracts-base

Scope

Three deployed contracts on Base (target network):MyAIEscrow.sol, MyAIReputation.sol,MyAIGovernance.sol. 408 nSLOC total, Solidity 0.8.20, OpenZeppelin Contracts 5.x.

Tools

Findings summary (pre-fix)

SeverityCountSource
High2Slither + Aderyn (reentrancy-no-eth)
Medium2Slither (low-level-call in Governance.execute without nonReentrant); follow-up: quorumBps never enforced (fixed 2026-05-17, commit def347b)
Low / Info15+Aderyn + Slither (centralization, pragma, naming, etc.)

Findings + remediation

H-1 - Reentrancy: state change after external call (MyAIEscrow.lockPayment)

State written after transferFrom. Protected bynonReentrant guard, but reorder still recommended.Fixed by writing escrows[jobId] and pushing to escrowIds before the token pull (CEI).

H-1 - Reentrancy: state change after external call (MyAIReputation.stake)

Had no reentrancy guard at all. Fixed by inheritingReentrancyGuard, adding nonReentrant tostake(), and reordering the state updates beforesafeTransferFrom.

M-1 - Low-level call in Governance.execute without reentrancy guard

p.target.call(p.callData) can re-enter back into Governance. Fixed by addingnonReentrant to execute() and markingreputation immutable.

M-2 - quorumBps declared but never enforced (added 2026-05-17)

MyAIGovernance.quorumBps = 1000 (10%) was declared as state but finalize() and execute()only checked votesFor > votesAgainst. A proposal could pass with a single voter regardless of turnout.Quorum bug fixed in commit def347b on 2026-05-17: propose() now snapshots total eligible voting weight, and finalize() reverts with custom error QuorumNotMet(participationBps, requiredBps)when turnout is below quorumBps. Four new unit tests (Q1-Q4) cover met / not-met / exactly-met / snapshot-stable; full suite: 70/70 passing.

L - Unsafe ERC20 operations (multiple)

All transfer / transferFrom calls migrated to OpenZeppelin SafeERC20. Supports tokens that do not return bool.

L - Missing zero-address checks (constructors + setters)

Added require(_x != address(0), ...) to all three constructors and setCoordinator / setTreasury.

L - State change without event (MyAIReputation.setCoordinator)

Added CoordinatorUpdated event and emit on change.setEscrowTimeout likewise emitsEscrowTimeoutUpdated and is bounded to [5min, 30d].

L - Unspecific pragma

Pinned all three contracts from ^0.8.20 to0.8.20.

Correctness bug found - latencyScore uses per-call latency

Not flagged by tools but caught during manual triage:recordCompletion recomputed reputation using thelast call's latencyMs rather than the running avgLatencyMs, making the latency component biased to whatever the most recent call happened to be. Fixed to use p.avgLatencyMs.

Accepted risks (not fixed)

Post-fix scan

Fix commit

myaitoken/myai-contracts-base @ 463629a - security: fix H/M findings from Slither+Aderyn scan (#8906)

Disclaimer

Static analysis is not a substitute for a manual audit. We will commission a third-party manual audit prior to mainnet deployment of these contracts. Findings here are produced by automated tools (Slither, Aderyn) and triaged by the MyAi team.