// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "forge-std/Test.sol"; import "../../../contracts/bridge/trustless/BondManager.sol"; contract BondManagerTest is Test { BondManager public bondManager; uint256 constant BOND_MULTIPLIER = 11000; // 110% uint256 constant MIN_BOND = 1 ether; address public relayer1 = address(0x1); address public relayer2 = address(0x2); address public challenger = address(0x3); event BondPosted(uint256 indexed depositId, address indexed relayer, uint256 bondAmount); event BondSlashed( uint256 indexed depositId, address indexed relayer, address indexed challenger, uint256 bondAmount, uint256 challengerReward, uint256 burnedAmount ); event BondReleased(uint256 indexed depositId, address indexed relayer, uint256 bondAmount); function setUp() public { bondManager = new BondManager(BOND_MULTIPLIER, MIN_BOND); vm.deal(relayer1, 100 ether); vm.deal(relayer2, 100 ether); vm.deal(challenger, 100 ether); } function testPostBond() public { uint256 depositId = 1; uint256 depositAmount = 10 ether; uint256 requiredBond = bondManager.getRequiredBond(depositAmount); vm.prank(relayer1); vm.expectEmit(true, true, false, true); emit BondPosted(depositId, relayer1, requiredBond); bondManager.postBond{value: requiredBond}(depositId, depositAmount, relayer1); (address relayer, uint256 amount, bool slashed, bool released) = bondManager.getBond(depositId); assertEq(relayer, relayer1); assertEq(amount, requiredBond); assertFalse(slashed); assertFalse(released); assertEq(bondManager.getTotalBonds(relayer1), requiredBond); } function testPostBondMinimumBond() public { uint256 depositId = 2; uint256 depositAmount = 0.5 ether; // Less than min bond uint256 requiredBond = bondManager.getRequiredBond(depositAmount); assertEq(requiredBond, MIN_BOND); // Should use minimum bond vm.prank(relayer1); bondManager.postBond{value: MIN_BOND}(depositId, depositAmount, relayer1); } function testPostBondInsufficientBond() public { uint256 depositId = 3; uint256 depositAmount = 10 ether; uint256 requiredBond = bondManager.getRequiredBond(depositAmount); vm.prank(relayer1); vm.expectRevert(BondManager.InsufficientBond.selector); bondManager.postBond{value: requiredBond - 1}(depositId, depositAmount, relayer1); } function testSlashBond() public { uint256 depositId = 4; uint256 depositAmount = 10 ether; uint256 requiredBond = bondManager.getRequiredBond(depositAmount); vm.prank(relayer1); bondManager.postBond{value: requiredBond}(depositId, depositAmount, relayer1); uint256 challengerBalanceBefore = challenger.balance; vm.prank(address(this)); // This contract calls slashBond (in practice, ChallengeManager would call it) vm.expectEmit(true, true, true, true); emit BondSlashed(depositId, relayer1, challenger, requiredBond, requiredBond / 2, requiredBond - requiredBond / 2); bondManager.slashBond(depositId, challenger); uint256 challengerReward = requiredBond / 2; assertEq(challenger.balance - challengerBalanceBefore, challengerReward); assertEq(bondManager.getTotalBonds(relayer1), 0); (address relayer, uint256 amount, bool slashed, bool released) = bondManager.getBond(depositId); assertTrue(slashed); assertFalse(released); } function testReleaseBond() public { uint256 depositId = 5; uint256 depositAmount = 10 ether; uint256 requiredBond = bondManager.getRequiredBond(depositAmount); vm.prank(relayer1); bondManager.postBond{value: requiredBond}(depositId, depositAmount, relayer1); uint256 relayerBalanceBefore = relayer1.balance; vm.prank(address(this)); // This contract calls releaseBond (in practice, InboxETH would call it) vm.expectEmit(true, true, false, true); emit BondReleased(depositId, relayer1, requiredBond); bondManager.releaseBond(depositId); assertEq(relayer1.balance - relayerBalanceBefore, requiredBond); assertEq(bondManager.getTotalBonds(relayer1), 0); (address relayer, uint256 amount, bool slashed, bool released) = bondManager.getBond(depositId); assertFalse(slashed); assertTrue(released); } function testGetRequiredBond() public { // Test with deposit amount that requires multiplier uint256 depositAmount = 10 ether; uint256 expectedBond = (depositAmount * BOND_MULTIPLIER) / 10000; assertEq(bondManager.getRequiredBond(depositAmount), expectedBond); // Test with deposit amount below minimum depositAmount = 0.5 ether; assertEq(bondManager.getRequiredBond(depositAmount), MIN_BOND); } function testSlashBondNotFound() public { vm.expectRevert(BondManager.BondNotFound.selector); bondManager.slashBond(999, challenger); } function testReleaseBondNotFound() public { vm.expectRevert(BondManager.BondNotFound.selector); bondManager.releaseBond(999); } function testSlashBondAlreadySlashed() public { uint256 depositId = 6; uint256 depositAmount = 10 ether; uint256 requiredBond = bondManager.getRequiredBond(depositAmount); vm.prank(relayer1); bondManager.postBond{value: requiredBond}(depositId, depositAmount, relayer1); bondManager.slashBond(depositId, challenger); vm.expectRevert(BondManager.BondAlreadySlashed.selector); bondManager.slashBond(depositId, challenger); } function testReleaseBondAlreadyReleased() public { uint256 depositId = 7; uint256 depositAmount = 10 ether; uint256 requiredBond = bondManager.getRequiredBond(depositAmount); vm.prank(relayer1); bondManager.postBond{value: requiredBond}(depositId, depositAmount, relayer1); bondManager.releaseBond(depositId); vm.expectRevert(BondManager.BondAlreadyReleased.selector); bondManager.releaseBond(depositId); } }