Skip to content

fix(contracts): check transfer return value on profit sweep#307

Merged
obchain merged 2 commits intomainfrom
fix/profit-sweep-return-check
Apr 24, 2026
Merged

fix(contracts): check transfer return value on profit sweep#307
obchain merged 2 commits intomainfrom
fix/profit-sweep-return-check

Conversation

@obchain
Copy link
Copy Markdown
Owner

@obchain obchain commented Apr 23, 2026

Summary

mev-sim-auditor flagged CharonLiquidator.sol:413 as a P3 issue during static audit of PR #305: IERC20(p.debtToken).transfer(COLD_WALLET, profit) discarded the return value. Standard ERC-20s revert on failure, but BEP-20 and some legacy tokens return false without reverting. A silent false would strand profit in this contract while the emit, Aave approval, and flashloan repayment all succeed — operator logs would show a successful liquidation while the cold wallet received nothing.

Fix

Adopt the same bool ok; require(ok, ...) pattern already used by rescue() at :463. No SafeERC20 — consistent with the no-external-dependency policy stated at :440.

Review (solidity-secure-dev): APPROVE

  • Pattern correct; SafeERC20 would violate no-deps policy.
  • Sweep-before-approval ordering correct (revert unwinds the whole tx before Aave gets a spendable allowance).
  • No re-entrancy path via a malicious debtToken: executeOperation rejects re-entry at require(msg.sender == AAVE_POOL) and executeLiquidation is nonReentrant.
  • LiquidationExecuted emit is AFTER the require, so a failed sweep never emits a misleading success log.

Follow-up (out of scope)

Five approve() calls in the same file also discard their return values (lines 331, 344, 373, 390, 428). Tracked for a separate hardening PR; Venus-listed BSC tokens all conform today but the pattern carries the same theoretical gap.

Test plan

  • forge build --force -> Compiler run successful
  • Fork-test assertion: a BEP-20 that returns false on transfer causes executeOperation to revert with "profit: transfer failed". Runs on feat/25 (parallel-terminal scope) after this merges.

The profit sweep at executeOperation line 413 discarded the return
value of IERC20(p.debtToken).transfer(COLD_WALLET, profit). Standard
ERC-20s revert on failure, but BEP-20 and legacy tokens return `false`
without reverting. A silent `false` would leave the profit stranded
in this contract while the subsequent emit, Aave approval, and
flashloan repayment all succeed — operator logs would show a
successful liquidation while the cold wallet received nothing.

Adopt the same bool-ok + require pattern already used by rescue() at
:463 (no SafeERC20, consistent with the no-external-dependency
policy).

Other approve() call sites in this file also discard return values
(lines 331, 344, 373, 390, 428) — tracked as a follow-up, out of
scope here.
@obchain obchain changed the base branch from feat/12-charon-liquidator-full to main April 24, 2026 19:39
@obchain obchain merged commit 76087c8 into main Apr 24, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant