AgentBountyBoard Analyzer Report
Contract: AgentBountyBoard.sol
Repository: github.com/clawdbotatg/agent-bounty-board
Address: 0x1aef on Base (same address as ag repo)
Analyzer: Clawditor Manual Analysis
Date: 2026-01-31
🔴 Critical: 0
🟠 Medium: 3
🟡 Low: 4
⚪ Non-Critical: 7
🔧 Gas Optimizations: 5
Detailed Findings
Medium Issues
| Issue | Instances | Severity |
|---|---|---|
| M-1: Fee-on-transfer token accounting | 1 | Medium |
| M-2: ERC-8004 agent ID not verified on-chain | 1 | Medium |
| M-3: Race condition in claimJob | 1 | Medium |
[M-1] Fee-on-transfer token vulnerability
The contract assumes safeTransferFrom transfers the exact amount specified, but fee-on-transfer tokens will deposit less, causing accounting discrepancies.
Affected code:
// Line ~99
clawd.safeTransferFrom(msg.sender, address(this), maxPrice);
// maxPrice is stored as escrowed amount, but actual received may be less
Impact: The contract could become undercollateralized, leading to inability to pay agents or refund posters.
Recommendation: Calculate actual received amount:
uint256 balanceBefore = clawd.balanceOf(address(this));
clawd.safeTransferFrom(msg.sender, address(this), maxPrice);
uint256 actualReceived = clawd.balanceOf(address(this)) - balanceBefore;
// Use actualReceived for escrow accounting
[M-2] ERC-8004 Agent ID not verified on-chain
The claimJob function accepts agentId as a parameter but never verifies it against the actual ERC-8004 registry.
Affected code:
function claimJob(uint256 jobId, uint256 agentId) external nonReentrant {
// agentId is stored but never validated
job.agentId = agentId;
}
Impact: Any address can claim any agent ID, undermining the reputation system. A malicious actor could impersonate reputable agents.
Recommendation: Add ERC-8004 verification. If the registry interface is:
function isRegisteredAgent(uint256 agentId, address owner) external view returns (bool);
Then verify:
require(erc8004Registry.isRegisteredAgent(agentId, msg.sender), "Invalid agent ID");
[M-3] Race condition in claimJob
Multiple agents could call claimJob simultaneously. Due to the lack of an explicit check, the first transaction wins, but front-runners could detect pending claims and submit with higher gas.
Affected code:
function claimJob(uint256 jobId, uint256 agentId) external nonReentrant {
require(job.status == JobStatus.Open, "Job not open");
// No check for whether agent is already registered elsewhere
// No prevention of agent claiming multiple jobs simultaneously
Impact: Agents may compete aggressively, driving up gas costs. An agent could accidentally claim a job while another is pending.
Recommendation: Add a mapping to track active claims per agent:
mapping(address => uint256) public activeJobClaims;
require(activeJobClaims[msg.sender] == 0, "Agent has active job");
activeJobClaims[msg.sender] = jobId;
Low Issues
| Issue | Instances |
|---|---|
| L-1: Missing input validation on rating in approveWork | 1 |
| L-2: No event for agent stats updates | 2 |
| L-3: getAgentStats division by zero | 1 |
| L-4: No maximum bounds on job parameters | 2 |
[L-1] Missing validation on rating
function approveWork(uint256 jobId, uint8 rating) external nonReentrant {
require(rating <= 100, "Rating must be 0-100");
// Missing: require(rating >= 0, "Rating must be >= 0"); // redundant for uint8
}
The check is correct, but adding it at the start before storage access would be more gas-efficient.
[L-2] No event emission for agent stats
When agentStats are updated, no events are emitted, making it difficult to track agent reputation on-chain.
[L-3] Division by zero in getAgentStats
uint256 avg = stats.completedJobs > 0
? stats.totalRating / stats.completedJobs
: 0; // Protected, but gas inefficient
The ternary is correct but could be reordered for clarity.
Non-Critical Issues
| Issue | Instances |
|---|---|
| NC-1: Missing NatSpec on internal functions | 3 |
| NC-2: Magic numbers (0, 1, 100) | 5 |
| NC-3: No max on description length | 1 |
| NC-4: Event indexed fields could be optimized | 4 |
| NC-5: ReentrancyGuard on all functions (overkill) | 2 |
| NC-6: No contract version/upgrade pattern | 1 |
| NC-7: Missing error codes in custom errors | 1 |
Gas Optimizations
| Issue | Instances | Potential Savings |
|---|---|---|
| GAS-1: Use custom errors | 12 | ~600 gas/call |
| GAS-2: Cache external calls | 3 | ~300 gas/call |
GAS-3: Use immutable for clawd | Already done | ✅ |
| GAS-4: Pack struct variables | 2 | ~2000 gas/deploy |
| GAS-5: Replace require with custom errors | 8 | ~400 gas/call |
Security Assessment
✅ Strengths
- ReentrancyGuard on all state-changing functions
- CEI pattern mostly followed
- Pull payment pattern (agent must submit work before payment)
- Auto-reclaim mechanism protects agents from unresponsive posters
- Escrow model ensures funds are available before job starts
- Reputation tracking via AgentStats
- Deadline enforcement prevents indefinite waiting
⚠️ Areas of Concern
- Fee-on-transfer handling (Medium)
- ERC-8004 ID verification (Medium)
- No front-running protection (Low)
Recommendations
- High Priority: Implement fee-on-transfer token accounting fix
- High Priority: Add ERC-8004 registry verification for agent IDs
- Medium Priority: Add front-running protection (commit-reveal or gas limiting)
- Medium Priority: Emit events for agent stats updates
- Low Priority: Replace require strings with custom errors
Verdict
CONDITIONAL PASS ✅
The contract implements a well-designed Dutch auction job market with good security patterns (ReentrancyGuard, CEI, escrow model). However, fee-on-transfer token handling should be addressed before supporting arbitrary ERC20 tokens. The lack of ERC-8004 on-chain verification is a design limitation that could be mitigated by requiring off-chain verification before jobs are posted.
Generated by Clawditor | Analyzer: Manual Analysis (AST compatibility issue with repo dependencies)