Files
defiQUG 76aa419320 feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs
- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault.
- Token-aggregation service routes, planner, chain config, relay env templates.
- Config snapshots and multi-chain deployment markdown updates.
- gitignore services/btc-intake/dist/ (tsc output); do not track dist.

Run forge build && forge test before deploy (large solc graph).

Made-with: Cursor
2026-04-07 23:40:52 -07:00

200 lines
7.3 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test, console} from "forge-std/Test.sol";
import {Stabilizer} from "../../../../contracts/bridge/trustless/integration/Stabilizer.sol";
import {PrivatePoolRegistry} from "../../../../contracts/dex/PrivatePoolRegistry.sol";
import {IStablecoinPegManager} from "../../../../contracts/bridge/trustless/integration/IStablecoinPegManager.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract MockStablecoinPegManager is IStablecoinPegManager {
int256 public deviationBps;
function setDeviation(int256 _deviationBps) external {
deviationBps = _deviationBps;
}
function checkUSDpeg(address) external view returns (bool isMaintained, int256 _deviationBps) {
_deviationBps = deviationBps;
isMaintained = deviationBps >= -50 && deviationBps <= 50;
}
function checkETHpeg(address) external view returns (bool, int256) {
return (true, deviationBps);
}
function calculateDeviation(address, uint256, uint256) external pure returns (int256) {
return 0;
}
function getPegStatus(address) external view returns (uint256, uint256, int256, bool) {
return (1e18, 1e18, deviationBps, true);
}
function getSupportedAssets() external pure returns (address[] memory) {
return new address[](0);
}
}
contract MockDODOPool {
address public baseToken;
address public quoteToken;
uint256 public midPrice = 1e18;
uint256 public sellOutAmount = 1e18;
constructor(address _base, address _quote) {
baseToken = _base;
quoteToken = _quote;
}
function _BASE_TOKEN_() external view returns (address) { return baseToken; }
function _QUOTE_TOKEN_() external view returns (address) { return quoteToken; }
function getMidPrice() external view returns (uint256) { return midPrice; }
function sellBase(address) external returns (uint256) {
return sellOutAmount;
}
function sellQuote(address) external returns (uint256) {
return sellOutAmount;
}
function setSellOutAmount(uint256 v) external { sellOutAmount = v; }
function setMidPrice(uint256 v) external { midPrice = v; }
}
contract MockERC20 {
mapping(address => uint256) public balanceOf;
function mint(address to, uint256 amount) external {
balanceOf[to] += amount;
}
function transfer(address to, uint256 amount) external returns (bool) {
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
return true;
}
function approve(address, uint256) external pure returns (bool) { return true; }
}
contract StabilizerTest is Test {
Stabilizer public stabilizer;
PrivatePoolRegistry public registry;
MockStablecoinPegManager public pegManager;
MockDODOPool public mockPool;
MockERC20 public tokenIn;
MockERC20 public tokenOut;
address public admin = address(0x1);
address public keeper = address(0x2);
function setUp() public {
registry = new PrivatePoolRegistry(admin);
stabilizer = new Stabilizer(admin, address(registry));
pegManager = new MockStablecoinPegManager();
tokenIn = new MockERC20();
tokenOut = new MockERC20();
tokenIn.mint(address(stabilizer), 1000e18);
mockPool = new MockDODOPool(address(tokenIn), address(tokenOut));
vm.startPrank(admin);
stabilizer.grantRole(stabilizer.STABILIZER_KEEPER_ROLE(), keeper);
stabilizer.setStablecoinPegSource(address(pegManager), address(tokenIn));
stabilizer.setMinBlocksBetweenExecution(3);
stabilizer.setMaxStabilizationVolumePerBlock(100e18);
stabilizer.setThresholdBps(50);
stabilizer.setSustainedDeviationBlocks(3);
stabilizer.setMaxSlippageBps(100);
registry.register(address(tokenIn), address(tokenOut), address(mockPool));
vm.stopPrank();
}
function test_checkDeviation_belowThreshold_returnsNoRebalance() public {
pegManager.setDeviation(30); // 0.3% < 50 bps
(int256 dev, bool shouldRebalance) = stabilizer.checkDeviation();
assertEq(dev, 30);
assertFalse(shouldRebalance);
}
function test_checkDeviation_aboveThreshold_singleBlock_noSustained_returnsNoRebalance() public {
pegManager.setDeviation(100);
(int256 dev, bool shouldRebalance) = stabilizer.checkDeviation();
assertEq(dev, 100);
assertFalse(shouldRebalance); // no samples yet
}
function test_checkDeviation_aboveThreshold_sustained_returnsRebalance() public {
pegManager.setDeviation(100);
vm.prank(keeper);
stabilizer.recordDeviation();
vm.roll(block.number + 1);
stabilizer.recordDeviation();
vm.roll(block.number + 1);
stabilizer.recordDeviation();
(int256 dev, bool shouldRebalance) = stabilizer.checkDeviation();
assertEq(dev, 100);
assertTrue(shouldRebalance);
}
function test_executePrivateSwap_revertWhenBlockDelayNotMet() public {
pegManager.setDeviation(100);
for (uint256 i = 0; i < 3; i++) {
stabilizer.recordDeviation();
vm.roll(block.number + 1);
}
vm.roll(2); // block 2 < lastExecutionBlock(0) + 3
vm.prank(keeper);
vm.expectRevert(Stabilizer.BlockDelayNotMet.selector);
stabilizer.executePrivateSwap(10e18, address(tokenIn), address(tokenOut));
}
function test_executePrivateSwap_revertWhenVolumeCapExceeded() public {
pegManager.setDeviation(100);
for (uint256 i = 0; i < 4; i++) {
stabilizer.recordDeviation();
vm.roll(block.number + 1);
}
vm.roll(block.number + 10);
vm.prank(keeper);
vm.expectRevert(Stabilizer.VolumeCapExceeded.selector);
stabilizer.executePrivateSwap(200e18, address(tokenIn), address(tokenOut));
}
function test_executePrivateSwap_revertWhenSlippageExceeded() public {
pegManager.setDeviation(100);
for (uint256 i = 0; i < 4; i++) {
stabilizer.recordDeviation();
vm.roll(block.number + 1);
}
vm.roll(block.number + 10);
mockPool.setSellOutAmount(1); // very low output => slippage
vm.prank(keeper);
vm.expectRevert(Stabilizer.SlippageExceeded.selector);
stabilizer.executePrivateSwap(10e18, address(tokenIn), address(tokenOut));
}
function test_executePrivateSwap_revertWhenNoPrivatePool() public {
vm.startPrank(admin);
MockERC20 otherIn = new MockERC20();
MockERC20 otherOut = new MockERC20();
otherIn.mint(address(stabilizer), 1000e18);
vm.stopPrank();
pegManager.setDeviation(100);
for (uint256 i = 0; i < 4; i++) {
stabilizer.recordDeviation();
vm.roll(block.number + 1);
}
vm.roll(block.number + 10);
vm.prank(keeper);
vm.expectRevert(Stabilizer.NoPrivatePool.selector);
stabilizer.executePrivateSwap(10e18, address(otherIn), address(otherOut));
}
function test_executePrivateSwap_revertWhenShouldNotRebalance() public {
pegManager.setDeviation(30); // below threshold
vm.roll(block.number + 10);
vm.prank(keeper);
vm.expectRevert(Stabilizer.ShouldNotRebalance.selector);
stabilizer.executePrivateSwap(10e18, address(tokenIn), address(tokenOut));
}
}