Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions src/Escrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,12 @@ contract Escrow is IEscrow {
for (uint256 i = 0; i < escrowIds.length; i++) {
Escrow storage _escrow = escrows[escrowIds[i]];
// If refund timestamp hasn't passed yet, then the refund is invalid.
if (block.timestamp <= _escrow.refundTimestamp) {
if (block.timestamp > _escrow.refundTimestamp || msg.sender == _escrow.recipient) {
_refundDepositor(escrowIds[i], _escrow);
_refundRecipient(escrowIds[i], _escrow);
} else {
revert RefundInvalid();
}

_refundDepositor(escrowIds[i], _escrow);
_refundRecipient(escrowIds[i], _escrow);
}
}

Expand All @@ -146,10 +146,11 @@ contract Escrow is IEscrow {
for (uint256 i = 0; i < escrowIds.length; i++) {
Escrow storage _escrow = escrows[escrowIds[i]];
// If refund timestamp hasn't passed yet, then the refund is invalid.
if (block.timestamp <= _escrow.refundTimestamp) {
if (block.timestamp > _escrow.refundTimestamp || msg.sender == _escrow.depositor) {
_refundDepositor(escrowIds[i], _escrow);
} else {
revert RefundInvalid();
}
_refundDepositor(escrowIds[i], _escrow);
}
}

Expand Down Expand Up @@ -180,11 +181,11 @@ contract Escrow is IEscrow {
Escrow storage _escrow = escrows[escrowIds[i]];

// If settlement is still within the deadline, then refund is invalid.
if (block.timestamp <= _escrow.refundTimestamp) {
if (block.timestamp > _escrow.refundTimestamp || msg.sender == _escrow.recipient) {
_refundRecipient(escrowIds[i], _escrow);
} else {
revert RefundInvalid();
}

_refundRecipient(escrowIds[i], _escrow);
}
}

Expand Down
74 changes: 74 additions & 0 deletions test/Escrow.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,80 @@ contract EscrowTest is BaseTest {
assertEq(uint8(escrow.statuses(escrowId3)), uint8(IEscrow.EscrowStatus.CREATED));
}

// ========== Early Refund Functionality Tests ==========

function testEarlyRefundFunctionality() public {
// Test 1: Recipient can trigger early refund for both parties
IEscrow.Escrow memory escrowData = _createEscrowData(1000, 800);
bytes32 escrowId = _createAndFundEscrow(escrowData);
bytes32[] memory escrowIds = new bytes32[](1);
escrowIds[0] = escrowId;

uint256 depositorBalanceBefore = token.balanceOf(depositor);
uint256 recipientBalanceBefore = token.balanceOf(recipient);

vm.prank(recipient);
escrow.refund(escrowIds);

assertEq(token.balanceOf(depositor) - depositorBalanceBefore, 800); // Depositor gets refundAmount
assertEq(token.balanceOf(recipient) - recipientBalanceBefore, 200); // Recipient gets escrowAmount - refundAmount
assertEq(uint256(escrow.statuses(escrowId)), uint256(IEscrow.EscrowStatus.FINALIZED));

// Test 2: Recipient can trigger early refund for themselves only
escrowData.salt = bytes12(uint96(2));
escrowId = _createAndFundEscrow(escrowData);
escrowIds[0] = escrowId;

recipientBalanceBefore = token.balanceOf(recipient);

vm.prank(recipient);
escrow.refundRecipient(escrowIds);

assertEq(token.balanceOf(recipient) - recipientBalanceBefore, 200); // Recipient gets escrowAmount - refundAmount
assertEq(uint256(escrow.statuses(escrowId)), uint256(IEscrow.EscrowStatus.REFUND_RECIPIENT));

// Test 3: Depositor can trigger early refund for themselves only
escrowData.salt = bytes12(uint96(3));
escrowId = _createAndFundEscrow(escrowData);
escrowIds[0] = escrowId;

depositorBalanceBefore = token.balanceOf(depositor);

vm.prank(depositor);
escrow.refundDepositor(escrowIds);

assertEq(token.balanceOf(depositor) - depositorBalanceBefore, 800); // Depositor gets refundAmount
assertEq(uint256(escrow.statuses(escrowId)), uint256(IEscrow.EscrowStatus.REFUND_DEPOSIT));

// Test 4: Unauthorized users cannot trigger early refunds
escrowData.salt = bytes12(uint96(4));
escrowId = _createAndFundEscrow(escrowData);
escrowIds[0] = escrowId;

vm.expectRevert(bytes4(keccak256("RefundInvalid()")));
vm.prank(randomUser);
escrow.refund(escrowIds);

vm.expectRevert(bytes4(keccak256("RefundInvalid()")));
vm.prank(attacker);
escrow.refundDepositor(escrowIds);

vm.expectRevert(bytes4(keccak256("RefundInvalid()")));
vm.prank(randomUser);
escrow.refundRecipient(escrowIds);

// Test 5: Normal timeout refunds still work after early refunds
depositorBalanceBefore = token.balanceOf(depositor);
recipientBalanceBefore = token.balanceOf(recipient);

vm.warp(block.timestamp + 2 hours);
vm.prank(randomUser);
escrow.refund(escrowIds);

assertEq(token.balanceOf(depositor) - depositorBalanceBefore, 800); // Depositor gets refundAmount
assertEq(token.balanceOf(recipient) - recipientBalanceBefore, 200); // Recipient gets escrowAmount - refundAmount
}

// ========== Helper Functions ==========

function _createEscrowData(uint256 escrowAmount, uint256 refundAmount)
Expand Down