// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {Test} from "forge-std/Test.sol"; import {PluginOracleAdapter} from "../src/PluginOracleAdapter.sol"; import {ERC4626RateGuard, IERC4626Rate} from "../src/ERC4626RateGuard.sol"; import {IPluginFeed} from "../src/interfaces/IPluginFeed.sol"; import {MockPluginFeed, MockVault} from "./mocks/Mocks.sol"; contract ERC4626RateGuardTest is Test { MockPluginFeed feed; MockVault vault; ERC4626RateGuard guard; address keeper = address(0x4EE9); function setUp() public { vm.warp(1_000_000); feed = new MockPluginFeed(16); feed.set(1e16, block.timestamp); // asset = 1.00 (16 dp) PluginOracleAdapter a = new PluginOracleAdapter(IPluginFeed(address(feed)), 1 days, 0, 0); vault = new MockVault(1.05e6); // 1 share = 1.05 asset (6-dp asset) // bounds 1.0..1.5, max 1% deviation for reads, max 0.5% ref move per update guard = new ERC4626RateGuard(IERC4626Rate(address(vault)), a, 6, 1e6, 1.5e6, 100, 50, address(this)); guard.setKeeper(keeper); } function test_GuardedPrice() public view { assertEq(guard.guardedPrice(), 1.05e16); } function test_RevertDeviation() public { vault.setRate(1.07e6); // +1.9% vm.expectRevert(); guard.guardedPrice(); } function test_RevertOutOfBounds() public { vault.setRate(0.9e6); vm.expectRevert(abi.encodeWithSelector(ERC4626RateGuard.RateOutOfBounds.selector, uint256(0.9e6))); guard.guardedRate(); } function test_UpdateNotPermissionless() public { vm.prank(address(0xBAD)); vm.expectRevert(abi.encodeWithSelector(ERC4626RateGuard.NotKeeper.selector, address(0xBAD))); guard.update(); } function test_UpdateCappedPerStep() public { vault.setRate(1.07e6); // live +1.9% vm.prank(keeper); assertEq(guard.update(), 1.05e6 + 5250); // +0.5% cap guard.update(); // owner may also update assertEq(guard.refRate(), 1.05525e6 + 5276); assertEq(guard.guardedRate(), 1.07e6); // now within 1% of ref } function test_UpdateSnapsWhenWithinStep() public { vault.setRate(1.052e6); vm.prank(keeper); assertEq(guard.update(), 1.052e6); } }