BuybackExecutor
Holds the 60% allocation, buys PLOUTO on the canonical route, and retires everything it acquires in the same transaction.
Immutables
IPloutoRegistry public immutable registry;IPonsV2Factory public immutable ponsFactory;IPoolManager public immutable poolManager;address public immutable ponsMemeHook; // read from factory.memeHook()uint256 public immutable expectedChainId; address public constant DEAD_ADDRESS = 0x000000000000000000000000000000000000dEaD;Safety parameters
Adjustable by DEFAULT_ADMIN_ROLE, with sane defaults:
| Parameter | Default | Purpose |
|---|---|---|
maxEthPerExecution | 0.25 ETH | Hard cap on a single trade. |
maxBudgetShareBps | 2500 | Cap as a share of available budget. |
maxPriceImpactBps | 500 | On-chain tolerance versus pre-trade spot. |
minEthPerExecution | 0.001 ETH | Dust floor. |
setSafetyParams validates its inputs and reverts with InvalidParams on nonsense (zero caps, bps over 10,000, min above max).
Execution
function executeBuyback(uint256 ethIn, uint256 minTokensOut, uint256 deadline) external onlyRole(KEEPER_ROLE) nonReentrant whenNotPaused returns (uint256 tokensRetired)In order:
block.chainid == expectedChainId, elseWrongChain.block.timestamp <= deadline, elseDeadlinePassed.minTokensOut != 0, elseZeroMinTokensOut.- Registry initialized, else
NotInitialized. - Spend bounds —
NoBudget,AmountTooSmall,ExceedsBudget,ExceedsPerExecutionCap,ExceedsBudgetShare. - Factory record matches the registry, else
RouteMismatch. - Budget debited before the route runs.
- Route by phase.
- Acquired amount measured by balance delta;
InsufficientOutputif short. - Retire.
- Record and emit.
The residual rule
uint256 shareCap = (budget * maxBudgetShareBps) / BPS;// Always allow draining a budget at or below the per-execution cap, otherwise a// small residual balance could never be spent.if (ethIn > shareCap && ethIn != budget) revert ExceedsBudgetShare(ethIn, shareCap);Without this, a budget smaller than the share cap would be permanently unspendable.
Retirement
uint256 supplyBefore = _totalSupply(token);(bool ok,) = token.call(abi.encodeWithSignature("burn(uint256)", amount));if (ok && _totalSupply(token) + amount == supplyBefore) { totalPloutoBurned += amount; return true;}IERC20(token).safeTransfer(DEAD_ADDRESS, amount);totalPloutoSentToDead += amount;return false;The supply is checked, not assumed. A burn that succeeds without reducing supply falls through to the dead address and is reported as such.
Price impact, enforced on chain
Both routes compute an ideal output from pre-trade spot and require the realised output to land within tolerance. Pre-graduation that comes from the curve reserves; post-graduation from sqrtPriceX96 read out of PoolManager storage via extsload.
This is what makes the keeper untrusted: minTokensOut = 1 still cannot execute a trade worse than maxPriceImpactBps.
Donations
receive() external payable { if (msg.sender == address(poolManager)) return; // v4 settlement refund totalDonationsReceived += msg.value; emit DonationReceived(msg.sender, msg.value);}Donated ETH is never spendable as budget.