Add Aave quote-push collateral supply and toggle hooks.
Some checks failed
CI/CD Pipeline / Security Scanning (push) Has been cancelled
CI/CD Pipeline / Solidity Contracts (push) Has been cancelled
CI/CD Pipeline / Lint and Format (push) Has been cancelled
CI/CD Pipeline / Terraform Validation (push) Has been cancelled
CI/CD Pipeline / Kubernetes Validation (push) Has been cancelled
Validation / validate-genesis (push) Has started running
Validation / validate-terraform (push) Has been cancelled
Validation / validate-kubernetes (push) Has been cancelled
Validation / validate-smart-contracts (push) Has been cancelled
Validation / validate-security (push) Has been cancelled
Validation / validate-documentation (push) Has been cancelled

Extends AaveQuotePushFlashReceiver with before/after swap collateral steps, env-driven run scripts, forkproof parity, and scoped forge tests for supply/toggle callback ordering.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-06-26 02:07:30 -07:00
parent aa5790bb8b
commit 848a5e35ea
12 changed files with 486 additions and 11 deletions

View File

@@ -26,6 +26,10 @@ interface IAavePoolLike {
bytes calldata params,
uint16 referralCode
) external;
function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;
function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external;
}
/// @dev Callback for `flashLoan` (multi-asset API).
@@ -84,13 +88,39 @@ interface IAaveAtomicBridgeCoordinator {
/**
* @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.
* flash borrow quote (`flashLoan` single-asset) -> optional Aave collateral supply/toggle hooks ->
* buy PMM base -> optional atomic bridge -> unwind base externally -> repay lender, retaining any surplus.
* @dev Collateral hooks apply to **this contract's** Aave position (`onBehalfOf` = address(this)). Pre-fund the
* receiver with collateral tokens before `supplyBeforeSwap` steps, or supply from flash proceeds in later txs.
*/
contract AaveQuotePushFlashReceiver is IAaveFlashLoanSimpleReceiver, IAaveFlashLoanReceiver, Ownable {
using SafeERC20 for IERC20;
address public immutable pool;
enum CollateralHook {
BeforeSwap,
AfterSwap,
BeforeUnwind
}
struct CollateralSupplyStep {
address asset;
uint256 amount;
}
struct CollateralToggleStep {
address asset;
bool useAsCollateral;
}
struct CollateralParams {
CollateralSupplyStep[] supplyBeforeSwap;
CollateralToggleStep[] toggleBeforeSwap;
CollateralToggleStep[] toggleAfterSwap;
CollateralToggleStep[] toggleBeforeUnwind;
}
struct QuotePushParams {
address integration;
address pmmPool;
@@ -100,6 +130,7 @@ contract AaveQuotePushFlashReceiver is IAaveFlashLoanSimpleReceiver, IAaveFlashL
uint256 minOutUnwind;
bytes unwindData;
AtomicBridgeParams atomicBridge;
CollateralParams collateral;
}
struct AtomicBridgeParams {
@@ -141,6 +172,18 @@ contract AaveQuotePushFlashReceiver is IAaveFlashLoanSimpleReceiver, IAaveFlashL
);
event TokenSwept(address indexed token, address indexed to, uint256 amount);
event SurplusSwept(address indexed token, address indexed to, uint256 amount, uint256 reserveRetained);
event CollateralSupplied(address indexed asset, uint256 amount);
event CollateralToggled(address indexed asset, bool useAsCollateral, CollateralHook hook);
/// @notice Empty `collateral` block for quote-push scripts that do not use Aave position hooks.
function emptyCollateralParams() external pure returns (CollateralParams memory params) {
return params;
}
/// @notice Non-zero when this deployment supports `QuotePushParams.collateral` atomic hooks.
function collateralHooksVersion() external pure returns (uint256) {
return 1;
}
constructor(address pool_, address initialOwner) Ownable(initialOwner) {
if (pool_ == address(0) || initialOwner == address(0)) revert BadParams();
@@ -225,13 +268,20 @@ contract AaveQuotePushFlashReceiver is IAaveFlashLoanSimpleReceiver, IAaveFlashL
) revert BadParams();
if (p.baseToken == asset) revert BadParams();
_applyCollateralSupplies(p.collateral.supplyBeforeSwap);
_applyCollateralToggles(p.collateral.toggleBeforeSwap, CollateralHook.BeforeSwap);
uint256 baseOut = _swapQuoteForBase(asset, amount, p.integration, p.pmmPool, p.minOutPmm);
_applyCollateralToggles(p.collateral.toggleAfterSwap, CollateralHook.AfterSwap);
uint256 baseBal = IERC20(p.baseToken).balanceOf(address(this));
if (p.atomicBridge.coordinator != address(0)) {
_triggerAtomicBridge(p.baseToken, baseBal, p.atomicBridge);
}
_applyCollateralToggles(p.collateral.toggleBeforeUnwind, CollateralHook.BeforeUnwind);
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);
@@ -258,6 +308,27 @@ contract AaveQuotePushFlashReceiver is IAaveFlashLoanSimpleReceiver, IAaveFlashL
IAaveExternalUnwinder(externalUnwinder).unwind(baseToken, quoteToken, remainingBase, minOutUnwind, unwindData);
}
function _applyCollateralSupplies(CollateralSupplyStep[] memory steps) internal {
uint256 n = steps.length;
for (uint256 i = 0; i < n; ++i) {
CollateralSupplyStep memory step = steps[i];
if (step.asset == address(0) || step.amount == 0) continue;
IERC20(step.asset).forceApprove(pool, step.amount);
IAavePoolLike(pool).supply(step.asset, step.amount, address(this), 0);
emit CollateralSupplied(step.asset, step.amount);
}
}
function _applyCollateralToggles(CollateralToggleStep[] memory steps, CollateralHook hook) internal {
uint256 n = steps.length;
for (uint256 i = 0; i < n; ++i) {
CollateralToggleStep memory step = steps[i];
if (step.asset == address(0)) continue;
IAavePoolLike(pool).setUserUseReserveAsCollateral(step.asset, step.useAsCollateral);
emit CollateralToggled(step.asset, step.useAsCollateral, hook);
}
}
function _approveRepayment(address quoteToken, uint256 need) internal returns (uint256 surplus) {
IERC20 quote = IERC20(quoteToken);
uint256 quoteBal = quote.balanceOf(address(this));