Skip to content

Commit

Permalink
feat: batch claim in EtherSwap
Browse files Browse the repository at this point in the history
  • Loading branch information
michael1011 committed Dec 1, 2024
1 parent af7a6f9 commit f0a969a
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 14 deletions.
72 changes: 59 additions & 13 deletions contracts/EtherSwap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ contract EtherSwap {
// Constants

/// @dev Version of the contract used for compatibility checks
uint8 public constant version = 3;
uint8 public constant version = 4;

bytes32 public immutable DOMAIN_SEPARATOR;
bytes32 public immutable TYPEHASH_REFUND;
Expand Down Expand Up @@ -103,22 +103,39 @@ contract EtherSwap {
function claim(bytes32 preimage, uint256 amount, address claimAddress, address refundAddress, uint256 timelock)
public
{
// If the preimage is wrong, so will be its hash which will result in a wrong value hash and no swap being found
bytes32 preimageHash = sha256(abi.encodePacked(preimage));
prepareClaim(preimage, amount, claimAddress, refundAddress, timelock);

bytes32 hash = hashValues(preimageHash, amount, claimAddress, refundAddress, timelock);
// Transfer the Ether to the claim address
TransferHelper.transferEther(payable(claimAddress), amount);
}

// Make sure that the swap to be claimed has Ether locked
checkSwapIsLocked(hash);
// Delete the swap from the mapping to ensure that it cannot be claimed or refunded anymore
// This *HAS* to be done before actually sending the Ether to avoid reentrancy
delete swaps[hash];
/// Claims multiple swaps
/// @dev All swaps that are claimed have to have "msg.sender" as "claimAddress"
/// @param preimages Preimages of the swaps
/// @param amounts Amounts that are locked in the contract for the swap in WEI
/// @param refundAddresses Addresses that locked the Ether in the contract
/// @param timelocks Block heights after which the locked Ether can be refunded
function claimBatch(
bytes32[] calldata preimages,
uint256[] calldata amounts,
address[] calldata refundAddresses,
uint256[] calldata timelocks
) external {
uint256 toSend = 0;
uint256 swapAmount = 0;

// Emit the claim event
emit Claim(preimageHash, preimage);
unchecked {
for (uint256 i = 0; i < preimages.length; i++) {
swapAmount = amounts[i];
prepareClaim(preimages[i], swapAmount, msg.sender, refundAddresses[i], timelocks[i]);

// Transfer the Ether to the claim address
TransferHelper.transferEther(payable(claimAddress), amount);
// For the "prepareClaim" function to not revert, the amount has to have been locked
// in the contract which means this addition cannot overflow
toSend += swapAmount;
}
}

TransferHelper.transferEther(payable(msg.sender), toSend);
}

/// Refunds Ether locked in the contract after the timeout
Expand Down Expand Up @@ -192,6 +209,35 @@ contract EtherSwap {

// Private functions

/// Prepares a claim by checking if funds were locked, deleting the swap from storage
/// and emitting an event but ***does not*** transfer
/// @param preimage Preimage of the swap
/// @param amount Amount locked in the contract for the swap in WEI
/// @param claimAddress Address that that was destined to claim the funds
/// @param refundAddress Address that locked the Ether and can refund them
/// @param timelock Block height after which the locked Ether can be refunded
function prepareClaim(
bytes32 preimage,
uint256 amount,
address claimAddress,
address refundAddress,
uint256 timelock
) private {
// If the preimage is wrong, so will be its hash which will result in a wrong value hash and no swap being found
bytes32 preimageHash = sha256(abi.encodePacked(preimage));
bytes32 hash = hashValues(preimageHash, amount, claimAddress, refundAddress, timelock);

// Make sure that the swap to be claimed has Ether locked
checkSwapIsLocked(hash);

// Delete the swap from the mapping to ensure that it cannot be claimed or refunded anymore
// This *HAS* to be done before actually sending the Ether to avoid reentrancy
delete swaps[hash];

// Emit the claim event
emit Claim(preimageHash, preimage);
}

/// Locks Ether in the contract
/// @notice The refund address is the sender of the transaction
/// @param preimageHash Preimage hash of the swap
Expand Down
46 changes: 45 additions & 1 deletion contracts/test/EtherSwapTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ contract EtherSwapTest is Test {
receive() external payable {}

function testCorrectVersion() external view {
assertEq(swap.version(), 3);
assertEq(swap.version(), 4);
}

function testNoSendEtherWithoutFunctionSig() external {
Expand Down Expand Up @@ -129,6 +129,50 @@ contract EtherSwapTest is Test {
assertEq(claimAddress.balance - balanceBeforeClaim, lockupAmount);
}

function testClaimBatchTwo() external {
uint256 timelock = block.number;
uint256 balanceBeforeClaim = claimAddress.balance;

swap.lock{value: lockupAmount}(preimageHash, claimAddress, timelock);

bytes32 preimageSecond = sha256("2");
bytes32 preimageHashSecond = sha256(abi.encodePacked(preimageSecond));

uint256 lockupAmountSecond = 123;
uint256 timelockSecond = block.number + 21;

swap.lock{value: lockupAmountSecond}(preimageHashSecond, claimAddress, timelockSecond);

bytes32[] memory preimages = new bytes32[](2);
preimages[0] = preimage;
preimages[1] = preimageSecond;

uint256[] memory amounts = new uint256[](2);
amounts[0] = lockupAmount;
amounts[1] = lockupAmountSecond;

address[] memory refundAddresses = new address[](2);
refundAddresses[0] = address(this);
refundAddresses[1] = address(this);

uint256[] memory timelocks = new uint256[](2);
timelocks[0] = timelock;
timelocks[1] = timelockSecond;

vm.prank(claimAddress);

vm.expectEmit(true, false, false, true, address(swap));
emit Claim(preimageHash, preimage);

vm.expectEmit(true, false, false, true, address(swap));
emit Claim(preimageHashSecond, preimageSecond);

swap.claimBatch(preimages, amounts, refundAddresses, timelocks);

assertEq(address(swap).balance, 0);
assertEq(claimAddress.balance - balanceBeforeClaim, lockupAmount + lockupAmountSecond);
}

function testClaimTwiceFail() external {
uint256 timelock = block.number;

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"docker:start": "./startRegtest.sh",
"docker:stop": "docker stop boltz-bitcoin && docker rm boltz-bitcoin && docker kill boltz-elements && docker rm boltz-elements",
"test": "npm run test:solidity && npm run test:unit && npm run test:int",
"format:solidity": "forge fmt contracts/*.sol contracts/script/ contracts/test/",
"test:solidity": "forge test",
"test:unit": "jest test/unit",
"test:int": "jest test/integration --runInBand",
Expand Down

0 comments on commit f0a969a

Please sign in to comment.