// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {Chain138LPLocker} from "../../contracts/wrapped-lp-public/Chain138LPLocker.sol"; import {WLPReceiptToken} from "../../contracts/wrapped-lp-public/WLPReceiptToken.sol"; import {PublicChainMintController} from "../../contracts/wrapped-lp-public/PublicChainMintController.sol"; import {WLPRedemptionGateway} from "../../contracts/wrapped-lp-public/WLPRedemptionGateway.sol"; import {WrappedLPNAVVault} from "../../contracts/wrapped-lp-public/WrappedLPNAVVault.sol"; import {WLPNAVOracle} from "../../contracts/wrapped-lp-public/WLPNAVOracle.sol"; contract MockERC20 is ERC20 { constructor(string memory n, string memory s) ERC20(n, s) {} function mint(address to, uint256 a) external { _mint(to, a); } } contract WrappedLPProgramTest is Test { MockERC20 lp; Chain138LPLocker locker; WLPReceiptToken wlp; PublicChainMintController mintCtl; WLPRedemptionGateway gateway; address admin = address(this); address relayer = address(0xBEEF); address user = address(0xA11CE); function setUp() public { lp = new MockERC20("DODO LP", "DLP"); locker = new Chain138LPLocker(address(lp), admin); wlp = new WLPReceiptToken("Wrapped LP", "wLP", 18, admin); mintCtl = new PublicChainMintController(address(wlp), address(locker), admin); gateway = new WLPRedemptionGateway(address(wlp), admin); wlp.grantRole(wlp.MINTER_ROLE(), address(mintCtl)); wlp.grantRole(wlp.BURNER_ROLE(), address(gateway)); mintCtl.grantRole(mintCtl.RELAYER_ROLE(), relayer); locker.grantRole(locker.BRIDGE_RELEASE_ROLE(), relayer); } function test_lock_mint_release_invariant() public { uint256 amt = 1000e18; lp.mint(user, amt); vm.startPrank(user); lp.approve(address(locker), amt); bytes32 lockRef = locker.deposit(amt); vm.stopPrank(); assertEq(locker.totalEscrowed(), amt); vm.prank(relayer); mintCtl.mintForLock(lockRef, user, amt); assertEq(wlp.balanceOf(user), amt); vm.expectRevert(); vm.prank(relayer); mintCtl.mintForLock(lockRef, user, amt); vm.prank(user); gateway.requestRedeem(amt); assertEq(wlp.balanceOf(user), 0); vm.prank(relayer); locker.releaseAmount(user, amt); assertEq(locker.totalEscrowed(), 0); assertEq(lp.balanceOf(user), amt); } function test_nav_vault_cap() public { MockERC20 usdc = new MockERC20("USDC", "USDC"); WrappedLPNAVVault vault = new WrappedLPNAVVault( usdc, "Wrapped LP NAV Vault", "wLPV", admin ); vault.setDepositCap(1_000_000e18); usdc.mint(user, 2_000_000e18); vm.startPrank(user); usdc.approve(address(vault), type(uint256).max); vm.expectRevert(); vault.deposit(1_500_000e18, user); vm.stopPrank(); } function test_nav_oracle_stale() public { WLPNAVOracle oracle = new WLPNAVOracle(admin, 3600); oracle.grantRole(oracle.KEEPER_ROLE(), relayer); vm.prank(relayer); oracle.submitAnswer(1e8); assertFalse(oracle.isStale()); vm.warp(block.timestamp + 4000); assertTrue(oracle.isStale()); } /// @notice Fuzz: distinct deposits yield distinct lockRefs and independent mints. function testFuzz_lockRef_unique(uint128 amtA, uint128 amtB) public { vm.assume(amtA > 0 && amtB > 0); vm.assume(amtA != amtB); uint256 a = uint256(amtA) * 1e10; uint256 b = uint256(amtB) * 1e10; lp.mint(user, a + b); vm.startPrank(user); lp.approve(address(locker), a + b); bytes32 rA = locker.deposit(a); bytes32 rB = locker.deposit(b); vm.stopPrank(); assertTrue(rA != rB); vm.startPrank(relayer); mintCtl.mintForLock(rA, user, a); mintCtl.mintForLock(rB, user, b); vm.stopPrank(); assertEq(wlp.balanceOf(user), a + b); } }