Refs #42
PR: feat(cli): wire scanner → router → builder → simulator pipeline (feat/17-cli-e2e-pipeline)
Commit: latest on feat/17-cli-e2e-pipeline
File: crates/charon-cli/src/main.rs or crates/charon-executor/src/builder.rs (swap_route construction)
PRD / invariant violated: PRD requires liquidations to be profitable after all costs including gas. min_amount_out must enforce this at the contract level.
Problem:
The PR sets swap_route.min_amount_out = quote.amount + quote.fee. This covers exactly the Aave flash loan repayment (principal + fee) and nothing else. It means:
- If the DEX swap returns exactly quote.amount + quote.fee, the bot receives zero collateral after repay.
- Gas cost comes out of the hot wallet with no reimbursement.
- The transaction is net-negative but does not revert (min_amount_out check passes).
The note 'on-chain backstop in CharonLiquidator.sol catches under-fills' is incorrect: the contract's amountOutMinimum is set from swap_route.min_amount_out — which the Rust layer sets to repay-only. The contract backstop is only as strong as what the Rust layer tells it.
The correct formula: min_amount_out = repay_amount + flash_fee + gas_cost_in_debt_token + minimum_profit_in_debt_token. Until the gas oracle is wired, use a conservative static floor denominated in the debt token's decimals.
Impact: Bot executes liquidations that cover flash loan repayment but are net-negative after gas. Hot wallet is drained without equivalent profit accumulation in the cold wallet.
Fix: Compute a static floor in debt-token units based on PLACEHOLDER_GAS_USD_CENTS and a debt token price estimate. Add it to min_amount_out. Track a GitHub issue for gas oracle wiring so this placeholder is replaced before mainnet deployment.
Refs #42
PR: feat(cli): wire scanner → router → builder → simulator pipeline (feat/17-cli-e2e-pipeline)
Commit: latest on feat/17-cli-e2e-pipeline
File: crates/charon-cli/src/main.rs or crates/charon-executor/src/builder.rs (swap_route construction)
PRD / invariant violated: PRD requires liquidations to be profitable after all costs including gas. min_amount_out must enforce this at the contract level.
Problem:
The PR sets swap_route.min_amount_out = quote.amount + quote.fee. This covers exactly the Aave flash loan repayment (principal + fee) and nothing else. It means:
The note 'on-chain backstop in CharonLiquidator.sol catches under-fills' is incorrect: the contract's amountOutMinimum is set from swap_route.min_amount_out — which the Rust layer sets to repay-only. The contract backstop is only as strong as what the Rust layer tells it.
The correct formula: min_amount_out = repay_amount + flash_fee + gas_cost_in_debt_token + minimum_profit_in_debt_token. Until the gas oracle is wired, use a conservative static floor denominated in the debt token's decimals.
Impact: Bot executes liquidations that cover flash loan repayment but are net-negative after gas. Hot wallet is drained without equivalent profit accumulation in the cold wallet.
Fix: Compute a static floor in debt-token units based on PLACEHOLDER_GAS_USD_CENTS and a debt token price estimate. Add it to min_amount_out. Track a GitHub issue for gas oracle wiring so this placeholder is replaced before mainnet deployment.