Flash unwinder contracts and scripts, relay lane tuning, trustless bridge and token-aggregation updates.

Made-with: Cursor
This commit is contained in:
defiQUG
2026-04-12 06:33:54 -07:00
parent 662b35ad69
commit 6817f53591
40 changed files with 682 additions and 88 deletions

View File

@@ -5,6 +5,19 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
interface IAavePoolLike {
/// @notice Standard V3 flash loan (single-asset via length-1 arrays). Use this on Aave V3.2+ pools
/// where `flashLoanSimple` may revert with `NotActivated()`.
function flashLoan(
address receiverAddress,
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata interestRateModes,
address onBehalfOf,
bytes calldata params,
uint16 referralCode
) external;
/// @dev Retained for compatibility with older pool deployments; prefer `flashLoan` for new integrations.
function flashLoanSimple(
address receiverAddress,
address asset,
@@ -14,6 +27,17 @@ interface IAavePoolLike {
) external;
}
/// @dev Callback for `flashLoan` (multi-asset API).
interface IAaveFlashLoanReceiver {
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external returns (bool);
}
interface IAaveFlashLoanSimpleReceiver {
function executeOperation(
address asset,
@@ -58,10 +82,10 @@ interface IAaveAtomicBridgeCoordinator {
/**
* @title AaveQuotePushFlashReceiver
* @notice Aave V3 flashLoanSimple receiver for the quote-push workflow:
* flash borrow quote -> buy PMM base -> unwind base externally -> repay lender, retaining any surplus.
* @notice Aave V3 flash-loan receiver for the quote-push workflow:
* flash borrow quote (`flashLoan` single-asset) -> buy PMM base -> unwind base externally -> repay lender, retaining any surplus.
*/
contract AaveQuotePushFlashReceiver is IAaveFlashLoanSimpleReceiver {
contract AaveQuotePushFlashReceiver is IAaveFlashLoanSimpleReceiver, IAaveFlashLoanReceiver {
using SafeERC20 for IERC20;
address public immutable pool;
@@ -119,7 +143,29 @@ contract AaveQuotePushFlashReceiver is IAaveFlashLoanSimpleReceiver {
}
function flashQuotePush(address asset, uint256 amount, QuotePushParams calldata params) external {
IAavePoolLike(pool).flashLoanSimple(address(this), asset, amount, abi.encode(address(this), params), 0);
address[] memory assets = new address[](1);
uint256[] memory amts = new uint256[](1);
uint256[] memory modes = new uint256[](1);
assets[0] = asset;
amts[0] = amount;
modes[0] = 0;
// `onBehalfOf` must not be zero on some Aave V3.2+ deployments (mapping lookups revert NotActivated).
IAavePoolLike(pool).flashLoan(
address(this), assets, amts, modes, address(this), abi.encode(address(this), params), 0
);
}
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external returns (bool) {
if (msg.sender != pool) revert UntrustedPool();
if (assets.length != 1 || amounts.length != 1 || premiums.length != 1) revert BadParams();
_executeQuotePush(assets[0], amounts[0], premiums[0], initiator, params);
return true;
}
function executeOperation(
@@ -130,6 +176,17 @@ contract AaveQuotePushFlashReceiver is IAaveFlashLoanSimpleReceiver {
bytes calldata params
) external returns (bool) {
if (msg.sender != pool) revert UntrustedPool();
_executeQuotePush(asset, amount, premium, initiator, params);
return true;
}
function _executeQuotePush(
address asset,
uint256 amount,
uint256 premium,
address initiator,
bytes calldata params
) internal {
(address expectedInitiator, QuotePushParams memory p) = abi.decode(params, (address, QuotePushParams));
if (initiator != expectedInitiator) revert UntrustedInitiator();
if (
@@ -148,7 +205,6 @@ contract AaveQuotePushFlashReceiver is IAaveFlashLoanSimpleReceiver {
uint256 unwindOut = _unwindBaseIntoQuote(p.baseToken, asset, p.externalUnwinder, p.minOutUnwind, p.unwindData);
uint256 surplus = _approveRepayment(asset, amount + premium);
emit QuotePushExecuted(asset, p.baseToken, amount, premium, baseOut, unwindOut, surplus);
return true;
}
function _swapQuoteForBase(address asset, uint256 amount, address integration, address pmmPool, uint256 minOutPmm)

View File

@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
interface IDODOIntegrationSwapExactIn {
function swapExactIn(address pool, address tokenIn, uint256 amountIn, uint256 minAmountOut)
external
returns (uint256 amountOut);
}
/**
* @title TwoHopDodoIntegrationUnwinder
* @notice Unwind `tokenIn -> midToken` on `poolA`, then `midToken -> tokenOut` on `poolB`, both via the same DODO PMM integration.
* @dev `data` = abi.encode(address poolA, address poolB, address midToken, uint256 minMidOut).
* Use for Mainnet when there is no Uniswap V3 route for cWUSDC/USDC but there *is* depth on
* cWUSDC/cWUSDT and a second PMM pool cWUSDT/USDC (sizes must fit the second pool).
*/
contract TwoHopDodoIntegrationUnwinder {
using SafeERC20 for IERC20;
address public immutable integration;
error BadParams();
constructor(address integration_) {
if (integration_ == address(0)) revert BadParams();
integration = integration_;
}
function unwind(address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, bytes calldata data)
external
returns (uint256 amountOut)
{
if (tokenIn == address(0) || tokenOut == address(0) || tokenIn == tokenOut || amountIn == 0) revert BadParams();
(address poolA, address poolB, address midToken, uint256 minMidOut) =
abi.decode(data, (address, address, address, uint256));
if (poolA == address(0) || poolB == address(0) || midToken == address(0)) revert BadParams();
if (midToken == tokenIn || midToken == tokenOut) revert BadParams();
IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
IERC20(tokenIn).forceApprove(integration, amountIn);
uint256 midOut = IDODOIntegrationSwapExactIn(integration).swapExactIn(poolA, tokenIn, amountIn, minMidOut);
if (midOut == 0) revert BadParams();
IERC20(midToken).forceApprove(integration, midOut);
amountOut = IDODOIntegrationSwapExactIn(integration).swapExactIn(poolB, midToken, midOut, minAmountOut);
IERC20(tokenOut).safeTransfer(msg.sender, amountOut);
}
}