AgentAuditRegistryV3 accumulates ETH from every stake() call into accruedServiceFees (and into the contract balance via the bond itself), but the contract exposes no withdrawal function. Any ETH paid as service fees by agent developers is permanently trapped.
Analysis
accruedServiceFees is declared at contracts/src/AgentAuditRegistryV3.sol:92 and incremented inside stake():
uint256 public accruedServiceFees;
// ...
accruedServiceFees += serviceFee; // line ~196 in stake()
I grepped the entire contract (and AgentAuditRegistryV2.sol, which has the same pattern) for the words withdraw, transfer, call{value:, and send. There is no path that moves ETH out of the contract for the operator/owner, except payable(profile.developer).transfer(...) which only refunds the developer's own bond on releaseBond.
Root Cause
The contract has the accounting variable but never implemented the corresponding action. Likely an oversight that wasn't caught because the unit tests don't assert on operator-side ETH balance changes.
Suggested Fix
Add an onlyOwner (or onlyOperator) function:
function withdrawServiceFees(address payable to, uint256 amount) external onlyOwner {
require(amount <= accruedServiceFees, "INSUFFICIENT_FEES");
accruedServiceFees -= amount;
(bool ok, ) = to.call{value: amount}("");
require(ok, "TRANSFER_FAILED");
emit ServiceFeesWithdrawn(to, amount);
}
Impact
Critical — every penny of fees collected on a deployed instance is unrecoverable without a contract upgrade. If V3 is already live on Polygon Edge with real funds, this needs an emergency upgrade path.
AgentAuditRegistryV3accumulates ETH from everystake()call intoaccruedServiceFees(and into the contract balance via the bond itself), but the contract exposes no withdrawal function. Any ETH paid as service fees by agent developers is permanently trapped.Analysis
accruedServiceFeesis declared atcontracts/src/AgentAuditRegistryV3.sol:92and incremented insidestake():I grepped the entire contract (and
AgentAuditRegistryV2.sol, which has the same pattern) for the wordswithdraw,transfer,call{value:, andsend. There is no path that moves ETH out of the contract for the operator/owner, exceptpayable(profile.developer).transfer(...)which only refunds the developer's own bond onreleaseBond.Root Cause
The contract has the accounting variable but never implemented the corresponding action. Likely an oversight that wasn't caught because the unit tests don't assert on operator-side ETH balance changes.
Suggested Fix
Add an
onlyOwner(oronlyOperator) function:Impact
Critical — every penny of fees collected on a deployed instance is unrecoverable without a contract upgrade. If V3 is already live on Polygon Edge with real funds, this needs an emergency upgrade path.