227 lines
8.7 KiB
Solidity
227 lines
8.7 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.19;
|
|
|
|
import {Test} from "forge-std/Test.sol";
|
|
import {PaymentChannelManager} from "../../contracts/channels/PaymentChannelManager.sol";
|
|
import {IPaymentChannelManager} from "../../contracts/channels/IPaymentChannelManager.sol";
|
|
|
|
contract PaymentChannelManagerTest is Test {
|
|
PaymentChannelManager public manager;
|
|
|
|
address public admin;
|
|
address public alice;
|
|
address public bob;
|
|
uint256 public alicePk;
|
|
uint256 public bobPk;
|
|
|
|
uint256 constant CHALLENGE_WINDOW = 1 hours;
|
|
|
|
function setUp() public {
|
|
admin = address(0x1);
|
|
(alice, alicePk) = makeAddrAndKey("alice");
|
|
(bob, bobPk) = makeAddrAndKey("bob");
|
|
manager = new PaymentChannelManager(admin, CHALLENGE_WINDOW);
|
|
vm.deal(alice, 100 ether);
|
|
vm.deal(bob, 100 ether);
|
|
}
|
|
|
|
function _signState(
|
|
uint256 channelId,
|
|
uint256 nonce,
|
|
uint256 balanceA,
|
|
uint256 balanceB,
|
|
uint256 pk
|
|
) internal view returns (uint8 v, bytes32 r, bytes32 s) {
|
|
bytes32 stateHash = keccak256(abi.encodePacked(channelId, nonce, balanceA, balanceB));
|
|
bytes32 ethSigned = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", stateHash));
|
|
(v, r, s) = vm.sign(pk, ethSigned);
|
|
}
|
|
|
|
function test_openChannel() public {
|
|
vm.prank(alice);
|
|
uint256 channelId = manager.openChannel{value: 10 ether}(bob);
|
|
assertEq(channelId, 0);
|
|
|
|
IPaymentChannelManager.Channel memory ch = manager.getChannel(channelId);
|
|
assertEq(ch.participantA, alice);
|
|
assertEq(ch.participantB, bob);
|
|
assertEq(ch.depositA, 10 ether);
|
|
assertEq(ch.depositB, 0);
|
|
assertEq(uint256(ch.status), uint256(IPaymentChannelManager.ChannelStatus.Open));
|
|
assertEq(address(manager).balance, 10 ether);
|
|
}
|
|
|
|
function test_openChannel_revertsZeroParticipant() public {
|
|
vm.prank(alice);
|
|
vm.expectRevert("zero participant");
|
|
manager.openChannel(address(0));
|
|
}
|
|
|
|
function test_openChannel_revertsZeroDeposit() public {
|
|
vm.prank(alice);
|
|
vm.expectRevert("zero deposit");
|
|
manager.openChannel(bob);
|
|
}
|
|
|
|
function test_fundChannel() public {
|
|
vm.prank(alice);
|
|
uint256 channelId = manager.openChannel{value: 10 ether}(bob);
|
|
vm.prank(bob);
|
|
manager.fundChannel{value: 10 ether}(channelId);
|
|
IPaymentChannelManager.Channel memory ch = manager.getChannel(channelId);
|
|
assertEq(ch.depositA, 10 ether);
|
|
assertEq(ch.depositB, 10 ether);
|
|
assertEq(address(manager).balance, 20 ether);
|
|
}
|
|
|
|
function test_closeChannelCooperative() public {
|
|
vm.prank(alice);
|
|
uint256 channelId = manager.openChannel{value: 10 ether}(bob);
|
|
vm.prank(bob);
|
|
manager.fundChannel{value: 10 ether}(channelId);
|
|
|
|
uint256 balanceA = 15 ether;
|
|
uint256 balanceB = 5 ether;
|
|
uint256 nonce = 1;
|
|
|
|
(uint8 vA, bytes32 rA, bytes32 sA) = _signState(channelId, nonce, balanceA, balanceB, alicePk);
|
|
(uint8 vB, bytes32 rB, bytes32 sB) = _signState(channelId, nonce, balanceA, balanceB, bobPk);
|
|
|
|
uint256 aliceBefore = alice.balance;
|
|
uint256 bobBefore = bob.balance;
|
|
|
|
vm.prank(alice);
|
|
manager.closeChannelCooperative(channelId, nonce, balanceA, balanceB, vA, rA, sA, vB, rB, sB);
|
|
|
|
assertEq(alice.balance, aliceBefore + balanceA);
|
|
assertEq(bob.balance, bobBefore + balanceB);
|
|
assertEq(uint256(manager.getChannel(channelId).status), uint256(IPaymentChannelManager.ChannelStatus.Closed));
|
|
}
|
|
|
|
function test_submitCloseAndFinalize() public {
|
|
vm.prank(alice);
|
|
uint256 channelId = manager.openChannel{value: 10 ether}(bob);
|
|
vm.prank(bob);
|
|
manager.fundChannel{value: 10 ether}(channelId);
|
|
|
|
uint256 balanceA = 12 ether;
|
|
uint256 balanceB = 8 ether;
|
|
uint256 nonce = 1;
|
|
|
|
(uint8 vA, bytes32 rA, bytes32 sA) = _signState(channelId, nonce, balanceA, balanceB, alicePk);
|
|
(uint8 vB, bytes32 rB, bytes32 sB) = _signState(channelId, nonce, balanceA, balanceB, bobPk);
|
|
|
|
vm.prank(alice);
|
|
manager.submitClose(channelId, nonce, balanceA, balanceB, vA, rA, sA, vB, rB, sB);
|
|
|
|
IPaymentChannelManager.Channel memory ch = manager.getChannel(channelId);
|
|
assertEq(uint256(ch.status), uint256(IPaymentChannelManager.ChannelStatus.Dispute));
|
|
assertEq(ch.disputeNonce, nonce);
|
|
assertEq(ch.disputeBalanceA, balanceA);
|
|
assertEq(ch.disputeBalanceB, balanceB);
|
|
|
|
vm.warp(block.timestamp + CHALLENGE_WINDOW + 1);
|
|
|
|
uint256 aliceBefore = alice.balance;
|
|
uint256 bobBefore = bob.balance;
|
|
vm.prank(bob);
|
|
manager.finalizeClose(channelId);
|
|
|
|
assertEq(alice.balance, aliceBefore + balanceA);
|
|
assertEq(bob.balance, bobBefore + balanceB);
|
|
assertEq(uint256(manager.getChannel(channelId).status), uint256(IPaymentChannelManager.ChannelStatus.Closed));
|
|
}
|
|
|
|
function test_challengeClose_newestStateWins() public {
|
|
vm.prank(alice);
|
|
uint256 channelId = manager.openChannel{value: 10 ether}(bob);
|
|
vm.prank(bob);
|
|
manager.fundChannel{value: 10 ether}(channelId);
|
|
|
|
uint256 balanceA1 = 12 ether;
|
|
uint256 balanceB1 = 8 ether;
|
|
(uint8 vA1, bytes32 rA1, bytes32 sA1) = _signState(channelId, 1, balanceA1, balanceB1, alicePk);
|
|
(uint8 vB1, bytes32 rB1, bytes32 sB1) = _signState(channelId, 1, balanceA1, balanceB1, bobPk);
|
|
|
|
vm.prank(alice);
|
|
manager.submitClose(channelId, 1, balanceA1, balanceB1, vA1, rA1, sA1, vB1, rB1, sB1);
|
|
|
|
uint256 balanceA2 = 5 ether;
|
|
uint256 balanceB2 = 15 ether;
|
|
(uint8 vA2, bytes32 rA2, bytes32 sA2) = _signState(channelId, 2, balanceA2, balanceB2, alicePk);
|
|
(uint8 vB2, bytes32 rB2, bytes32 sB2) = _signState(channelId, 2, balanceA2, balanceB2, bobPk);
|
|
|
|
vm.prank(bob);
|
|
manager.challengeClose(channelId, 2, balanceA2, balanceB2, vA2, rA2, sA2, vB2, rB2, sB2);
|
|
|
|
IPaymentChannelManager.Channel memory ch = manager.getChannel(channelId);
|
|
assertEq(ch.disputeNonce, 2);
|
|
assertEq(ch.disputeBalanceA, balanceA2);
|
|
assertEq(ch.disputeBalanceB, balanceB2);
|
|
|
|
vm.warp(block.timestamp + CHALLENGE_WINDOW + 1);
|
|
uint256 aliceBefore = alice.balance;
|
|
uint256 bobBefore = bob.balance;
|
|
vm.prank(alice);
|
|
manager.finalizeClose(channelId);
|
|
|
|
assertEq(alice.balance, aliceBefore + balanceA2);
|
|
assertEq(bob.balance, bobBefore + balanceB2);
|
|
}
|
|
|
|
function test_challengeClose_revertsOlderState() public {
|
|
vm.prank(alice);
|
|
uint256 channelId = manager.openChannel{value: 10 ether}(bob);
|
|
vm.prank(bob);
|
|
manager.fundChannel{value: 10 ether}(channelId);
|
|
|
|
uint256 balanceA1 = 12 ether;
|
|
uint256 balanceB1 = 8 ether;
|
|
(uint8 vA1, bytes32 rA1, bytes32 sA1) = _signState(channelId, 1, balanceA1, balanceB1, alicePk);
|
|
(uint8 vB1, bytes32 rB1, bytes32 sB1) = _signState(channelId, 1, balanceA1, balanceB1, bobPk);
|
|
vm.prank(alice);
|
|
manager.submitClose(channelId, 1, balanceA1, balanceB1, vA1, rA1, sA1, vB1, rB1, sB1);
|
|
|
|
uint256 balanceA0 = 20 ether;
|
|
uint256 balanceB0 = 0;
|
|
(uint8 vA0, bytes32 rA0, bytes32 sA0) = _signState(channelId, 0, balanceA0, balanceB0, alicePk);
|
|
(uint8 vB0, bytes32 rB0, bytes32 sB0) = _signState(channelId, 0, balanceA0, balanceB0, bobPk);
|
|
|
|
vm.prank(bob);
|
|
vm.expectRevert("not newer");
|
|
manager.challengeClose(channelId, 0, balanceA0, balanceB0, vA0, rA0, sA0, vB0, rB0, sB0);
|
|
}
|
|
|
|
function test_finalizeClose_revertsBeforeDeadline() public {
|
|
vm.prank(alice);
|
|
uint256 channelId = manager.openChannel{value: 10 ether}(bob);
|
|
vm.prank(bob);
|
|
manager.fundChannel{value: 10 ether}(channelId);
|
|
|
|
uint256 balanceA = 10 ether;
|
|
uint256 balanceB = 10 ether;
|
|
(uint8 vA, bytes32 rA, bytes32 sA) = _signState(channelId, 1, balanceA, balanceB, alicePk);
|
|
(uint8 vB, bytes32 rB, bytes32 sB) = _signState(channelId, 1, balanceA, balanceB, bobPk);
|
|
vm.prank(alice);
|
|
manager.submitClose(channelId, 1, balanceA, balanceB, vA, rA, sA, vB, rB, sB);
|
|
|
|
vm.expectRevert("window open");
|
|
manager.finalizeClose(channelId);
|
|
}
|
|
|
|
function test_pauseBlocksOpen() public {
|
|
vm.prank(admin);
|
|
manager.pause();
|
|
vm.prank(alice);
|
|
vm.expectRevert("paused");
|
|
manager.openChannel(bob);
|
|
}
|
|
|
|
function test_getChannelCount() public {
|
|
assertEq(manager.getChannelCount(), 0);
|
|
vm.prank(alice);
|
|
manager.openChannel{value: 1 ether}(bob);
|
|
assertEq(manager.getChannelCount(), 1);
|
|
}
|
|
}
|