diff --git a/script/ClaimEscrow.s.sol b/script/ClaimEscrow.s.sol deleted file mode 100644 index 22e397c..0000000 --- a/script/ClaimEscrow.s.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.28; - -import {Script, console} from "forge-std/Script.sol"; -import {GitBondEscrow} from "src/GitBondEscrow.sol"; - -contract ClaimEscrowScript is Script { - function run() public { - address escrow = vm.envAddress("ESCROW_ADDRESS"); - address token = vm.envAddress("TOKEN"); - - vm.startBroadcast(); - GitBondEscrow(escrow).claim(token); - vm.stopBroadcast(); - - console.log("Claimed token:", token); - } -} diff --git a/script/ReadEscrow.s.sol b/script/ReadEscrow.s.sol index 9f4596a..8b05874 100644 --- a/script/ReadEscrow.s.sol +++ b/script/ReadEscrow.s.sol @@ -29,9 +29,6 @@ contract ReadEscrowScript is Script { console.log("Counterparty: ", e.counterparty); console.log("Token: ", e.token); console.log("Principal: ", e.principal); - console.log("Beneficiary claim:", escrow.claimableAmount(e.beneficiary, e.token)); - console.log("Counterparty claim:", escrow.claimableAmount(e.counterparty, e.token)); - console.log("Treasury claim:", escrow.treasuryClaimableAmount(e.token)); console.log("Deposited at: ", e.depositedAt); console.log("Status: ", uint256(uint8(e.status))); console.log("Scope: ", vm.toString(e.scope)); diff --git a/src/GitBondEscrow.sol b/src/GitBondEscrow.sol index 7480bb7..1b91d90 100644 --- a/src/GitBondEscrow.sol +++ b/src/GitBondEscrow.sol @@ -33,10 +33,8 @@ library GitBondEscrowStorage { mapping(bytes32 => Escrow) escrows; mapping(address => bool) tokenWhitelist; mapping(address => uint256) totalEscrowedByToken; - mapping(address => mapping(address => uint256)) claimAmounts; address treasury; uint256 platformCutBps; - uint256 deprecatedFlatRefundFee; uint256 minEscrowAmount; uint256 totalEscrowed; mapping(address => mapping(address => bool)) refundDelegates; @@ -98,8 +96,6 @@ contract GitBondEscrow is uint256 platformCut ); - event EscrowClaimed(address indexed claimant, address indexed token, uint256 amount); - event EscrowCounterpartyUpdated( bytes32 indexed key, address indexed previousCounterparty, address indexed newCounterparty ); @@ -116,7 +112,6 @@ contract GitBondEscrow is error GitBondEscrow__InvalidAmount(); error GitBondEscrow__InvalidPlatformCut(uint256 requestedBps); error GitBondEscrow__MinimumEscrowAmountTooLow(uint256 requestedAmount, uint256 minimumAllowedAmount); - error GitBondEscrow__NoClaimableAmount(); error GitBondEscrow__NotAuthorized(); error GitBondEscrow__PermitCallerMustBePayer(address caller, address payer); error GitBondEscrow__TokenNotWhitelisted(address token); @@ -194,8 +189,7 @@ contract GitBondEscrow is e.status = GitBondEscrowStorage.EscrowStatus.Refunded; s.totalEscrowed -= e.principal; s.totalEscrowedByToken[e.token] -= e.principal; - - s.claimAmounts[e.beneficiary][e.token] += amountToReturn; + _transferWithAmountCheck(e.token, e.beneficiary, amountToReturn); emit EscrowRefunded(key, e.payer, e.beneficiary, e.token, amountToReturn); } @@ -217,27 +211,13 @@ contract GitBondEscrow is uint256 amountToCounterparty = e.principal - platformCut; if (platformCut > 0) { - s.claimAmounts[s.treasury][e.token] += platformCut; + _transferWithAmountCheck(e.token, s.treasury, platformCut); } - s.claimAmounts[e.counterparty][e.token] += amountToCounterparty; + _transferWithAmountCheck(e.token, e.counterparty, amountToCounterparty); emit EscrowSlashed(key, e.beneficiary, e.counterparty, e.token, amountToCounterparty, platformCut); } - function claim(address token) external virtual whenNotPaused nonReentrant { - GitBondEscrowStorage.Layout storage s = _layout(); - uint256 amount = s.claimAmounts[msg.sender][token]; - - if (amount == 0) { - revert GitBondEscrow__NoClaimableAmount(); - } - - s.claimAmounts[msg.sender][token] = 0; - _transferWithAmountCheck(token, msg.sender, amount); - - emit EscrowClaimed(msg.sender, token, amount); - } - function addRefundDelegate(address delegate) external virtual { if (delegate == address(0)) { revert GitBondEscrow__InvalidAddress(); @@ -282,15 +262,6 @@ contract GitBondEscrow is return _layout().tokenWhitelist[token]; } - function claimableAmount(address claimant, address token) external view virtual returns (uint256) { - return _layout().claimAmounts[claimant][token]; - } - - function treasuryClaimableAmount(address token) external view virtual returns (uint256) { - GitBondEscrowStorage.Layout storage s = _layout(); - return s.claimAmounts[s.treasury][token]; - } - function isRefundDelegate(address counterparty, address delegate) external view virtual returns (bool) { return _layout().refundDelegates[counterparty][delegate]; } diff --git a/test/GitBondEscrow.invariant.t.sol b/test/GitBondEscrow.invariant.t.sol index e52a18e..57844cf 100644 --- a/test/GitBondEscrow.invariant.t.sol +++ b/test/GitBondEscrow.invariant.t.sol @@ -8,27 +8,32 @@ import {GitBondEscrow, GitBondEscrowStorage} from "src/GitBondEscrow.sol"; import {MockUSDToken} from "test/utils/GitBondEscrowTestUtils.sol"; contract GitBondEscrowHandler { + uint256 public constant INITIAL_TOKEN_SUPPLY = 500_000_000; + GitBondEscrow public escrow; MockUSDToken public immutable token; address public immutable beneficiary; address public immutable counterparty; + address public immutable treasury; bytes32[3] internal keys; mapping(bytes32 => uint256) public activePrincipal; - mapping(address => uint256) public claimablePrincipal; uint256 public activePrincipalTotal; - uint256 public claimablePrincipalTotal; + uint256 public beneficiaryPaidTotal; + uint256 public counterpartyPaidTotal; + uint256 public treasuryPaidTotal; - constructor(MockUSDToken token_, address beneficiary_, address counterparty_) { + constructor(MockUSDToken token_, address beneficiary_, address counterparty_, address treasury_) { token = token_; beneficiary = beneficiary_; counterparty = counterparty_; + treasury = treasury_; keys[0] = keccak256("handler-key-0"); keys[1] = keccak256("handler-key-1"); keys[2] = keccak256("handler-key-2"); - token.mint(address(this), 500_000_000); + token.mint(address(this), INITIAL_TOKEN_SUPPLY); } function setEscrow(GitBondEscrow escrow_) external { @@ -64,8 +69,7 @@ contract GitBondEscrowHandler { escrow.refundEscrow(key); activePrincipalTotal -= activePrincipal[key]; - claimablePrincipal[beneficiary] += tracked.principal; - claimablePrincipalTotal += tracked.principal; + beneficiaryPaidTotal += tracked.principal; activePrincipal[key] = 0; } @@ -77,14 +81,13 @@ contract GitBondEscrowHandler { } uint256 principal = activePrincipal[key]; - uint256 claimable = principal - ((principal * escrow.platformCutBps()) / 10_000); + uint256 platformCut = (principal * escrow.platformCutBps()) / 10_000; + uint256 counterpartyPayout = principal - platformCut; escrow.slashEscrow(key); activePrincipalTotal -= principal; - claimablePrincipal[counterparty] += claimable; - claimablePrincipal[address(0xD00D)] += principal - claimable; - claimablePrincipalTotal += claimable; - claimablePrincipalTotal += principal - claimable; + counterpartyPaidTotal += counterpartyPayout; + treasuryPaidTotal += platformCut; activePrincipal[key] = 0; } } @@ -93,10 +96,11 @@ contract GitBondEscrowInvariantTest is StdInvariant, Test { GitBondEscrow internal escrow; GitBondEscrowHandler internal handler; MockUSDToken internal token; + address internal constant TREASURY = address(0xD00D); function setUp() public { token = new MockUSDToken("pathUSD", "pathUSD"); - handler = new GitBondEscrowHandler(token, address(0xBEEF), address(0xCAFE)); + handler = new GitBondEscrowHandler(token, address(0xBEEF), address(0xCAFE), TREASURY); address[] memory initialWhitelistedTokens = new address[](1); initialWhitelistedTokens[0] = address(token); @@ -109,7 +113,7 @@ contract GitBondEscrowInvariantTest is StdInvariant, Test { implementation, abi.encodeCall( GitBondEscrow.initialize, - (address(0xD00D), address(handler), initialWhitelistedTokens, initialResolutionOperators) + (TREASURY, address(handler), initialWhitelistedTokens, initialResolutionOperators) ) ) ) @@ -117,6 +121,11 @@ contract GitBondEscrowInvariantTest is StdInvariant, Test { handler.setEscrow(escrow); targetContract(address(handler)); + bytes4[] memory selectors = new bytes4[](3); + selectors[0] = GitBondEscrowHandler.create.selector; + selectors[1] = GitBondEscrowHandler.refund.selector; + selectors[2] = GitBondEscrowHandler.slash.selector; + targetSelector(FuzzSelector({addr: address(handler), selectors: selectors})); } function invariant_totalEscrowedMatchesHandlerAccounting() public view { @@ -124,8 +133,23 @@ contract GitBondEscrowInvariantTest is StdInvariant, Test { assertEq(escrow.getTotalEscrowed(address(token)), handler.activePrincipalTotal()); } - function invariant_contractBalanceMatchesActiveAndClaimablePrincipal() public view { - assertEq(token.balanceOf(address(escrow)), handler.activePrincipalTotal() + handler.claimablePrincipalTotal()); + function invariant_contractBalanceMatchesActivePrincipal() public view { + assertEq(token.balanceOf(address(escrow)), handler.activePrincipalTotal()); + } + + function invariant_payoutRecipientsMatchTrackedBalances() public view { + assertEq(token.balanceOf(handler.beneficiary()), handler.beneficiaryPaidTotal()); + assertEq(token.balanceOf(handler.counterparty()), handler.counterpartyPaidTotal()); + assertEq(token.balanceOf(handler.treasury()), handler.treasuryPaidTotal()); + } + + function invariant_tokenBalanceConservationHolds() public view { + assertEq( + token.balanceOf(address(handler)) + token.balanceOf(address(escrow)) + + token.balanceOf(handler.beneficiary()) + token.balanceOf(handler.counterparty()) + + token.balanceOf(handler.treasury()), + handler.INITIAL_TOKEN_SUPPLY() + ); } function invariant_handlerAndContractAgreeOnActiveKeys() public view { @@ -133,18 +157,14 @@ contract GitBondEscrowInvariantTest is StdInvariant, Test { bytes32 key = handler.keyAt(i); GitBondEscrowStorage.Escrow memory tracked = escrow.getEscrow(key); bool contractActive = tracked.status == GitBondEscrowStorage.EscrowStatus.Active; - bool handlerActive = handler.activePrincipal(key) > 0; + uint256 handlerPrincipal = handler.activePrincipal(key); + bool handlerActive = handlerPrincipal > 0; assertEq(contractActive, handlerActive); + if (contractActive) { + assertEq(tracked.principal, handlerPrincipal); + } else { + assertEq(handlerPrincipal, 0); + } } - - assertEq( - escrow.claimableAmount(handler.beneficiary(), address(token)), - handler.claimablePrincipal(handler.beneficiary()) - ); - assertEq( - escrow.claimableAmount(handler.counterparty(), address(token)), - handler.claimablePrincipal(handler.counterparty()) - ); - assertEq(escrow.treasuryClaimableAmount(address(token)), handler.claimablePrincipal(address(0xD00D))); } } diff --git a/test/GitBondEscrow.t.sol b/test/GitBondEscrow.t.sol index 8732190..6e17125 100644 --- a/test/GitBondEscrow.t.sol +++ b/test/GitBondEscrow.t.sol @@ -290,22 +290,20 @@ contract GitBondEscrowTest is Test { assertEq(permitUsd.balanceOf(address(escrow)), permitAmount); } - function test_refundEscrowCreditsFullBeneficiaryClaimBalance() public { + function test_refundEscrowTransfersFullAmountToBeneficiaryImmediately() public { uint256 amount = 5_000_000; _createEscrow(TEST_KEY, address(pathUsd), amount); escrow.refundEscrow(TEST_KEY); - assertEq(pathUsd.balanceOf(beneficiary), 0); + assertEq(pathUsd.balanceOf(beneficiary), amount); assertEq(pathUsd.balanceOf(treasury), 0); - assertEq(pathUsd.balanceOf(address(escrow)), amount); + assertEq(pathUsd.balanceOf(address(escrow)), 0); assertEq(escrow.getTotalEscrowed(), 0); assertEq(uint8(escrow.getEscrow(TEST_KEY).status), uint8(GitBondEscrowStorage.EscrowStatus.Refunded)); - assertEq(escrow.claimableAmount(beneficiary, address(pathUsd)), amount); - assertEq(escrow.treasuryClaimableAmount(address(pathUsd)), 0); } - function test_slashEscrowCreditsCounterpartyAndTreasuryClaimBalances() public { + function test_slashEscrowTransfersCounterpartyAndTreasuryImmediately() public { uint256 amount = 5_000_000; _createEscrow(TEST_KEY, address(pathUsd), amount); @@ -316,31 +314,14 @@ contract GitBondEscrowTest is Test { GitBondEscrowStorage.Escrow memory slashed = escrow.getEscrow(TEST_KEY); - assertEq(pathUsd.balanceOf(counterparty), 0); - assertEq(pathUsd.balanceOf(treasury), 0); - assertEq(pathUsd.balanceOf(address(escrow)), amount); + assertEq(pathUsd.balanceOf(counterparty), amountToCounterparty); + assertEq(pathUsd.balanceOf(treasury), platformCut); + assertEq(pathUsd.balanceOf(address(escrow)), 0); assertEq(escrow.getTotalEscrowed(), 0); assertEq(uint8(slashed.status), uint8(GitBondEscrowStorage.EscrowStatus.Closed)); - assertEq(escrow.claimableAmount(counterparty, address(pathUsd)), amountToCounterparty); - assertEq(escrow.treasuryClaimableAmount(address(pathUsd)), platformCut); } - function test_claimTransfersRefundBalanceToBeneficiary() public { - uint256 amount = 5_000_000; - _createEscrow(TEST_KEY, address(pathUsd), amount); - escrow.refundEscrow(TEST_KEY); - - vm.prank(beneficiary); - escrow.claim(address(pathUsd)); - - assertEq(pathUsd.balanceOf(beneficiary), amount); - assertEq(pathUsd.balanceOf(treasury), 0); - assertEq(pathUsd.balanceOf(address(escrow)), 0); - assertEq(escrow.claimableAmount(beneficiary, address(pathUsd)), 0); - assertEq(escrow.treasuryClaimableAmount(address(pathUsd)), 0); - } - - function test_claimTransfersAggregatedCounterpartyBalance() public { + function test_multipleSlashesAccumulateImmediatePayouts() public { bytes32 secondKey = keccak256("slash-aggregate-key"); uint256 firstAmount = 5_000_000; uint256 secondAmount = 7_000_000; @@ -351,19 +332,18 @@ contract GitBondEscrowTest is Test { escrow.slashEscrow(TEST_KEY); escrow.slashEscrow(secondKey); - uint256 firstClaim = firstAmount - ((firstAmount * escrow.platformCutBps()) / 10_000); - uint256 secondClaim = secondAmount - ((secondAmount * escrow.platformCutBps()) / 10_000); - uint256 totalClaim = firstClaim + secondClaim; - - vm.prank(counterparty); - escrow.claim(address(pathUsd)); + uint256 firstCounterpartyPayout = firstAmount - ((firstAmount * escrow.platformCutBps()) / 10_000); + uint256 secondCounterpartyPayout = secondAmount - ((secondAmount * escrow.platformCutBps()) / 10_000); + uint256 totalCounterpartyPayout = firstCounterpartyPayout + secondCounterpartyPayout; + uint256 totalTreasuryPayout = (firstAmount + secondAmount) - totalCounterpartyPayout; - assertEq(pathUsd.balanceOf(counterparty), totalClaim); - assertEq(escrow.claimableAmount(counterparty, address(pathUsd)), 0); - assertEq(pathUsd.balanceOf(address(escrow)), (firstAmount + secondAmount) - totalClaim); + assertEq(pathUsd.balanceOf(counterparty), totalCounterpartyPayout); + assertEq(pathUsd.balanceOf(treasury), totalTreasuryPayout); + assertEq(pathUsd.balanceOf(address(escrow)), 0); + assertEq(escrow.getTotalEscrowed(), 0); } - function test_treasuryCanClaimSlashFees() public { + function test_refundAndSlashDistributeToDistinctRecipients() public { bytes32 secondKey = keccak256("treasury-fee-key"); uint256 firstAmount = 5_000_000; uint256 secondAmount = 7_000_000; @@ -375,33 +355,16 @@ contract GitBondEscrowTest is Test { escrow.slashEscrow(secondKey); uint256 slashCut = (secondAmount * escrow.platformCutBps()) / 10_000; + uint256 counterpartyPayout = secondAmount - slashCut; - vm.prank(treasury); - escrow.claim(address(pathUsd)); - + assertEq(pathUsd.balanceOf(beneficiary), firstAmount); + assertEq(pathUsd.balanceOf(counterparty), counterpartyPayout); assertEq(pathUsd.balanceOf(treasury), slashCut); - assertEq(escrow.treasuryClaimableAmount(address(pathUsd)), 0); - } - - function test_claimRevertsWhenNothingClaimable() public { - vm.prank(counterparty); - vm.expectRevert(abi.encodeWithSelector(GitBondEscrow.GitBondEscrow__NoClaimableAmount.selector)); - escrow.claim(address(pathUsd)); - } - - function test_claimRevertsAfterClaimed() public { - _createEscrow(TEST_KEY, address(pathUsd), 5_000_000); - escrow.refundEscrow(TEST_KEY); - - vm.prank(beneficiary); - escrow.claim(address(pathUsd)); - - vm.prank(beneficiary); - vm.expectRevert(abi.encodeWithSelector(GitBondEscrow.GitBondEscrow__NoClaimableAmount.selector)); - escrow.claim(address(pathUsd)); + assertEq(pathUsd.balanceOf(address(escrow)), 0); + assertEq(escrow.getTotalEscrowed(), 0); } - function test_claimSeparatesBalancesByToken() public { + function test_immediatePayoutsStaySeparatedByToken() public { bytes32 secondKey = keccak256("refund-alt-key"); uint256 amount = 5_000_000; @@ -411,15 +374,11 @@ contract GitBondEscrowTest is Test { escrow.refundEscrow(TEST_KEY); escrow.refundEscrow(secondKey); - vm.prank(beneficiary); - escrow.claim(address(pathUsd)); - assertEq(pathUsd.balanceOf(beneficiary), amount); - assertEq(altUsd.balanceOf(beneficiary), 0); - assertEq(escrow.claimableAmount(beneficiary, address(pathUsd)), 0); - assertEq(escrow.claimableAmount(beneficiary, address(altUsd)), amount); - assertEq(escrow.treasuryClaimableAmount(address(pathUsd)), 0); - assertEq(escrow.treasuryClaimableAmount(address(altUsd)), 0); + assertEq(altUsd.balanceOf(beneficiary), amount); + assertEq(pathUsd.balanceOf(address(escrow)), 0); + assertEq(altUsd.balanceOf(address(escrow)), 0); + assertEq(escrow.getTotalEscrowed(), 0); } function test_getTotalEscrowedTracksActivePrincipalAcrossMultipleTokens() public { @@ -436,27 +395,35 @@ contract GitBondEscrowTest is Test { } function test_counterpartyCanRefundEscrow() public { - _createEscrow(TEST_KEY, address(pathUsd), 5_000_000); + uint256 amount = 5_000_000; + _createEscrow(TEST_KEY, address(pathUsd), amount); vm.prank(counterparty); escrow.refundEscrow(TEST_KEY); assertEq(uint8(escrow.getEscrow(TEST_KEY).status), uint8(GitBondEscrowStorage.EscrowStatus.Refunded)); - assertGt(escrow.claimableAmount(beneficiary, address(pathUsd)), 0); + assertEq(pathUsd.balanceOf(beneficiary), amount); + assertEq(pathUsd.balanceOf(address(escrow)), 0); } function test_counterpartyCanSlashEscrow() public { - _createEscrow(TEST_KEY, address(pathUsd), 5_000_000); + uint256 amount = 5_000_000; + uint256 platformCut = (amount * escrow.platformCutBps()) / 10_000; + uint256 amountToCounterparty = amount - platformCut; + _createEscrow(TEST_KEY, address(pathUsd), amount); vm.prank(counterparty); escrow.slashEscrow(TEST_KEY); assertEq(uint8(escrow.getEscrow(TEST_KEY).status), uint8(GitBondEscrowStorage.EscrowStatus.Closed)); - assertGt(escrow.claimableAmount(counterparty, address(pathUsd)), 0); + assertEq(pathUsd.balanceOf(counterparty), amountToCounterparty); + assertEq(pathUsd.balanceOf(treasury), platformCut); + assertEq(pathUsd.balanceOf(address(escrow)), 0); } function test_refundDelegateCanRefundEscrow() public { - _createEscrow(TEST_KEY, address(pathUsd), 5_000_000); + uint256 amount = 5_000_000; + _createEscrow(TEST_KEY, address(pathUsd), amount); vm.prank(counterparty); escrow.addRefundDelegate(refundDelegate); @@ -467,11 +434,15 @@ contract GitBondEscrowTest is Test { escrow.refundEscrow(TEST_KEY); assertEq(uint8(escrow.getEscrow(TEST_KEY).status), uint8(GitBondEscrowStorage.EscrowStatus.Refunded)); - assertGt(escrow.claimableAmount(beneficiary, address(pathUsd)), 0); + assertEq(pathUsd.balanceOf(beneficiary), amount); + assertEq(pathUsd.balanceOf(address(escrow)), 0); } function test_slashDelegateCanSlashEscrow() public { - _createEscrow(TEST_KEY, address(pathUsd), 5_000_000); + uint256 amount = 5_000_000; + uint256 platformCut = (amount * escrow.platformCutBps()) / 10_000; + uint256 amountToCounterparty = amount - platformCut; + _createEscrow(TEST_KEY, address(pathUsd), amount); vm.prank(counterparty); escrow.addSlashDelegate(slashDelegate); @@ -482,11 +453,14 @@ contract GitBondEscrowTest is Test { escrow.slashEscrow(TEST_KEY); assertEq(uint8(escrow.getEscrow(TEST_KEY).status), uint8(GitBondEscrowStorage.EscrowStatus.Closed)); - assertGt(escrow.claimableAmount(counterparty, address(pathUsd)), 0); + assertEq(pathUsd.balanceOf(counterparty), amountToCounterparty); + assertEq(pathUsd.balanceOf(treasury), platformCut); + assertEq(pathUsd.balanceOf(address(escrow)), 0); } function test_resolutionOperatorCanRefundEscrow() public { - _createEscrow(TEST_KEY, address(pathUsd), 5_000_000); + uint256 amount = 5_000_000; + _createEscrow(TEST_KEY, address(pathUsd), amount); escrow.setResolutionOperator(resolutionOperator, true); assertTrue(escrow.isResolutionOperator(resolutionOperator)); @@ -495,11 +469,15 @@ contract GitBondEscrowTest is Test { escrow.refundEscrow(TEST_KEY); assertEq(uint8(escrow.getEscrow(TEST_KEY).status), uint8(GitBondEscrowStorage.EscrowStatus.Refunded)); - assertGt(escrow.claimableAmount(beneficiary, address(pathUsd)), 0); + assertEq(pathUsd.balanceOf(beneficiary), amount); + assertEq(pathUsd.balanceOf(address(escrow)), 0); } function test_resolutionOperatorCanSlashEscrow() public { - _createEscrow(TEST_KEY, address(pathUsd), 5_000_000); + uint256 amount = 5_000_000; + uint256 platformCut = (amount * escrow.platformCutBps()) / 10_000; + uint256 amountToCounterparty = amount - platformCut; + _createEscrow(TEST_KEY, address(pathUsd), amount); escrow.setResolutionOperator(resolutionOperator, true); assertTrue(escrow.isResolutionOperator(resolutionOperator)); @@ -508,7 +486,9 @@ contract GitBondEscrowTest is Test { escrow.slashEscrow(TEST_KEY); assertEq(uint8(escrow.getEscrow(TEST_KEY).status), uint8(GitBondEscrowStorage.EscrowStatus.Closed)); - assertGt(escrow.claimableAmount(counterparty, address(pathUsd)), 0); + assertEq(pathUsd.balanceOf(counterparty), amountToCounterparty); + assertEq(pathUsd.balanceOf(treasury), platformCut); + assertEq(pathUsd.balanceOf(address(escrow)), 0); } function test_removeRefundDelegateRevokesAccess() public { @@ -600,8 +580,10 @@ contract GitBondEscrowTest is Test { vm.stopPrank(); } - function test_createEscrowAllowsKeyReuseWithPendingClaimBalance() public { + function test_createEscrowAllowsKeyReuseAfterImmediateSlashPayout() public { uint256 amount = 5_000_000; + uint256 platformCut = (amount * escrow.platformCutBps()) / 10_000; + uint256 amountToCounterparty = amount - platformCut; _createEscrow(TEST_KEY, address(pathUsd), amount); escrow.slashEscrow(TEST_KEY); @@ -610,7 +592,9 @@ contract GitBondEscrowTest is Test { GitBondEscrowStorage.Escrow memory created = escrow.getEscrow(TEST_KEY); assertEq(uint8(created.status), uint8(GitBondEscrowStorage.EscrowStatus.Active)); assertEq(created.principal, amount); - assertGt(escrow.claimableAmount(counterparty, address(pathUsd)), 0); + assertEq(pathUsd.balanceOf(counterparty), amountToCounterparty); + assertEq(pathUsd.balanceOf(treasury), platformCut); + assertEq(pathUsd.balanceOf(address(escrow)), amount); } function test_createEscrowWithPermitRevertsForDuplicateKey() public { @@ -654,13 +638,12 @@ contract GitBondEscrowTest is Test { assertEq(permitUsd.allowance(payer, address(escrow)), 0); } - function test_pauseBlocksCreateRefundSlashAndClaim() public { + function test_pauseBlocksCreateRefundAndSlash() public { bytes32 slashKey = keccak256("paused-slash-key"); bytes32 refundKey = keccak256("paused-refund-key"); _createEscrow(slashKey, address(pathUsd), 5_000_000); _createEscrow(refundKey, address(pathUsd), 5_000_000); - escrow.refundEscrow(refundKey); escrow.pause(); @@ -671,11 +654,11 @@ contract GitBondEscrowTest is Test { vm.prank(counterparty); vm.expectRevert(); - escrow.slashEscrow(slashKey); + escrow.refundEscrow(refundKey); - vm.prank(beneficiary); + vm.prank(counterparty); vm.expectRevert(); - escrow.claim(address(pathUsd)); + escrow.slashEscrow(slashKey); } function test_unpauseRestoresCreateEscrow() public {