Files
smom-dbis-138/contracts/flash/AaveQuotePushFlashReceiver.sol
defiQUG 2b52cc6e32 refactor(archive): move historical contracts and adapters to archive directory
- Archived multiple non-EVM adapters (Algorand, Hedera, Tron, TON, Cosmos, Solana) and compliance contracts (IndyVerifier) to `archive/solidity/contracts/`.
- Updated documentation to reflect the historical status of archived components.
- Adjusted `foundry.toml` and `README.md` for clarity on historical dependencies and configurations.
- Enhanced Makefile and package.json scripts for improved contract testing and building processes.
- Removed obsolete contracts (AlltraCustomBridge, CommodityCCIPBridge, ISO4217WCCIPBridge, VaultBridgeAdapter) from the main directory.
- Updated implementation reports to indicate archived status for various components.
2026-04-12 18:21:05 -07:00

305 lines
11 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
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,
uint256 amount,
bytes calldata params,
uint16 referralCode
) 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,
uint256 amount,
uint256 premium,
address initiator,
bytes calldata params
) external returns (bool);
}
interface IAaveDODOQuotePushSwapExactIn {
function swapExactIn(address pool, address tokenIn, uint256 amountIn, uint256 minAmountOut)
external
returns (uint256 amountOut);
}
interface IAaveExternalUnwinder {
function unwind(address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, bytes calldata data)
external
returns (uint256 amountOut);
}
interface IAaveAtomicBridgeCoordinator {
struct CreateIntentParams {
uint64 sourceChain;
uint64 destinationChain;
address assetIn;
address assetOut;
uint256 amountIn;
uint256 minAmountOut;
address recipient;
uint256 deadline;
bytes32 routeId;
}
function createIntent(CreateIntentParams calldata p) external returns (bytes32 obligationId);
function submitCommitment(bytes32 obligationId, bytes32 settlementMode) external;
function obligationEscrow() external view returns (address);
}
/**
* @title AaveQuotePushFlashReceiver
* @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, IAaveFlashLoanReceiver, Ownable {
using SafeERC20 for IERC20;
address public immutable pool;
struct QuotePushParams {
address integration;
address pmmPool;
address baseToken;
address externalUnwinder;
uint256 minOutPmm;
uint256 minOutUnwind;
bytes unwindData;
AtomicBridgeParams atomicBridge;
}
struct AtomicBridgeParams {
address coordinator;
uint64 sourceChain;
uint64 destinationChain;
address destinationAsset;
uint256 bridgeAmount;
uint256 minDestinationAmount;
address destinationRecipient;
uint256 destinationDeadline;
bytes32 routeId;
bytes32 settlementMode;
bool submitCommitment;
}
error UntrustedPool();
error UntrustedInitiator();
error BadParams();
error InsufficientToRepay();
error InvalidAtomicBridge();
error NothingToSweep();
event QuotePushExecuted(
address indexed quoteToken,
address indexed baseToken,
uint256 borrowedAmount,
uint256 premium,
uint256 baseOut,
uint256 unwindOut,
uint256 surplus
);
event AtomicBridgeTriggered(
bytes32 indexed obligationId,
address indexed coordinator,
address indexed destinationRecipient,
uint256 bridgeAmount,
uint256 minDestinationAmount
);
event TokenSwept(address indexed token, address indexed to, uint256 amount);
event SurplusSwept(address indexed token, address indexed to, uint256 amount, uint256 reserveRetained);
constructor(address pool_, address initialOwner) Ownable(initialOwner) {
if (pool_ == address(0) || initialOwner == address(0)) revert BadParams();
pool = pool_;
}
function flashQuotePush(address asset, uint256 amount, QuotePushParams calldata params) external {
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 quoteSurplusBalance(address quoteToken, uint256 reserveRetained) public view returns (uint256 surplus) {
uint256 quoteBal = IERC20(quoteToken).balanceOf(address(this));
if (quoteBal > reserveRetained) {
surplus = quoteBal - reserveRetained;
}
}
function sweepQuoteSurplus(address quoteToken, address to, uint256 reserveRetained)
external
onlyOwner
returns (uint256 amount)
{
if (quoteToken == address(0) || to == address(0)) revert BadParams();
amount = quoteSurplusBalance(quoteToken, reserveRetained);
if (amount == 0) revert NothingToSweep();
IERC20(quoteToken).safeTransfer(to, amount);
emit SurplusSwept(quoteToken, to, amount, reserveRetained);
}
function sweepToken(address token, address to, uint256 amount) external onlyOwner {
if (token == address(0) || to == address(0) || amount == 0) revert BadParams();
IERC20(token).safeTransfer(to, amount);
emit TokenSwept(token, to, amount);
}
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(
address asset,
uint256 amount,
uint256 premium,
address initiator,
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 (
p.integration == address(0) || p.pmmPool == address(0) || p.baseToken == address(0)
|| p.externalUnwinder == address(0)
) revert BadParams();
if (p.baseToken == asset) revert BadParams();
uint256 baseOut = _swapQuoteForBase(asset, amount, p.integration, p.pmmPool, p.minOutPmm);
uint256 baseBal = IERC20(p.baseToken).balanceOf(address(this));
if (p.atomicBridge.coordinator != address(0)) {
_triggerAtomicBridge(p.baseToken, baseBal, p.atomicBridge);
}
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);
}
function _swapQuoteForBase(address asset, uint256 amount, address integration, address pmmPool, uint256 minOutPmm)
internal
returns (uint256 baseOut)
{
IERC20(asset).forceApprove(integration, amount);
baseOut = IAaveDODOQuotePushSwapExactIn(integration).swapExactIn(pmmPool, asset, amount, minOutPmm);
}
function _unwindBaseIntoQuote(
address baseToken,
address quoteToken,
address externalUnwinder,
uint256 minOutUnwind,
bytes memory unwindData
) internal returns (uint256 unwindOut) {
uint256 remainingBase = IERC20(baseToken).balanceOf(address(this));
IERC20(baseToken).forceApprove(externalUnwinder, remainingBase);
unwindOut =
IAaveExternalUnwinder(externalUnwinder).unwind(baseToken, quoteToken, remainingBase, minOutUnwind, unwindData);
}
function _approveRepayment(address quoteToken, uint256 need) internal returns (uint256 surplus) {
IERC20 quote = IERC20(quoteToken);
uint256 quoteBal = quote.balanceOf(address(this));
if (quoteBal < need) revert InsufficientToRepay();
surplus = quoteBal - need;
quote.forceApprove(pool, need);
}
function _triggerAtomicBridge(address baseToken, uint256 baseBal, AtomicBridgeParams memory atomicBridge) internal {
if (
atomicBridge.destinationAsset == address(0) || atomicBridge.destinationRecipient == address(0)
|| atomicBridge.bridgeAmount == 0 || atomicBridge.bridgeAmount > baseBal
|| atomicBridge.destinationDeadline <= block.timestamp
) revert InvalidAtomicBridge();
address escrowAddress = IAaveAtomicBridgeCoordinator(atomicBridge.coordinator).obligationEscrow();
IERC20(baseToken).forceApprove(escrowAddress, atomicBridge.bridgeAmount);
bytes32 obligationId = IAaveAtomicBridgeCoordinator(atomicBridge.coordinator).createIntent(
IAaveAtomicBridgeCoordinator.CreateIntentParams({
sourceChain: atomicBridge.sourceChain,
destinationChain: atomicBridge.destinationChain,
assetIn: baseToken,
assetOut: atomicBridge.destinationAsset,
amountIn: atomicBridge.bridgeAmount,
minAmountOut: atomicBridge.minDestinationAmount,
recipient: atomicBridge.destinationRecipient,
deadline: atomicBridge.destinationDeadline,
routeId: atomicBridge.routeId
})
);
if (atomicBridge.submitCommitment) {
IAaveAtomicBridgeCoordinator(atomicBridge.coordinator).submitCommitment(
obligationId, atomicBridge.settlementMode
);
}
emit AtomicBridgeTriggered(
obligationId,
atomicBridge.coordinator,
atomicBridge.destinationRecipient,
atomicBridge.bridgeAmount,
atomicBridge.minDestinationAmount
);
}
}