{"sources":{"src/FlashLoopAdapter.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\n/// @title FlashLoopAdapter — atomic open/close of Aave v3 leveraged loops via a Safe module\n/// @notice Installed as a Safe MODULE (safe.enableModule(adapter)). The Safe calls open()/close()\n///         in ONE transaction; the adapter takes free flash liquidity (Morpho / Balancer v2, or\n///         Aave flashLoanSimple as paid fallback), and drives the Safe itself for every\n///         position-touching call via execTransactionFromModule — so the POSITION IS OWNED BY THE\n///         SAFE, with no credit delegation and no aToken approvals to any external contract.\n///\n///         open:  flash debt (L-1)*E -> swap debt->collateral (router calldata) -> supply to pool\n///                onBehalfOf safe -> [setUserEMode] -> safe borrows debt (module) -> safe transfers\n///                debt to adapter (module) -> repay flash -> sweep dust to safe.\n///         close: flash debt -> repay pool onBehalfOf safe -> safe withdraws collateral to adapter\n///                (module) -> swap collateral->debt (router calldata) -> repay flash -> sweep BOTH\n///                tokens to safe.\n///\n///         Aave-v3-only surface; swap route/calldata is quoted OFF-CHAIN at execution and\n///         min-out enforced ON-CHAIN. Stateless between calls (transient pending-op hash).\n///\n///         Engine contract: ~/goals/aave-yield-hunter/engine/CONTRACTS.md (\"Flash liquidity\").\n\ninterface IERC20 {\n    function balanceOf(address) external view returns (uint256);\n    function transfer(address, uint256) external returns (bool);\n    function approve(address, uint256) external returns (bool);\n}\n\ninterface ISafe {\n    function execTransactionFromModule(address to, uint256 value, bytes calldata data, uint8 operation)\n        external returns (bool);\n    function isModuleEnabled(address module) external view returns (bool);\n}\n\ninterface IAavePool {\n    function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;\n    function repay(address asset, uint256 amount, uint256 interestRateMode, address onBehalfOf)\n        external returns (uint256);\n    function flashLoanSimple(address receiverAddress, address asset, uint256 amount, bytes calldata params,\n        uint16 referralCode) external;\n}\n\ninterface IMorpho {\n    function flashLoan(address token, uint256 assets, bytes calldata data) external;\n}\n\ninterface IBalancerV2Vault {\n    function flashLoan(address recipient, address[] calldata tokens, uint256[] calldata amounts,\n        bytes calldata userData) external;\n}\n\ncontract FlashLoopAdapter {\n    enum Provider { MORPHO, BALANCER_V2, AAVE_SIMPLE }\n    enum Op { OPEN, CLOSE }\n\n    struct Params {\n        Op op;\n        Provider provider;\n        address providerAddr;   // Morpho / Balancer vault / Aave pool (flash source)\n        address pool;           // Aave v3 Pool of the target market\n        address collateral;     // collateral underlying\n        address debt;           // debt underlying (== flashed asset)\n        uint256 flashAmount;    // debt units to flash: open = (L-1)*E equiv; close = outstanding debt\n        address swapRouter;     // aggregator router (quoted off-chain at execution)\n        bytes   swapCalldata;   // router call: open = debt->collateral, close = collateral->debt\n        uint256 minOut;         // min output of the swap leg, on-chain enforced\n        uint8   emodeId;        // 0 = skip setUserEMode\n        uint256 withdrawAmount; // close only: collateral to withdraw (type(uint).max = all)\n    }\n\n    error NotSafeOwner();\n    error ModuleNotEnabled();\n    error UntrustedCallback();\n    error ModuleExecFailed(string what);\n    error SwapMinOut(uint256 got, uint256 want);\n    error FlashShortfall(uint256 have, uint256 owe);\n\n    uint256 private constant VARIABLE_RATE = 2;\n\n    // transient pending-operation guard: hash of (safe, params) set right before the\n    // flash call, checked+cleared in the callback. tstore -> zero cost between txs.\n    bytes32 private transient _pending;\n    address private transient _safe;\n\n    // ---------------------------------------------------------------- entrypoints\n\n    /// @notice msg.sender MUST be the Safe (which has this adapter enabled as module).\n    function open(Params calldata p) external { _start(p); }\n    function close(Params calldata p) external { _start(p); }\n\n    function _start(Params calldata p) internal {\n        if (!ISafe(msg.sender).isModuleEnabled(address(this))) revert ModuleNotEnabled();\n        _safe = msg.sender;\n        _pending = keccak256(abi.encode(msg.sender, p));\n        bytes memory data = abi.encode(p);\n        if (p.provider == Provider.MORPHO) {\n            IMorpho(p.providerAddr).flashLoan(p.debt, p.flashAmount, data);\n        } else if (p.provider == Provider.BALANCER_V2) {\n            address[] memory tokens = new address[](1);\n            uint256[] memory amounts = new uint256[](1);\n            tokens[0] = p.debt; amounts[0] = p.flashAmount;\n            IBalancerV2Vault(p.providerAddr).flashLoan(address(this), tokens, amounts, data);\n        } else {\n            IAavePool(p.providerAddr).flashLoanSimple(address(this), p.debt, p.flashAmount, data, 0);\n        }\n        // sweep every touched token back to the Safe — adapter never holds funds\n        _sweep(p.debt);\n        _sweep(p.collateral);\n    }\n\n    // ---------------------------------------------------------------- flash callbacks\n\n    /// Morpho: onMorphoFlashLoan(uint256 assets, bytes data); repay via approve (Morpho pulls).\n    function onMorphoFlashLoan(uint256 assets, bytes calldata data) external {\n        Params memory p = _validate(data);\n        _run(p, assets, 0);\n        IERC20(p.debt).approve(msg.sender, assets);\n    }\n\n    /// Balancer v2: receiveFlashLoan(tokens, amounts, feeAmounts, userData); repay via transfer.\n    function receiveFlashLoan(address[] calldata tokens, uint256[] calldata amounts,\n                              uint256[] calldata feeAmounts, bytes calldata userData) external {\n        Params memory p = _validate(userData);\n        _run(p, amounts[0], feeAmounts[0]);\n        IERC20(tokens[0]).transfer(msg.sender, amounts[0] + feeAmounts[0]);\n    }\n\n    /// Aave: executeOperation(asset, amount, premium, initiator, params); repay via approve (pool pulls).\n    function executeOperation(address asset, uint256 amount, uint256 premium, address initiator,\n                              bytes calldata params) external returns (bool) {\n        if (initiator != address(this)) revert UntrustedCallback();\n        Params memory p = _validate(params);\n        _run(p, amount, premium);\n        IERC20(asset).approve(msg.sender, amount + premium);\n        return true;\n    }\n\n    // ---------------------------------------------------------------- core\n\n    function _validate(bytes memory data) internal returns (Params memory p) {\n        p = abi.decode(data, (Params));\n        // provider addr is bound into the pending hash — only the flash source we just\n        // called can re-enter, with exactly the params we committed to.\n        if (msg.sender != p.providerAddr) revert UntrustedCallback();\n        bytes32 h = keccak256(abi.encode(_safe, p));\n        if (h != _pending) revert UntrustedCallback();\n        _pending = bytes32(0);\n    }\n\n    function _run(Params memory p, uint256 flashed, uint256 fee) internal {\n        address safe = _safe;\n        if (p.op == Op.OPEN) {\n            // 1. swap flashed debt -> collateral (router quoted off-chain, min-out on-chain)\n            uint256 got = _swap(p.debt, p.collateral, p.swapRouter, p.swapCalldata, p.minOut);\n            // 2. supply collateral to the pool, position owned by the Safe\n            IERC20(p.collateral).approve(p.pool, got);\n            IAavePool(p.pool).supply(p.collateral, got, safe, 0);\n            // 3. e-mode BEFORE borrow — the quoted LTV only exists inside the category\n            if (p.emodeId != 0) {\n                _moduleExec(safe, p.pool, abi.encodeWithSignature(\"setUserEMode(uint8)\", p.emodeId), \"setUserEMode\");\n            }\n            // 4. Safe borrows the flash repayment (+fee) against its new collateral\n            uint256 owed = flashed + fee;\n            _moduleExec(safe, p.pool,\n                abi.encodeWithSignature(\"borrow(address,uint256,uint256,uint16,address)\",\n                                        p.debt, owed, VARIABLE_RATE, uint16(0), safe), \"borrow\");\n            // 5. Safe funds the flash repayment\n            _moduleExec(safe, p.debt,\n                abi.encodeWithSignature(\"transfer(address,uint256)\", address(this), owed), \"fund repay\");\n        } else {\n            // 1. repay the Safe's debt with the flashed asset\n            IERC20(p.debt).approve(p.pool, flashed);\n            IAavePool(p.pool).repay(p.debt, flashed, VARIABLE_RATE, safe);\n            // 2. Safe withdraws collateral straight to the adapter\n            _moduleExec(safe, p.pool,\n                abi.encodeWithSignature(\"withdraw(address,uint256,address)\",\n                                        p.collateral, p.withdrawAmount, address(this)), \"withdraw\");\n            // 3. swap collateral -> debt to cover the flash (+fee); leftovers sweep to Safe\n            _swap(p.collateral, p.debt, p.swapRouter, p.swapCalldata, p.minOut);\n            uint256 have = IERC20(p.debt).balanceOf(address(this));\n            if (have < flashed + fee) revert FlashShortfall(have, flashed + fee);\n        }\n    }\n\n    function _swap(address tokenIn, address tokenOut, address router, bytes memory data, uint256 minOut)\n        internal returns (uint256 got)\n    {\n        IERC20(tokenIn).approve(router, type(uint256).max);\n        (bool ok, bytes memory ret) = router.call(data);\n        if (!ok) {\n            assembly { revert(add(ret, 0x20), mload(ret)) }\n        }\n        IERC20(tokenIn).approve(router, 0);\n        got = IERC20(tokenOut).balanceOf(address(this));\n        if (got < minOut) revert SwapMinOut(got, minOut);\n    }\n\n    function _moduleExec(address safe, address to, bytes memory data, string memory what) internal {\n        if (!ISafe(safe).execTransactionFromModule(to, 0, data, 0)) revert ModuleExecFailed(what);\n    }\n\n    function _sweep(address token) internal {\n        uint256 bal = IERC20(token).balanceOf(address(this));\n        if (bal > 0) IERC20(token).transfer(_safe, bal);\n    }\n}\n"}},"matchId":"43760590","creationMatch":"exact_match","runtimeMatch":"exact_match","verifiedAt":"2026-08-18T10:47:30Z","match":"exact_match","chainId":"1","address":"0x16bb8B912da187870C23eC6756bB3FAd061283d8"}