// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import { Test } from "forge-std/Test.sol"; import { GenericStateChannelManager } from "../../contracts/channels/GenericStateChannelManager.sol"; import { IGenericStateChannelManager } from "../../contracts/channels/IGenericStateChannelManager.sol"; contract GenericStateChannelManagerTest is Test { GenericStateChannelManager public manager; address public admin; address public alice; address public bob; uint256 public alicePk; uint256 public bobPk; uint256 constant CHALLENGE_WINDOW = 1 hours; function _signState( uint256 channelId, bytes32 stateHash, uint256 nonce, uint256 balanceA, uint256 balanceB, uint256 pk ) internal pure returns (uint8 v, bytes32 r, bytes32 s) { bytes32 stateHashInner = keccak256(abi.encodePacked(channelId, stateHash, nonce, balanceA, balanceB)); bytes32 ethSigned = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", stateHashInner)); (v, r, s) = vm.sign(pk, ethSigned); } function setUp() public { admin = address(0x1); (alice, alicePk) = makeAddrAndKey("alice"); (bob, bobPk) = makeAddrAndKey("bob"); manager = new GenericStateChannelManager(admin, CHALLENGE_WINDOW); vm.deal(alice, 100 ether); vm.deal(bob, 100 ether); } function test_OpenFundCloseCooperative_WithStateHash() public { vm.prank(alice); uint256 channelId = manager.openChannel{ value: 10 ether }(bob); vm.prank(bob); manager.fundChannel{ value: 10 ether }(channelId); bytes32 stateHash = keccak256("game-result-v1"); uint256 balanceA = 12 ether; uint256 balanceB = 8 ether; uint256 nonce = 1; (uint8 vA, bytes32 rA, bytes32 sA) = _signState(channelId, stateHash, nonce, balanceA, balanceB, alicePk); (uint8 vB, bytes32 rB, bytes32 sB) = _signState(channelId, stateHash, nonce, balanceA, balanceB, bobPk); uint256 aliceBefore = alice.balance; uint256 bobBefore = bob.balance; vm.prank(alice); manager.closeChannelCooperative(channelId, stateHash, nonce, balanceA, balanceB, vA, rA, sA, vB, rB, sB); assertEq(alice.balance, aliceBefore + balanceA); assertEq(bob.balance, bobBefore + balanceB); IGenericStateChannelManager.Channel memory ch = manager.getChannel(channelId); assertEq(uint256(ch.status), uint256(IGenericStateChannelManager.ChannelStatus.Closed)); } function test_SubmitCloseAndFinalize_WithStateHash() public { vm.prank(alice); uint256 channelId = manager.openChannel{ value: 10 ether }(bob); vm.prank(bob); manager.fundChannel{ value: 10 ether }(channelId); bytes32 stateHash = keccak256("attestation"); uint256 balanceA = 5 ether; uint256 balanceB = 15 ether; uint256 nonce = 1; (uint8 vA, bytes32 rA, bytes32 sA) = _signState(channelId, stateHash, nonce, balanceA, balanceB, alicePk); (uint8 vB, bytes32 rB, bytes32 sB) = _signState(channelId, stateHash, nonce, balanceA, balanceB, bobPk); vm.prank(bob); manager.submitClose(channelId, stateHash, nonce, balanceA, balanceB, vA, rA, sA, vB, rB, sB); IGenericStateChannelManager.Channel memory ch = manager.getChannel(channelId); assertEq(ch.disputeStateHash, stateHash); vm.warp(block.timestamp + CHALLENGE_WINDOW + 1); manager.finalizeClose(channelId); assertEq(uint256(manager.getChannel(channelId).status), uint256(IGenericStateChannelManager.ChannelStatus.Closed)); } }