// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {Test, console} from "forge-std/Test.sol"; import {TreasuryVault} from "../../contracts/treasury/TreasuryVault.sol"; import {CcipBridgeAdapter138} from "../../contracts/treasury/CcipBridgeAdapter138.sol"; import {StrategyExecutor138} from "../../contracts/treasury/StrategyExecutor138.sol"; contract MockWETH { 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 transferFrom(address from, address to, uint256 amount) external returns (bool) { balanceOf[from] -= amount; balanceOf[to] += amount; return true; } function approve(address, uint256) external pure returns (bool) { return true; } } contract MockBridge { function sendCrossChain(uint64, address, uint256) external payable returns (bytes32) { return keccak256("ok"); } } contract StrategyExecutor138Test is Test { TreasuryVault public vault; MockBridge public bridge; CcipBridgeAdapter138 public adapter; StrategyExecutor138 public executor; MockWETH public weth9; address public admin; address public keeper; address public receiverMainnet = address(0x99); function setUp() public { admin = address(1); keeper = address(2); weth9 = new MockWETH(); vault = new TreasuryVault(admin); bridge = new MockBridge(); adapter = new CcipBridgeAdapter138( address(weth9), address(bridge), receiverMainnet, admin ); executor = new StrategyExecutor138(address(vault), address(adapter), admin); vm.startPrank(admin); adapter.setStrategyExecutor(address(executor)); vault.setModule(address(executor), true); vault.setModule(address(adapter), true); vault.setToken(address(weth9), true); executor.setToken(address(weth9), true); executor.grantRole(executor.KEEPER_ROLE(), keeper); adapter.setExportsEnabled(true); vm.stopPrank(); weth9.mint(address(vault), 100e18); } function test_exportToMainnet_revert_tokenNotApproved() public { MockWETH other = new MockWETH(); other.mint(address(vault), 50e18); vm.prank(admin); vault.setToken(address(other), true); vm.prank(keeper); vm.expectRevert(StrategyExecutor138.TokenNotApproved.selector); executor.exportToMainnet(address(other), 50e18, block.timestamp + 3600); } function test_exportToMainnet_revert_cooldown() public { vm.prank(admin); executor.setCooldownBlocks(1); vm.prank(keeper); executor.exportToMainnet(address(weth9), 10e18, block.timestamp + 3600); vm.prank(keeper); vm.expectRevert(StrategyExecutor138.CooldownNotElapsed.selector); executor.exportToMainnet(address(weth9), 10e18, block.timestamp + 3600); } function test_setExportPolicy_success() public { StrategyExecutor138.ExportPolicy memory policy = StrategyExecutor138.ExportPolicy({ mode: StrategyExecutor138.ExportMode.Threshold, minExportUsd: 10_000e6, maxPerTxUsd: 50_000e6, maxDailyUsd: 100_000e6, rateLimitPerHour: 5, cooldownBlocks: 10, exportAsset: address(weth9), destinationSelector: 5009297550715157269, destinationReceiver: receiverMainnet }); vm.prank(admin); executor.setExportPolicy(policy); (StrategyExecutor138.ExportMode mode, uint256 minExportUsd,,,, uint256 cooldownBlocks,,,) = executor.exportPolicy(); assertEq(uint8(mode), uint8(StrategyExecutor138.ExportMode.Threshold)); assertEq(minExportUsd, 10_000e6); assertEq(cooldownBlocks, 10); assertEq(executor.cooldownBlocks(), 10); } function test_recordExportIntent_success() public { vm.prank(admin); adapter.setExportsEnabled(false); vm.prank(keeper); executor.recordExportIntent(address(weth9), 5e18); assertEq(executor.pendingIntentToken(), address(weth9)); assertEq(executor.pendingIntentAmount(), 5e18); } function test_recordExportIntent_revert_exportsEnabled() public { vm.prank(admin); adapter.setExportsEnabled(true); vm.prank(keeper); vm.expectRevert(StrategyExecutor138.ExportsNotEnabled.selector); executor.recordExportIntent(address(weth9), 5e18); } function test_recordExportIntent_revert_tokenNotApproved() public { MockWETH other = new MockWETH(); vm.prank(admin); adapter.setExportsEnabled(false); vm.prank(keeper); vm.expectRevert(StrategyExecutor138.TokenNotApproved.selector); executor.recordExportIntent(address(other), 1e18); } function test_processPendingIntent_revert_noIntent() public { vm.prank(keeper); vm.expectRevert(StrategyExecutor138.NoPendingIntent.selector); executor.processPendingIntent{value: 0}(block.timestamp + 3600); } function test_processPendingIntent_revert_exportsDisabled() public { vm.prank(admin); adapter.setExportsEnabled(false); vm.prank(keeper); executor.recordExportIntent(address(weth9), 5e18); vm.prank(admin); adapter.setExportsEnabled(false); vm.prank(keeper); vm.expectRevert(StrategyExecutor138.ExportsNotEnabled.selector); executor.processPendingIntent{value: 0}(block.timestamp + 3600); } function test_processPendingIntent_success() public { vm.prank(admin); adapter.setExportsEnabled(false); vm.prank(keeper); executor.recordExportIntent(address(weth9), 5e18); vm.prank(admin); adapter.setExportsEnabled(true); uint256 vaultBefore = weth9.balanceOf(address(vault)); vm.prank(keeper); executor.processPendingIntent{value: 0}(block.timestamp + 3600); assertEq(executor.pendingIntentToken(), address(0)); assertEq(executor.pendingIntentAmount(), 0); assertEq(weth9.balanceOf(address(vault)), vaultBefore - 5e18); } function test_harvestFees_revert_notImplemented() public { vm.prank(keeper); vm.expectRevert(StrategyExecutor138.NotImplemented.selector); executor.harvestFees(); } function test_rebalanceLp_revert_notImplemented() public { vm.prank(keeper); vm.expectRevert(StrategyExecutor138.NotImplemented.selector); executor.rebalanceLp(); } }