# Asset-accounting callback security review

Base: integrated `8bb19e4e977780648061e635c63cfcbc48961dde`.

## Findings reviewed and remediation

### `arbitrary-send-erc20` — remediated

`PullPaymentsV3._pullExact` and the local Reward helper no longer accept a caller-controlled `from` address. They debit only `msg.sender` through `SafeERC20.safeTransferFrom`. This preserves allowance-based pulls while preventing any caller from supplying a different account as the debit source.

### `reentrancy-eth` — remediated with reservation plus one-line detector dispositions

* `TaskEscrowV3.createTaskERC20` now validates, reserves `assetLiabilities`, records the task, and only then makes the ERC-20 pull. The shared pull-payment reentrancy lock covers task creation, lifecycle transitions, surplus skimming, and credit withdrawal. A token callback therefore sees a fully reserved task but cannot bid, create, settle, or refund it.
* `RewardProgramV3.fundERC20` now updates program funding and `assetLiabilities` before the ERC-20 pull. All state-changing reward entry points share `nonReentrant`, so a callback cannot cancel, issue, release, claim, or alter the program during funding.

The two `slither-disable-next-line reentrancy-eth` comments are intentionally restricted to the exact guarded ERC-20 pull lines. They suppress the detector's syntactic report after state reservation; neither suppresses unrelated sites nor modifies Slither configuration.

### `reentrancy-balance` — false positive after callback protection, with narrow source-line dispositions

Exact receipt/delivery validation necessarily reads balances immediately before and after an ERC-20 call. The following guarded sites retain those checks and carry only a `slither-disable-next-line reentrancy-balance` disposition on the balance snapshot:

* PullPaymentsV3: exact inbound pull, exact outbound sender snapshot, exact outbound recipient snapshot.
* RewardProgramV3: exact inbound pull; exact claim sender and receiver snapshots. Claim liability is decremented before transfer.
* MutualAidPoolV3: exact contribution snapshot; exact payout sender and receiver snapshots. Mutating entry points and `receive` share OpenZeppelin's reentrancy guard.
* VaultfireBIPVaultV3: exact deposit snapshot; exact withdrawal source and receiver snapshots. Deposit, mint, withdraw, redeem, and owner rotation share the guard.

These are not broad detector exclusions. The accounting checks remain required to reject fee-on-transfer, no-delivery, and output-fee tokens where liabilities require exact values.

## Regression coverage

`CallbackReentrancyV3.t.sol` uses a token that executes configured callbacks from both `transferFrom` and `transfer` and proves:

1. Task ERC-20 creation reserves liabilities/task state and rejects a callback bid.
2. Task pull-credit withdrawal rejects a callback nested task creation.
3. Reward ERC-20 funding rejects a sponsor-token callback cancellation.
4. Reward claim rejects replay through a token callback, paying exactly once.
5. Mutual Aid payout rejects an owner-token callback replay.
6. Vault deposit and withdrawal reject nested callback entry points.

Existing asset-accounting tests continue to cover exact input/output deltas, fee/no-delivery rejection, forced-donation surplus isolation, solvency, task caps, and deadline paths.

## Validation commands

* `forge fmt --check`
* `forge build`
* `forge test -vv`
* `forge build --sizes`
* `PATH=$HOME/.foundry/bin:$PATH python3 ci/run_slither_gate.py`

The unfiltered size report retains the pre-existing `BasePilotForkBootstrap` test/fork helper at 102,441 runtime bytes (over EIP-170), so Forge exits nonzero for that report alone. The changed deployable contracts remain below the limit: PullPaymentsV3Concrete 1,934 B, MutualAidPoolV3 3,739 B, RewardProgramV3 5,119 B, VaultfireBIPVaultV3 6,360 B, and TaskEscrowV3 8,347 B.
