- 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.
160 lines
6.5 KiB
Solidity
160 lines
6.5 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import {Test} from "forge-std/Test.sol";
|
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
import {
|
|
AaveQuotePushFlashReceiver,
|
|
IAaveExternalUnwinder
|
|
} from "../../contracts/flash/AaveQuotePushFlashReceiver.sol";
|
|
|
|
contract AaveForkMockExternalUnwinder is IAaveExternalUnwinder {
|
|
IERC20 public immutable base;
|
|
IERC20 public immutable quote;
|
|
uint256 public immutable numerator;
|
|
uint256 public immutable denominator;
|
|
|
|
constructor(IERC20 base_, IERC20 quote_, uint256 numerator_, uint256 denominator_) {
|
|
base = base_;
|
|
quote = quote_;
|
|
numerator = numerator_;
|
|
denominator = denominator_;
|
|
}
|
|
|
|
function unwind(address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, bytes calldata)
|
|
external
|
|
override
|
|
returns (uint256 amountOut)
|
|
{
|
|
require(tokenIn == address(base), "base only");
|
|
require(tokenOut == address(quote), "quote only");
|
|
IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
|
|
amountOut = amountIn * numerator / denominator;
|
|
require(amountOut >= minAmountOut, "min unwind");
|
|
IERC20(address(quote)).transfer(msg.sender, amountOut);
|
|
}
|
|
}
|
|
|
|
contract AaveQuotePushFlashReceiverMainnetForkTest is Test {
|
|
address constant AAVE_POOL_MAINNET = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2;
|
|
address constant DODO_PMM_INTEGRATION_MAINNET = 0xa9F284eD010f4F7d7F8F201742b49b9f58e29b84;
|
|
address constant POOL_CWUSDC_USDC = 0x69776fc607e9edA8042e320e7e43f54d06c68f0E;
|
|
address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
|
|
address constant CWUSDC = 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a;
|
|
|
|
bool public forkAvailable;
|
|
AaveQuotePushFlashReceiver internal receiver;
|
|
AaveForkMockExternalUnwinder internal unwinder;
|
|
|
|
modifier skipIfNoFork() {
|
|
if (!forkAvailable) {
|
|
return;
|
|
}
|
|
_;
|
|
}
|
|
|
|
function setUp() public {
|
|
string memory rpcUrl = vm.envOr("ETHEREUM_MAINNET_RPC", string(""));
|
|
if (bytes(rpcUrl).length == 0) {
|
|
forkAvailable = false;
|
|
return;
|
|
}
|
|
|
|
try vm.createSelectFork(rpcUrl) {
|
|
forkAvailable = true;
|
|
} catch {
|
|
forkAvailable = false;
|
|
return;
|
|
}
|
|
|
|
receiver = new AaveQuotePushFlashReceiver(AAVE_POOL_MAINNET, address(this));
|
|
unwinder = new AaveForkMockExternalUnwinder(IERC20(CWUSDC), IERC20(USDC), 112, 100);
|
|
// PMM + unwind sizing can require materially more than 100 USDC on a live reserve snapshot.
|
|
deal(USDC, address(unwinder), 50_000_000_000); // 50k USDC (6 decimals) for mock unwind payouts
|
|
}
|
|
|
|
function testFork_aaveQuotePush_usesRealAaveAndRealMainnetPmm() public skipIfNoFork {
|
|
uint256 amount = 2_964_298; // current safe tranche at 120/120
|
|
uint256 receiverQuoteBefore = IERC20(USDC).balanceOf(address(receiver));
|
|
uint256 poolBaseBefore = IERC20(CWUSDC).balanceOf(POOL_CWUSDC_USDC);
|
|
uint256 poolQuoteBefore = IERC20(USDC).balanceOf(POOL_CWUSDC_USDC);
|
|
|
|
AaveQuotePushFlashReceiver.QuotePushParams memory p = AaveQuotePushFlashReceiver.QuotePushParams({
|
|
integration: DODO_PMM_INTEGRATION_MAINNET,
|
|
pmmPool: POOL_CWUSDC_USDC,
|
|
baseToken: CWUSDC,
|
|
externalUnwinder: address(unwinder),
|
|
minOutPmm: 2_800_000,
|
|
minOutUnwind: amount + 1_483, // 5 bps Aave premium
|
|
unwindData: bytes(""),
|
|
atomicBridge: AaveQuotePushFlashReceiver.AtomicBridgeParams({
|
|
coordinator: address(0),
|
|
sourceChain: 0,
|
|
destinationChain: 0,
|
|
destinationAsset: address(0),
|
|
bridgeAmount: 0,
|
|
minDestinationAmount: 0,
|
|
destinationRecipient: address(0),
|
|
destinationDeadline: 0,
|
|
routeId: bytes32(0),
|
|
settlementMode: bytes32(0),
|
|
submitCommitment: false
|
|
})
|
|
});
|
|
|
|
receiver.flashQuotePush(USDC, amount, p);
|
|
|
|
uint256 receiverQuoteAfter = IERC20(USDC).balanceOf(address(receiver));
|
|
uint256 poolBaseAfter = IERC20(CWUSDC).balanceOf(POOL_CWUSDC_USDC);
|
|
uint256 poolQuoteAfter = IERC20(USDC).balanceOf(POOL_CWUSDC_USDC);
|
|
|
|
assertGt(receiverQuoteAfter, receiverQuoteBefore, "receiver retains surplus");
|
|
assertEq(IERC20(CWUSDC).balanceOf(address(receiver)), 0, "base fully unwound");
|
|
assertLt(poolBaseAfter, poolBaseBefore, "pool base decreased via quote push");
|
|
assertGt(poolQuoteAfter, poolQuoteBefore, "pool quote increased via quote push");
|
|
}
|
|
|
|
function testFork_ownerCanSweepRetainedQuoteSurplus() public skipIfNoFork {
|
|
uint256 amount = 2_964_298;
|
|
address recipient = address(0xBEEF);
|
|
|
|
AaveQuotePushFlashReceiver.QuotePushParams memory p = AaveQuotePushFlashReceiver.QuotePushParams({
|
|
integration: DODO_PMM_INTEGRATION_MAINNET,
|
|
pmmPool: POOL_CWUSDC_USDC,
|
|
baseToken: CWUSDC,
|
|
externalUnwinder: address(unwinder),
|
|
minOutPmm: 2_800_000,
|
|
minOutUnwind: amount + 1_483,
|
|
unwindData: bytes(""),
|
|
atomicBridge: AaveQuotePushFlashReceiver.AtomicBridgeParams({
|
|
coordinator: address(0),
|
|
sourceChain: 0,
|
|
destinationChain: 0,
|
|
destinationAsset: address(0),
|
|
bridgeAmount: 0,
|
|
minDestinationAmount: 0,
|
|
destinationRecipient: address(0),
|
|
destinationDeadline: 0,
|
|
routeId: bytes32(0),
|
|
settlementMode: bytes32(0),
|
|
submitCommitment: false
|
|
})
|
|
});
|
|
|
|
receiver.flashQuotePush(USDC, amount, p);
|
|
|
|
uint256 retainedBeforeSweep = IERC20(USDC).balanceOf(address(receiver));
|
|
uint256 reserveRetained = 10_000;
|
|
uint256 expectedSweep = retainedBeforeSweep - reserveRetained;
|
|
|
|
uint256 sweepable = receiver.quoteSurplusBalance(USDC, reserveRetained);
|
|
assertEq(sweepable, expectedSweep, "sweepable balance excludes retained reserve");
|
|
|
|
uint256 recipientBefore = IERC20(USDC).balanceOf(recipient);
|
|
receiver.sweepQuoteSurplus(USDC, recipient, reserveRetained);
|
|
|
|
assertEq(IERC20(USDC).balanceOf(address(receiver)), reserveRetained, "receiver keeps configured reserve");
|
|
assertEq(IERC20(USDC).balanceOf(recipient), recipientBefore + expectedSweep, "recipient receives swept surplus");
|
|
}
|
|
}
|