vielite's blog

Silent Failure in handleAgentTaxes

April 4, 2026
21 min read
Table of Contents
silent-failure-in-handle-agent-taxes

Summary

handleAgentTaxes() updates accounting state and records tax events before calling _swapForAsset(), but it ignores the result of that external call.

Because _swapForAsset() returns a boolean success indicator, the current implementation can treat failed swaps as if settlement succeeded.

Vulnerability details

2025-04-virtuals-protocol/contracts/tax/AgentTax.sol
function handleAgentTaxes(
uint256 agentId,
bytes32[] memory txhashes,
uint256[] memory amounts,
uint256 minOutput
) public onlyRole(EXECUTOR_ROLE) {
require(txhashes.length == amounts.length, "Unmatched inputs");
TaxAmounts storage agentAmounts = agentTaxAmounts[agentId];
uint256 totalAmount = 0;
for (uint i = 0; i < txhashes.length; i++) {
bytes32 txhash = txhashes[i];
if (taxHistory[txhash].agentId > 0) {
revert TxHashExists(txhash);
}
taxHistory[txhash] = TaxHistory(agentId, amounts[i]);
totalAmount += amounts[i];
emit TaxCollected(txhash, agentId, amounts[i]);
}
agentAmounts.amountCollected += totalAmount;
_swapForAsset(agentId, minOutput, maxSwapThreshold);
}

Impact

The contract records tax processing in taxHistory and increases agentAmounts.amountCollected, while agentAmounts.amountSwapped depends on the swap path succeeding. If the swap fails:

  • tax events remain marked as processed
  • accounting state advances
  • the asset conversion may never happen

That creates a mismatch between recorded state and actual settlement.

Recommendation

Check the return value from _swapForAsset() and either revert on failure or route failed swaps through an explicit retry or debt-accounting path. The state transition should not silently continue when the settlement leg fails.