diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f63b6d9..a6e8cf2 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -40,6 +40,9 @@ jobs: - name: Prettier run: npm run prettier:check + - name: Forge fmt + run: forge fmt --check contracts/*.sol contracts/script/ contracts/test/ + - name: Unit tests run: npm run test:unit diff --git a/contracts/BadERC20.sol b/contracts/BadERC20.sol index 7556e24..76dd91f 100644 --- a/contracts/BadERC20.sol +++ b/contracts/BadERC20.sol @@ -10,7 +10,7 @@ interface IBadERC20 { function balanceOf(address account) external view returns (uint256); function allowance(address owner, address spender) external view returns (uint256); - function approve(address spender, uint value) external; + function approve(address spender, uint256 value) external; function transfer(address recipient, uint256 amount) external; function transferFrom(address sender, address recipient, uint256 amount) external; @@ -22,9 +22,9 @@ interface IBadERC20 { contract BadERC20 is IBadERC20 { uint8 public decimals; - mapping (address => uint256) private _balances; + mapping(address => uint256) private _balances; - mapping (address => mapping (address => uint256)) private _allowances; + mapping(address => mapping(address => uint256)) private _allowances; string private _name; string private _symbol; diff --git a/contracts/ERC20Swap.sol b/contracts/ERC20Swap.sol index a1e61bc..a6a75a2 100644 --- a/contracts/ERC20Swap.sol +++ b/contracts/ERC20Swap.sol @@ -9,15 +9,15 @@ contract ERC20Swap { // Constants /// @dev Version of the contract used for compatibility checks - uint8 constant public version = 3; + uint8 public constant version = 3; - bytes32 immutable public DOMAIN_SEPARATOR; - bytes32 immutable public TYPEHASH_REFUND; + bytes32 public immutable DOMAIN_SEPARATOR; + bytes32 public immutable TYPEHASH_REFUND; // State variables /// @dev Mapping between value hashes of swaps and whether they have Ether locked in the contract - mapping (bytes32 => bool) public swaps; + mapping(bytes32 => bool) public swaps; // Events @@ -27,7 +27,7 @@ contract ERC20Swap { address tokenAddress, address claimAddress, address indexed refundAddress, - uint timelock + uint256 timelock ); event Claim(bytes32 indexed preimageHash, bytes32 preimage); @@ -65,7 +65,7 @@ contract ERC20Swap { uint256 amount, address tokenAddress, address payable claimAddress, - uint timelock + uint256 timelock ) external payable { lock(preimageHash, amount, tokenAddress, claimAddress, timelock); @@ -80,13 +80,9 @@ contract ERC20Swap { /// @param tokenAddress Address of the token locked for the swap /// @param refundAddress Address that locked the tokens in the contract /// @param timelock Block height after which the locked tokens can be refunded - function claim( - bytes32 preimage, - uint amount, - address tokenAddress, - address refundAddress, - uint timelock - ) external { + function claim(bytes32 preimage, uint256 amount, address tokenAddress, address refundAddress, uint256 timelock) + external + { // Passing "msg.sender" as "claimAddress" to "hashValues" ensures that only the destined address can claim // All other addresses would produce a different hash for which no swap can be found in the "swaps" mapping claim(preimage, amount, tokenAddress, msg.sender, refundAddress, timelock); @@ -102,23 +98,16 @@ contract ERC20Swap { /// @param timelock Block height after which the locked tokens can be refunded function claim( bytes32 preimage, - uint amount, + uint256 amount, address tokenAddress, address claimAddress, address refundAddress, - uint timelock + 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)); - bytes32 hash = hashValues( - preimageHash, - amount, - tokenAddress, - claimAddress, - refundAddress, - timelock - ); + bytes32 hash = hashValues(preimageHash, amount, tokenAddress, claimAddress, refundAddress, timelock); // Make sure that the swap to be claimed has tokens locked checkSwapIsLocked(hash); @@ -143,13 +132,9 @@ contract ERC20Swap { /// @param tokenAddress Address of the token locked for the swap /// @param claimAddress Address that that was destined to claim the funds /// @param timelock Block height after which the locked Ether can be refunded - function refund( - bytes32 preimageHash, - uint amount, - address tokenAddress, - address claimAddress, - uint timelock - ) external { + function refund(bytes32 preimageHash, uint256 amount, address tokenAddress, address claimAddress, uint256 timelock) + external + { // Make sure the timelock has expired already // If the timelock is wrong, so will be the value hash of the swap which results in no swap being found require(timelock <= block.number, "ERC20Swap: swap has not timed out yet"); @@ -169,10 +154,10 @@ contract ERC20Swap { /// @param s first 32 bytes of the signature function refundCooperative( bytes32 preimageHash, - uint amount, + uint256 amount, address tokenAddress, address claimAddress, - uint timelock, + uint256 timelock, uint8 v, bytes32 r, bytes32 s @@ -182,16 +167,7 @@ contract ERC20Swap { abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR, - keccak256( - abi.encode( - TYPEHASH_REFUND, - preimageHash, - amount, - tokenAddress, - claimAddress, - timelock - ) - ) + keccak256(abi.encode(TYPEHASH_REFUND, preimageHash, amount, tokenAddress, claimAddress, timelock)) ) ), v, @@ -213,37 +189,26 @@ contract ERC20Swap { /// @param tokenAddress Address of the token to be locked /// @param claimAddress Address that can claim the locked tokens /// @param timelock Block height after which the locked tokens can be refunded - function lock( - bytes32 preimageHash, - uint256 amount, - address tokenAddress, - address claimAddress, - uint timelock - ) public { + function lock(bytes32 preimageHash, uint256 amount, address tokenAddress, address claimAddress, uint256 timelock) + public + { // Locking zero tokens in the contract is pointless require(amount > 0, "ERC20Swap: locked amount must not be zero"); - // Transfer the specified amount of tokens from the sender of the transaction to the contract - TransferHelper.safeTransferTokenFrom(tokenAddress, msg.sender, address(this), amount); - // Hash the values of the swap - bytes32 hash = hashValues( - preimageHash, - amount, - tokenAddress, - claimAddress, - msg.sender, - timelock - ); + bytes32 hash = hashValues(preimageHash, amount, tokenAddress, claimAddress, msg.sender, timelock); // Make sure no swap with this value hash exists yet - require(swaps[hash] == false, "ERC20Swap: swap exists already"); + require(!swaps[hash], "ERC20Swap: swap exists already"); // Save to the state that funds were locked for this swap swaps[hash] = true; // Emit the "Lockup" event emit Lockup(preimageHash, amount, tokenAddress, claimAddress, msg.sender, timelock); + + // Transfer the specified amount of tokens from the sender of the transaction to the contract + TransferHelper.safeTransferTokenFrom(tokenAddress, msg.sender, address(this), amount); } /// Hashes all the values of a swap with Keccak256 @@ -260,35 +225,21 @@ contract ERC20Swap { address tokenAddress, address claimAddress, address refundAddress, - uint timelock + uint256 timelock ) public pure returns (bytes32) { - return keccak256(abi.encodePacked( - preimageHash, - amount, - tokenAddress, - claimAddress, - refundAddress, - timelock - )); + return keccak256(abi.encodePacked(preimageHash, amount, tokenAddress, claimAddress, refundAddress, timelock)); } // Private functions function refundInternal( bytes32 preimageHash, - uint amount, + uint256 amount, address tokenAddress, address claimAddress, - uint timelock + uint256 timelock ) private { - bytes32 hash = hashValues( - preimageHash, - amount, - tokenAddress, - claimAddress, - msg.sender, - timelock - ); + bytes32 hash = hashValues(preimageHash, amount, tokenAddress, claimAddress, msg.sender, timelock); checkSwapIsLocked(hash); delete swaps[hash]; @@ -302,6 +253,6 @@ contract ERC20Swap { /// @dev This function reverts if the swap has no tokens locked in the contract /// @param hash Value hash of the swap function checkSwapIsLocked(bytes32 hash) private view { - require(swaps[hash] == true, "ERC20Swap: swap has no tokens locked in the contract"); + require(swaps[hash], "ERC20Swap: swap has no tokens locked in the contract"); } } diff --git a/contracts/EtherSwap.sol b/contracts/EtherSwap.sol index 46642e0..268432b 100644 --- a/contracts/EtherSwap.sol +++ b/contracts/EtherSwap.sol @@ -9,24 +9,24 @@ contract EtherSwap { // Constants /// @dev Version of the contract used for compatibility checks - uint8 constant public version = 3; + uint8 public constant version = 3; - bytes32 immutable public DOMAIN_SEPARATOR; - bytes32 immutable public TYPEHASH_REFUND; + bytes32 public immutable DOMAIN_SEPARATOR; + bytes32 public immutable TYPEHASH_REFUND; // State variables /// @dev Mapping between value hashes of swaps and whether they have Ether locked in the contract - mapping (bytes32 => bool) public swaps; + mapping(bytes32 => bool) public swaps; // Events event Lockup( bytes32 indexed preimageHash, - uint amount, + uint256 amount, address claimAddress, address indexed refundAddress, - uint timelock + uint256 timelock ); event Claim(bytes32 indexed preimageHash, bytes32 preimage); @@ -44,9 +44,7 @@ contract EtherSwap { address(this) ) ); - TYPEHASH_REFUND = keccak256( - "Refund(bytes32 preimageHash,uint256 amount,address claimAddress,uint256 timeout)" - ); + TYPEHASH_REFUND = keccak256("Refund(bytes32 preimageHash,uint256 amount,address claimAddress,uint256 timeout)"); } // External functions @@ -56,11 +54,7 @@ contract EtherSwap { /// @param preimageHash Preimage hash of the swap /// @param claimAddress Address that can claim the locked Ether /// @param timelock Block height after which the locked Ether can be refunded - function lock( - bytes32 preimageHash, - address claimAddress, - uint timelock - ) external payable { + function lock(bytes32 preimageHash, address claimAddress, uint256 timelock) external payable { lockEther(preimageHash, msg.value, claimAddress, timelock); } @@ -74,8 +68,8 @@ contract EtherSwap { function lockPrepayMinerfee( bytes32 preimageHash, address payable claimAddress, - uint timelock, - uint prepayAmount + uint256 timelock, + uint256 prepayAmount ) external payable { // Revert on underflow in next statement require(msg.value > prepayAmount, "EtherSwap: sent amount must be greater than the prepay amount"); @@ -93,12 +87,7 @@ contract EtherSwap { /// @param amount Amount locked in the contract for the swap in WEI /// @param refundAddress Address that locked the Ether in the contract /// @param timelock Block height after which the locked Ether can be refunded - function claim( - bytes32 preimage, - uint amount, - address refundAddress, - uint timelock - ) external { + function claim(bytes32 preimage, uint256 amount, address refundAddress, uint256 timelock) external { // Passing "msg.sender" as "claimAddress" to "hashValues" ensures that only the destined address can claim // All other addresses would produce a different hash for which no swap can be found in the "swaps" mapping claim(preimage, amount, msg.sender, refundAddress, timelock); @@ -111,23 +100,13 @@ contract EtherSwap { /// @param claimAddress Address to which the claimed funds will be sent /// @param refundAddress Address that locked the Ether in the contract /// @param timelock Block height after which the locked Ether can be refunded - function claim( - bytes32 preimage, - uint amount, - address claimAddress, - address refundAddress, - uint timelock - ) public { + 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)); - bytes32 hash = hashValues( - preimageHash, - amount, - claimAddress, - refundAddress, - timelock - ); + bytes32 hash = hashValues(preimageHash, amount, claimAddress, refundAddress, timelock); // Make sure that the swap to be claimed has Ether locked checkSwapIsLocked(hash); @@ -149,12 +128,7 @@ contract EtherSwap { /// @param amount Amount locked in the contract for the swap in WEI /// @param claimAddress Address that that was destined to claim the funds /// @param timelock Block height after which the locked Ether can be refunded - function refund( - bytes32 preimageHash, - uint amount, - address claimAddress, - uint timelock - ) external { + function refund(bytes32 preimageHash, uint256 amount, address claimAddress, uint256 timelock) external { // Make sure the timelock has expired already // If the timelock is wrong, so will be the value hash of the swap which results in no swap being found require(timelock <= block.number, "EtherSwap: swap has not timed out yet"); @@ -172,28 +146,20 @@ contract EtherSwap { /// @param r second 32 bytes of the signature /// @param s first 32 bytes of the signature function refundCooperative( - bytes32 preimageHash, - uint amount, - address claimAddress, - uint timelock, - uint8 v, - bytes32 r, - bytes32 s + bytes32 preimageHash, + uint256 amount, + address claimAddress, + uint256 timelock, + uint8 v, + bytes32 r, + bytes32 s ) external { address recoveredAddress = ecrecover( keccak256( abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR, - keccak256( - abi.encode( - TYPEHASH_REFUND, - preimageHash, - amount, - claimAddress, - timelock - ) - ) + keccak256(abi.encode(TYPEHASH_REFUND, preimageHash, amount, claimAddress, timelock)) ) ), v, @@ -216,18 +182,12 @@ contract EtherSwap { /// @return Value hash of the swap function hashValues( bytes32 preimageHash, - uint amount, + uint256 amount, address claimAddress, address refundAddress, - uint timelock + uint256 timelock ) public pure returns (bytes32) { - return keccak256(abi.encodePacked( - preimageHash, - amount, - claimAddress, - refundAddress, - timelock - )); + return keccak256(abi.encodePacked(preimageHash, amount, claimAddress, refundAddress, timelock)); } // Private functions @@ -238,21 +198,15 @@ contract EtherSwap { /// @param amount Amount to be locked in the contract /// @param claimAddress Address that can claim the locked Ether /// @param timelock Block height after which the locked Ether can be refunded - function lockEther(bytes32 preimageHash, uint amount, address claimAddress, uint timelock) private { + function lockEther(bytes32 preimageHash, uint256 amount, address claimAddress, uint256 timelock) private { // Locking zero WEI in the contract is pointless require(amount > 0, "EtherSwap: locked amount must not be zero"); // Hash the values of the swap - bytes32 hash = hashValues( - preimageHash, - amount, - claimAddress, - msg.sender, - timelock - ); + bytes32 hash = hashValues(preimageHash, amount, claimAddress, msg.sender, timelock); // Make sure no swap with this value hash exists yet - require(swaps[hash] == false, "EtherSwap: swap exists already"); + require(!swaps[hash], "EtherSwap: swap exists already"); // Save to the state that funds were locked for this swap swaps[hash] = true; @@ -261,14 +215,8 @@ contract EtherSwap { emit Lockup(preimageHash, amount, claimAddress, msg.sender, timelock); } - function refundInternal(bytes32 preimageHash, uint amount, address claimAddress, uint timelock) private { - bytes32 hash = hashValues( - preimageHash, - amount, - claimAddress, - msg.sender, - timelock - ); + function refundInternal(bytes32 preimageHash, uint256 amount, address claimAddress, uint256 timelock) private { + bytes32 hash = hashValues(preimageHash, amount, claimAddress, msg.sender, timelock); checkSwapIsLocked(hash); delete swaps[hash]; @@ -282,6 +230,6 @@ contract EtherSwap { /// @dev This function reverts if the swap has no Ether locked in the contract /// @param hash Value hash of the swap function checkSwapIsLocked(bytes32 hash) private view { - require(swaps[hash] == true, "EtherSwap: swap has no Ether locked in the contract"); + require(swaps[hash], "EtherSwap: swap has no Ether locked in the contract"); } } diff --git a/contracts/TestERC20.sol b/contracts/TestERC20.sol index dbd7fe8..ef3a4e5 100644 --- a/contracts/TestERC20.sol +++ b/contracts/TestERC20.sol @@ -7,7 +7,9 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract TestERC20 is ERC20 { uint8 public tokenDecimals; - constructor(string memory name, string memory symbol, uint8 initialDecimals, uint256 initialSupply) ERC20(name, symbol) { + constructor(string memory name, string memory symbol, uint8 initialDecimals, uint256 initialSupply) + ERC20(name, symbol) + { tokenDecimals = initialDecimals; _mint(msg.sender, initialSupply); } diff --git a/contracts/TransferHelper.sol b/contracts/TransferHelper.sol index 867bd37..6ae80ae 100644 --- a/contracts/TransferHelper.sol +++ b/contracts/TransferHelper.sol @@ -11,8 +11,8 @@ library TransferHelper { /// @dev Please note that ".call" forwards all leftover gas which means that sending Ether to accounts and contract is possible but also that you should specify or sanity check the gas limit /// @param to Address to which the Ether should be sent /// @param amount Amount of Ether to send in WEI - function transferEther(address payable to, uint amount) internal { - (bool success, ) = to.call{value: amount}(""); + function transferEther(address payable to, uint256 amount) internal { + (bool success,) = to.call{value: amount}(""); require(success, "TransferHelper: could not transfer Ether"); } @@ -22,12 +22,11 @@ library TransferHelper { /// @param token Address of the token /// @param to Address to which the tokens should be transferred /// @param value Amount of token that should be transferred in the smallest denomination of the token - function safeTransferToken(address token, address to, uint value) internal { + function safeTransferToken(address token, address to, uint256 value) internal { // bytes4(keccak256(bytes('transfer(address,uint256)'))); (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value)); require( - success && (data.length == 0 || abi.decode(data, (bool))), - "TransferHelper: could not transfer ERC20 tokens" + success && (data.length == 0 || abi.decode(data, (bool))), "TransferHelper: could not transfer ERC20 tokens" ); } @@ -39,7 +38,7 @@ library TransferHelper { /// @param from Address from which the tokens should be transferred /// @param to Address to which the tokens should be transferred /// @param value Amount of token that should be transferred in the smallest denomination of the token - function safeTransferTokenFrom(address token, address from, address to, uint value) internal { + function safeTransferTokenFrom(address token, address from, address to, uint256 value) internal { // bytes4(keccak256(bytes('transferFrom(address,address,uint256)'))); (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value)); require( diff --git a/contracts/lib/forge-std b/contracts/lib/forge-std index 978ac6f..07263d1 160000 --- a/contracts/lib/forge-std +++ b/contracts/lib/forge-std @@ -1 +1 @@ -Subproject commit 978ac6fadb62f5f0b723c996f64be52eddba6801 +Subproject commit 07263d193d621c4b2b0ce8b4d54af58f6957d97d diff --git a/contracts/test/ERC20SwapFuzzTest.sol b/contracts/test/ERC20SwapFuzzTest.sol new file mode 100644 index 0000000..10ef410 --- /dev/null +++ b/contracts/test/ERC20SwapFuzzTest.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.26; + +import "forge-std/Test.sol"; +import "./SigUtils.sol"; +import "../BadERC20.sol"; +import "../ERC20Swap.sol"; +import "../TestERC20.sol"; + +contract ERC20SwapFuzzTest is Test { + ERC20Swap internal swap = new ERC20Swap(); + IERC20 internal token = new TestERC20("TestERC20", "TRC", 18, 10 ** 21); + + function testLock(uint256 amount, bytes32 preimageHash, address claimAddress, uint256 timelock) external { + vm.assume(amount < token.balanceOf(address(this))); + vm.assume(amount > 0); + + token.approve(address(swap), amount); + swap.lock(preimageHash, amount, address(token), claimAddress, timelock); + assertTrue( + swap.swaps(swap.hashValues(preimageHash, amount, address(token), claimAddress, address(this), timelock)) + ); + } + + function testLockPrepayMinerFee( + uint256 amount, + uint256 prepayAmount, + bytes32 preimageHash, + address payable claimAddress, + uint256 timelock + ) external { + // Make sure that address can be paid + (bool claimAddressPayable,) = claimAddress.call{value: 1}(""); + vm.assume(claimAddressPayable); + + vm.assume(claimAddress != address(this)); + + vm.assume(amount < token.balanceOf(address(this))); + vm.assume(amount > 0); + + vm.assume(prepayAmount < address(this).balance); + + uint256 claimAddressBalanceBefore = claimAddress.balance; + + token.approve(address(swap), amount); + swap.lockPrepayMinerfee{value: prepayAmount}(preimageHash, amount, address(token), claimAddress, timelock); + assertTrue( + swap.swaps(swap.hashValues(preimageHash, amount, address(token), claimAddress, address(this), timelock)) + ); + + assertEq(claimAddress.balance, claimAddressBalanceBefore + prepayAmount); + } + + function testLockClaim(uint256 amount, bytes32 preimage, address claimAddress, uint256 timelock) external { + vm.assume(amount < token.balanceOf(address(this))); + vm.assume(amount > 0); + + vm.assume(claimAddress != address(0)); + vm.assume(claimAddress != address(swap)); + + bytes32 preimageHash = sha256(abi.encodePacked(preimage)); + + token.approve(address(swap), amount); + swap.lock(preimageHash, amount, address(token), claimAddress, timelock); + assertTrue( + swap.swaps(swap.hashValues(preimageHash, amount, address(token), claimAddress, address(this), timelock)) + ); + + uint256 claimAddressBalanceBefore = token.balanceOf(claimAddress); + + vm.startPrank(claimAddress); + swap.claim(preimage, amount, address(token), address(this), timelock); + assertEq(token.balanceOf(claimAddress), claimAddressBalanceBefore + amount); + + assertFalse( + swap.swaps(swap.hashValues(preimageHash, amount, address(token), claimAddress, address(this), timelock)) + ); + } + + function testLockClaimInvalidPreimage( + uint256 amount, + bytes32 preimage, + bytes32 invalidPreimage, + address payable claimAddress, + uint256 timelock + ) external { + vm.assume(amount < token.balanceOf(address(this))); + vm.assume(amount > 0); + + vm.assume(preimage != invalidPreimage); + + bytes32 preimageHash = sha256(abi.encodePacked(preimage)); + + token.approve(address(swap), amount); + swap.lock(preimageHash, amount, address(token), claimAddress, timelock); + assertTrue( + swap.swaps(swap.hashValues(preimageHash, amount, address(token), claimAddress, address(this), timelock)) + ); + + vm.startPrank(claimAddress); + vm.expectRevert("ERC20Swap: swap has no tokens locked in the contract"); + swap.claim(invalidPreimage, amount, address(token), address(this), timelock); + } + + function testLockRefund(uint256 amount, bytes32 preimageHash, address payable claimAddress, uint256 timelock) + external + { + vm.roll(6_573_352); + + vm.assume(amount < token.balanceOf(address(this))); + vm.assume(amount > 0); + + vm.assume(timelock <= block.number); + + token.approve(address(swap), amount); + swap.lock(preimageHash, amount, address(token), claimAddress, timelock); + assertTrue( + swap.swaps(swap.hashValues(preimageHash, amount, address(token), claimAddress, address(this), timelock)) + ); + + uint256 thisBalanceBefore = token.balanceOf(address(this)); + + swap.refund(preimageHash, amount, address(token), claimAddress, timelock); + assertEq(token.balanceOf(address(this)), thisBalanceBefore + amount); + + assertFalse( + swap.swaps(swap.hashValues(preimageHash, amount, address(token), claimAddress, address(this), timelock)) + ); + } + + function testLockRefundTimelockNotPassed( + uint256 amount, + bytes32 preimageHash, + address payable claimAddress, + uint256 timelock + ) external { + vm.roll(6_573_352); + + vm.assume(amount < token.balanceOf(address(this))); + vm.assume(amount > 0); + + vm.assume(timelock > block.number); + + token.approve(address(swap), amount); + swap.lock(preimageHash, amount, address(token), claimAddress, timelock); + assertTrue( + swap.swaps(swap.hashValues(preimageHash, amount, address(token), claimAddress, address(this), timelock)) + ); + + vm.expectRevert("ERC20Swap: swap has not timed out yet"); + swap.refund(preimageHash, amount, address(token), claimAddress, timelock); + + assertTrue( + swap.swaps(swap.hashValues(preimageHash, amount, address(token), claimAddress, address(this), timelock)) + ); + } + + function testLockRefundCooperativeInvalidSignature( + uint256 amount, + bytes32 preimageHash, + address payable claimAddress, + uint256 timelock, + uint8 v, + bytes32 r, + bytes32 s + ) external { + vm.assume(amount < token.balanceOf(address(this))); + vm.assume(amount > 0); + + token.approve(address(swap), amount); + swap.lock(preimageHash, amount, address(token), claimAddress, timelock); + assertTrue( + swap.swaps(swap.hashValues(preimageHash, amount, address(token), claimAddress, address(this), timelock)) + ); + + vm.expectRevert("ERC20Swap: invalid signature"); + swap.refundCooperative(preimageHash, amount, address(token), claimAddress, timelock, v, r, s); + + assertTrue( + swap.swaps(swap.hashValues(preimageHash, amount, address(token), claimAddress, address(this), timelock)) + ); + } +} diff --git a/contracts/test/ERC20SwapTest.sol b/contracts/test/ERC20SwapTest.sol index ad7f9f7..ab0503d 100644 --- a/contracts/test/ERC20SwapTest.sol +++ b/contracts/test/ERC20SwapTest.sol @@ -15,7 +15,7 @@ contract ERC20SwapTest is Test { address tokenAddress, address claimAddress, address indexed refundAddress, - uint timelock + uint256 timelock ); event Claim(bytes32 indexed preimageHash, bytes32 preimage); @@ -45,7 +45,7 @@ contract ERC20SwapTest is Test { } function testShouldNotAcceptEther() external { - (bool success,) = address(swap).call{ value: 1 }(""); + (bool success,) = address(swap).call{value: 1}(""); require(!success); } @@ -54,7 +54,9 @@ contract ERC20SwapTest is Test { assertEq( swap.hashValues(preimageHash, lockupAmount, address(token), claimAddress, address(this), timelock), - keccak256(abi.encodePacked(preimageHash, lockupAmount, address(token), claimAddress, address(this), timelock)) + keccak256( + abi.encodePacked(preimageHash, lockupAmount, address(token), claimAddress, address(this), timelock) + ) ); } @@ -251,9 +253,11 @@ contract ERC20SwapTest is Test { badToken.approve(address(swap), lockupAmount); swap.lock(preimageHash, lockupAmount, address(badToken), claimAddress, timelock); - assertTrue(swap.swaps( - swap.hashValues(preimageHash, lockupAmount, address(badToken), claimAddress, address(this), timelock) - )); + assertTrue( + swap.swaps( + swap.hashValues(preimageHash, lockupAmount, address(badToken), claimAddress, address(this), timelock) + ) + ); // Check the balances to make sure tokens were transferred to the contract assertEq(badToken.balanceOf(address(swap)), lockupAmount); @@ -278,7 +282,9 @@ contract ERC20SwapTest is Test { vm.expectEmit(true, true, false, true, address(swap)); emit Lockup(preimageHash, lockupAmount, address(token), claimAddress, address(this), timelock); - swap.lockPrepayMinerfee{ value: prepayAmount }(preimageHash, lockupAmount, address(token), payable(claimAddress), timelock); + swap.lockPrepayMinerfee{value: prepayAmount}( + preimageHash, lockupAmount, address(token), payable(claimAddress), timelock + ); assertTrue(querySwap(timelock)); assertEq(claimAddress.balance, claimEthBalanceBefore + prepayAmount); diff --git a/contracts/test/EtherSwapFuzzTest.sol b/contracts/test/EtherSwapFuzzTest.sol new file mode 100644 index 0000000..ad46956 --- /dev/null +++ b/contracts/test/EtherSwapFuzzTest.sol @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.26; + +import "forge-std/Test.sol"; +import "./SigUtils.sol"; +import "../EtherSwap.sol"; + +contract EtherSwapFuzzTest is Test { + EtherSwap internal swap = new EtherSwap(); + + receive() external payable {} + + function testLock(uint256 amount, bytes32 preimageHash, address payable claimAddress, uint256 timelock) external { + vm.assume(amount < address(this).balance); + vm.assume(amount > 0); + + swap.lock{value: amount}(preimageHash, claimAddress, timelock); + assertTrue(swap.swaps(swap.hashValues(preimageHash, amount, claimAddress, address(this), timelock))); + } + + function testLockPrepayMinerFee( + uint256 amount, + uint256 prepayAmount, + bytes32 preimageHash, + address payable claimAddress, + uint256 timelock + ) external { + // Make sure that address can be paid + (bool claimAddressPayable,) = claimAddress.call{value: 1}(""); + vm.assume(claimAddressPayable); + + vm.assume(claimAddress != address(this)); + vm.assume(amount < address(this).balance); + vm.assume(amount > 0); + vm.assume(prepayAmount < amount); + + uint256 claimAddressBalanceBefore = claimAddress.balance; + + swap.lockPrepayMinerfee{value: amount}(preimageHash, claimAddress, timelock, prepayAmount); + assertTrue( + swap.swaps(swap.hashValues(preimageHash, amount - prepayAmount, claimAddress, address(this), timelock)) + ); + + assertEq(claimAddress.balance, claimAddressBalanceBefore + prepayAmount); + } + + function testLockClaim(uint256 amount, bytes32 preimage, address payable claimAddress, uint256 timelock) external { + // Make sure that address can be paid + (bool claimAddressPayable,) = claimAddress.call{value: 1}(""); + vm.assume(claimAddressPayable); + + vm.assume(amount < address(this).balance); + vm.assume(amount > 0); + + bytes32 preimageHash = sha256(abi.encodePacked(preimage)); + + swap.lock{value: amount}(preimageHash, claimAddress, timelock); + assertTrue(swap.swaps(swap.hashValues(preimageHash, amount, claimAddress, address(this), timelock))); + + uint256 claimAddressBalanceBefore = claimAddress.balance; + + vm.startPrank(claimAddress); + swap.claim(preimage, amount, address(this), timelock); + assertEq(claimAddress.balance, claimAddressBalanceBefore + amount); + + assertFalse(swap.swaps(swap.hashValues(preimageHash, amount, claimAddress, address(this), timelock))); + } + + function testLockClaimInvalidPreimage( + uint256 amount, + bytes32 preimage, + bytes32 invalidPreimage, + address payable claimAddress, + uint256 timelock + ) external { + vm.assume(amount < address(this).balance); + vm.assume(amount > 0); + + vm.assume(preimage != invalidPreimage); + + bytes32 preimageHash = sha256(abi.encodePacked(preimage)); + + swap.lock{value: amount}(preimageHash, claimAddress, timelock); + assertTrue(swap.swaps(swap.hashValues(preimageHash, amount, claimAddress, address(this), timelock))); + + vm.startPrank(claimAddress); + vm.expectRevert("EtherSwap: swap has no Ether locked in the contract"); + swap.claim(invalidPreimage, amount, address(this), timelock); + } + + function testLockRefund(uint256 amount, bytes32 preimageHash, address payable claimAddress, uint256 timelock) + external + { + vm.roll(6_573_352); + + vm.assume(amount < address(this).balance); + vm.assume(amount > 0); + + vm.assume(timelock <= block.number); + + swap.lock{value: amount}(preimageHash, claimAddress, timelock); + assertTrue(swap.swaps(swap.hashValues(preimageHash, amount, claimAddress, address(this), timelock))); + + uint256 thisBalanceBefore = address(this).balance; + + swap.refund(preimageHash, amount, claimAddress, timelock); + assertEq(address(this).balance, thisBalanceBefore + amount); + + assertFalse(swap.swaps(swap.hashValues(preimageHash, amount, claimAddress, address(this), timelock))); + } + + function testLockRefundTimelockNotPassed( + uint256 amount, + bytes32 preimageHash, + address payable claimAddress, + uint256 timelock + ) external { + vm.roll(6_573_352); + + vm.assume(amount < address(this).balance); + vm.assume(amount > 0); + + vm.assume(timelock > block.number); + + swap.lock{value: amount}(preimageHash, claimAddress, timelock); + assertTrue(swap.swaps(swap.hashValues(preimageHash, amount, claimAddress, address(this), timelock))); + + vm.expectRevert("EtherSwap: swap has not timed out yet"); + swap.refund(preimageHash, amount, claimAddress, timelock); + + assertTrue(swap.swaps(swap.hashValues(preimageHash, amount, claimAddress, address(this), timelock))); + } + + function testLockRefundCooperativeInvalidSignature( + uint256 amount, + bytes32 preimageHash, + address payable claimAddress, + uint256 timelock, + uint8 v, + bytes32 r, + bytes32 s + ) external { + vm.assume(amount < address(this).balance); + vm.assume(amount > 0); + + swap.lock{value: amount}(preimageHash, claimAddress, timelock); + assertTrue(swap.swaps(swap.hashValues(preimageHash, amount, claimAddress, address(this), timelock))); + + vm.expectRevert("EtherSwap: invalid signature"); + swap.refundCooperative(preimageHash, amount, claimAddress, timelock, v, r, s); + + assertTrue(swap.swaps(swap.hashValues(preimageHash, amount, claimAddress, address(this), timelock))); + } +} diff --git a/contracts/test/EtherSwapTest.sol b/contracts/test/EtherSwapTest.sol index 8d83a01..c5342e0 100644 --- a/contracts/test/EtherSwapTest.sol +++ b/contracts/test/EtherSwapTest.sol @@ -9,10 +9,10 @@ import "../EtherSwap.sol"; contract EtherSwapTest is Test { event Lockup( bytes32 indexed preimageHash, - uint amount, + uint256 amount, address claimAddress, address indexed refundAddress, - uint timelock + uint256 timelock ); event Claim(bytes32 indexed preimageHash, bytes32 preimage); @@ -33,14 +33,14 @@ contract EtherSwapTest is Test { sigUtils = new SigUtils(swap.DOMAIN_SEPARATOR(), swap.TYPEHASH_REFUND()); } - receive() payable external {} + receive() external payable {} function testCorrectVersion() external view { assertEq(swap.version(), 3); } function testNoSendEtherWithoutFunctionSig() external { - (bool success,) = address(swap).call{ value: 1 }(""); + (bool success,) = address(swap).call{value: 1}(""); require(!success); } @@ -193,9 +193,7 @@ contract EtherSwapTest is Test { (uint8 v, bytes32 r, bytes32 s) = vm.sign( claimAddressKey, - sigUtils.getTypedDataHash( - sigUtils.hashEtherSwapRefund(preimageHash, lockupAmount, claimAddress, timelock) - ) + sigUtils.getTypedDataHash(sigUtils.hashEtherSwapRefund(preimageHash, lockupAmount, claimAddress, timelock)) ); vm.expectEmit(true, false, false, false, address(swap)); @@ -229,7 +227,9 @@ contract EtherSwapTest is Test { vm.expectEmit(true, true, false, true, address(swap)); emit Lockup(preimageHash, lockupAmount, claimAddress, address(this), timelock); - swap.lockPrepayMinerfee{ value: lockupAmount + prepayAmount }(preimageHash, payable(claimAddress), timelock, prepayAmount); + swap.lockPrepayMinerfee{value: lockupAmount + prepayAmount}( + preimageHash, payable(claimAddress), timelock, prepayAmount + ); assertEq(address(swap).balance, lockupAmount); assertEq(claimAddress.balance, prepayAmount); @@ -239,21 +239,19 @@ contract EtherSwapTest is Test { function testLockupPrepayMinerFeeGtValueFail() external { vm.expectRevert("EtherSwap: sent amount must be greater than the prepay amount"); - swap.lockPrepayMinerfee{ value: 1 }(preimageHash, payable(claimAddress), block.number, 2); + swap.lockPrepayMinerfee{value: 1}(preimageHash, payable(claimAddress), block.number, 2); } function testLockupPrepayMinerFeeEqValueFail() external { vm.expectRevert("EtherSwap: sent amount must be greater than the prepay amount"); - swap.lockPrepayMinerfee{ value: 1 }(preimageHash, payable(claimAddress), block.number, 1); + swap.lockPrepayMinerfee{value: 1}(preimageHash, payable(claimAddress), block.number, 1); } function lock(uint256 timelock) internal { - swap.lock{ value: lockupAmount }(preimageHash, claimAddress, timelock); + swap.lock{value: lockupAmount}(preimageHash, claimAddress, timelock); } function querySwap(uint256 timelock) internal view returns (bool) { - return swap.swaps( - swap.hashValues(preimageHash, lockupAmount, claimAddress, address(this), timelock) - ); + return swap.swaps(swap.hashValues(preimageHash, lockupAmount, claimAddress, address(this), timelock)); } } diff --git a/contracts/test/SigUtils.sol b/contracts/test/SigUtils.sol index 1ec531c..651629e 100644 --- a/contracts/test/SigUtils.sol +++ b/contracts/test/SigUtils.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.26; contract SigUtils { - bytes32 immutable public DOMAIN_SEPARATOR; - bytes32 immutable public TYPEHASH; + bytes32 public immutable DOMAIN_SEPARATOR; + bytes32 public immutable TYPEHASH; constructor(bytes32 _DOMAIN_SEPARATOR, bytes32 _TYPEHASH) { DOMAIN_SEPARATOR = _DOMAIN_SEPARATOR; @@ -12,48 +12,24 @@ contract SigUtils { } function getTypedDataHash(bytes32 message) public view returns (bytes32) { - return keccak256( - abi.encodePacked( - "\x19\x01", - DOMAIN_SEPARATOR, - message - ) - ); + return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, message)); } - function hashEtherSwapRefund( - bytes32 preimageHash, - uint amount, - address claimAddress, - uint timelock - ) public view returns (bytes32) { - return keccak256( - abi.encode( - TYPEHASH, - preimageHash, - amount, - claimAddress, - timelock - ) - ); + function hashEtherSwapRefund(bytes32 preimageHash, uint256 amount, address claimAddress, uint256 timelock) + public + view + returns (bytes32) + { + return keccak256(abi.encode(TYPEHASH, preimageHash, amount, claimAddress, timelock)); } function hashERC20SwapRefund( bytes32 preimageHash, - uint amount, + uint256 amount, address tokenAddress, address claimAddress, - uint timelock + uint256 timelock ) public view returns (bytes32) { - return keccak256( - abi.encode( - TYPEHASH, - preimageHash, - amount, - tokenAddress, - claimAddress, - timelock - ) - ); + return keccak256(abi.encode(TYPEHASH, preimageHash, amount, tokenAddress, claimAddress, timelock)); } } diff --git a/foundry.toml b/foundry.toml index 4aaa6f5..0d85cef 100644 --- a/foundry.toml +++ b/foundry.toml @@ -7,3 +7,6 @@ gas_reports = ["EtherSwap", "ERC20Swap"] [profile.release] via-ir = true + +[fuzz] +runs = 8192 diff --git a/lib/swap/SwapUtils.ts b/lib/swap/SwapUtils.ts index c851a90..768da4a 100644 --- a/lib/swap/SwapUtils.ts +++ b/lib/swap/SwapUtils.ts @@ -3,7 +3,7 @@ */ import ops from '@boltz/bitcoin-ops'; import * as bip65 from 'bip65'; -import bip66 from 'bip66'; +import { encode as bip66Encode } from 'bip66'; import { script } from 'bitcoinjs-lib'; import Bn from 'bn.js'; import * as varuint from 'varuint-bitcoin'; @@ -32,7 +32,7 @@ const derEncode = (point: string) => { return zeroHexBuffer; } - x = x.slice(i); + x = x.subarray(i); if (x[0] & 0x80) { return Buffer.concat([zeroHexBuffer, x], x.length + 1); @@ -56,10 +56,12 @@ export const encodeSignature = (flag: number, signature: Buffer): Buffer => { const hashType = Buffer.from([flag]); - const r = derEncode(getHexString(signature.slice(0, pointSize))); - const s = derEncode(getHexString(signature.slice(pointSize, signatureEnd))); + const r = derEncode(getHexString(signature.subarray(0, pointSize))); + const s = derEncode( + getHexString(signature.subarray(pointSize, signatureEnd)), + ); - return Buffer.concat([bip66.encode(r, s), hashType]); + return Buffer.concat([bip66Encode(r, s), hashType]); }; /** diff --git a/package-lock.json b/package-lock.json index 43fecad..c4f8fd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@vulpemventures/secp256k1-zkp": "^3.2.1", "bip32": "^4.0.0", "bip65": "^1.0.3", - "bip66": "^1.1.5", + "bip66": "^2.0.0", "bitcoinjs-lib": "^6.1.6", "bn.js": "^5.2.1", "ecpair": "^2.1.0", @@ -24,8 +24,8 @@ "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@typechain/ethers-v6": "^0.5.1", "@types/jest": "^29.5.12", - "@types/node": "^20.14.7", - "@types/ws": "^8.5.10", + "@types/node": "^22.0.2", + "@types/ws": "^8.5.12", "@typescript-eslint/eslint-plugin": "^7.13.1", "@typescript-eslint/parser": "^7.13.1", "bip174": "^2.1.1", @@ -34,20 +34,20 @@ "eslint-plugin-import": "^2.29.1", "eslint-plugin-jest": "^28.6.0", "eslint-plugin-node": "^11.1.0", - "ethers": "^6.13.1", - "git-cliff": "^2.3.0", + "ethers": "^6.13.2", + "git-cliff": "^2.4.0", "jest": "^29.7.0", - "prettier": "^3.3.2", + "prettier": "^3.3.3", "slip77": "^0.2.0", "tiny-secp256k1": "^2.2.3", - "ts-jest": "^29.1.5", + "ts-jest": "^29.2.4", "ts-node": "^10.9.2", "typechain": "^8.3.2", - "typescript": "^5.5.2", - "ws": "^8.17.1" + "typescript": "^5.5.4", + "ws": "^8.18.0" }, "engines": { - "node": ">=14" + "node": ">=18" }, "peerDependencies": { "liquidjs-lib": "^6.0.2-liquid.35" @@ -1548,12 +1548,12 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.14.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.7.tgz", - "integrity": "sha512-uTr2m2IbJJucF3KUxgnGOZvYbN0QgkGyWxG6973HCpMYFy2KfcgYuIwkJQMQkt1VbBMlvWRbpshFTLxnxCZjKQ==", + "version": "22.0.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.0.2.tgz", + "integrity": "sha512-yPL6DyFwY5PiMVEwymNeqUTKsDczQBJ/5T7W/46RwLU/VH+AA8aT5TZkvBviLKLbbm0hlfftEkGrNzfRk/fofQ==", "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.11.1" } }, "node_modules/@types/prettier": { @@ -1578,10 +1578,11 @@ "dev": true }, "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -2101,6 +2102,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true, + "license": "MIT" + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -2263,12 +2271,10 @@ } }, "node_modules/bip66": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz", - "integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=", - "dependencies": { - "safe-buffer": "^5.0.1" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bip66/-/bip66-2.0.0.tgz", + "integrity": "sha512-kBG+hSpgvZBrkIm9dt5T1Hd/7xGCPEX2npoxAWZfsK1FvjgaxySEh2WizjyIstWXriKo9K9uJ4u0OnsyLDUPXQ==", + "license": "MIT" }, "node_modules/bitcoinjs-lib": { "version": "6.1.6", @@ -2918,6 +2924,22 @@ "node": ">=8.0.0" } }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.284", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", @@ -3508,9 +3530,9 @@ } }, "node_modules/ethers": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.1.tgz", - "integrity": "sha512-hdJ2HOxg/xx97Lm9HdCWk949BfYqYWpyw4//78SiwOLgASyfrNszfMUNB2joKjvGUdwhHfaiMMFFwacVVoLR9A==", + "version": "6.13.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.2.tgz", + "integrity": "sha512-9VkriTTed+/27BGuY1s0hf441kqwHJ1wtN2edksEtiRvXx+soxRX3iSXTfFqq2+YwrOqbDoTHjIhQnjJRlzKmg==", "dev": true, "funding": [ { @@ -3548,6 +3570,28 @@ "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", "dev": true }, + "node_modules/ethers/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -3661,6 +3705,39 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -3867,9 +3944,9 @@ } }, "node_modules/git-cliff": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/git-cliff/-/git-cliff-2.3.0.tgz", - "integrity": "sha512-ijM+ySxGFmkFsi4d6wv/JQKORHvZpqajdXxLlhYnR6EsiyQlEiwhzSmT20U4uzR0B2FWVdvUkfRqD0XiOHOe6w==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/git-cliff/-/git-cliff-2.4.0.tgz", + "integrity": "sha512-e+4mMArblL4mrD/auUTa3bf3U1ahAuhIRb1bxTIWCBxnR6UqVctGaLf3pJJufaKi3nqTT7JvrSYu9Q+L6yiAaw==", "dev": true, "license": "MIT OR Apache-2.0", "dependencies": { @@ -3882,18 +3959,18 @@ "node": ">=18.19 || >=20.6 || >=21" }, "optionalDependencies": { - "git-cliff-darwin-arm64": "2.3.0", - "git-cliff-darwin-x64": "2.3.0", - "git-cliff-linux-arm64": "2.3.0", - "git-cliff-linux-x64": "2.3.0", - "git-cliff-windows-arm64": "2.3.0", - "git-cliff-windows-x64": "2.3.0" + "git-cliff-darwin-arm64": "2.4.0", + "git-cliff-darwin-x64": "2.4.0", + "git-cliff-linux-arm64": "2.4.0", + "git-cliff-linux-x64": "2.4.0", + "git-cliff-windows-arm64": "2.4.0", + "git-cliff-windows-x64": "2.4.0" } }, "node_modules/git-cliff-darwin-arm64": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/git-cliff-darwin-arm64/-/git-cliff-darwin-arm64-2.3.0.tgz", - "integrity": "sha512-YT54HLsl0NA5RlCgX8zgoQGC2GBr9EkXWJt8UQRsXA8npQ5ozNU1s+PWZXd4H1gciKYGYNoGl2vGfcVt9hqFuw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/git-cliff-darwin-arm64/-/git-cliff-darwin-arm64-2.4.0.tgz", + "integrity": "sha512-KImSJhO8pTkKCauYlKmx7vNz3caIIBs0QW3JIS0uwUXGGJas4uuvGXlGTbLqVxdCur2yBwRDD6xobtuLC0yQ4w==", "cpu": [ "arm64" ], @@ -3905,9 +3982,9 @@ ] }, "node_modules/git-cliff-darwin-x64": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/git-cliff-darwin-x64/-/git-cliff-darwin-x64-2.3.0.tgz", - "integrity": "sha512-mHnW+gjaqXrl7BUMo8l66/BCRO0mCSI3GA/aOlKryw6mH+fU9Kcmz3sN03eSEjIhR4XmALyjrqFw+gOVs03R9A==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/git-cliff-darwin-x64/-/git-cliff-darwin-x64-2.4.0.tgz", + "integrity": "sha512-KtJ/V0i9xxs5iXl+hh1J4wyhMOrMRNylfSQC0lMJ+ScIIr1sZdF9qz4Mk06ZdJD7HnvPWawBhLcpCKVXkaeufQ==", "cpu": [ "x64" ], @@ -3919,9 +3996,9 @@ ] }, "node_modules/git-cliff-linux-arm64": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/git-cliff-linux-arm64/-/git-cliff-linux-arm64-2.3.0.tgz", - "integrity": "sha512-5ea+VW5k3NPXKQzcTjmXIygt/xEVIHbWMGfmhYzA+GarlJCvdQa+A1/xJ4F0vHPA4e7szXjeQrdHlRMzfWAJlQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/git-cliff-linux-arm64/-/git-cliff-linux-arm64-2.4.0.tgz", + "integrity": "sha512-ZSL5Jw06pgJ0F2e4Dv7j7l69qmq718NA8IKIUscjayc+71iuzyOw0T2tnbkV6H1h1rDjP9LMXGyPtLIvGz62BA==", "cpu": [ "arm64" ], @@ -3933,9 +4010,9 @@ ] }, "node_modules/git-cliff-linux-x64": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/git-cliff-linux-x64/-/git-cliff-linux-x64-2.3.0.tgz", - "integrity": "sha512-qT9XgG1lirXKiFwyfT0aqcPv8lsz6ghFGQIzYqXkEWjj20SuT1KgUqpA6ld+VVyJd+iXvYqoLZvtpsSJOeeGQg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/git-cliff-linux-x64/-/git-cliff-linux-x64-2.4.0.tgz", + "integrity": "sha512-1ckJ+2io52HuBnEeS87PeFanEPt3BuZoHKcXfkQPYFj9C91ckHgY7OCqze38ejy1V/cfKhlVJsbjbVMxixRaog==", "cpu": [ "x64" ], @@ -3947,9 +4024,9 @@ ] }, "node_modules/git-cliff-windows-arm64": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/git-cliff-windows-arm64/-/git-cliff-windows-arm64-2.3.0.tgz", - "integrity": "sha512-KiSU+1BvUB6b4rDr2AFHG5tV7nkNA8+b8psn9CJJAf5E5eBChtqlTcv0yx/SAZ6LaW1TFs0Rthn2Xr1yZbq68Q==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/git-cliff-windows-arm64/-/git-cliff-windows-arm64-2.4.0.tgz", + "integrity": "sha512-DZf+C7lTmCvvBjgtOHMcXJC5KaIL+QDE/C6cYxzLMgRcn9wGNTm2ot0jkAN2I1PmSmg4I1/89iiahSWHfG1m4A==", "cpu": [ "arm64" ], @@ -3961,9 +4038,9 @@ ] }, "node_modules/git-cliff-windows-x64": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/git-cliff-windows-x64/-/git-cliff-windows-x64-2.3.0.tgz", - "integrity": "sha512-cphU/S1yyT4Z4mm3//oF53JdUc64Bs6QbPqfGpqLeAROWrdXSy7BUHdCggjyB0GhN/ZM0RMWbQRLmRy/ADvOVQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/git-cliff-windows-x64/-/git-cliff-windows-x64-2.4.0.tgz", + "integrity": "sha512-bYUQIyG975KdPza/bNDxwBFpqTZEvF1zPKlhThgPgdCKdzbdezsgaFuaDbnM+vsmBD751ZJGWLQ7aJCSBG2s/Q==", "cpu": [ "x64" ], @@ -4766,6 +4843,25 @@ "node": ">=8" } }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/javascript-natural-sort": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", @@ -5524,6 +5620,16 @@ "node": ">=8.0.0" } }, + "node_modules/liquidjs-lib/node_modules/bip66": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz", + "integrity": "sha512-nemMHz95EmS38a26XbbdxIYj5csHd3RMP3H5bwQknX0WYHF01qhpufP42mLOwVICuH2JmhIhXiWs89MfUGL7Xw==", + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -6030,9 +6136,9 @@ } }, "node_modules/prettier": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", - "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, "license": "MIT", "bin": { @@ -6804,13 +6910,14 @@ } }, "node_modules/ts-jest": { - "version": "29.1.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.5.tgz", - "integrity": "sha512-UuClSYxM7byvvYfyWdFI+/2UxMmwNyJb0NPkZPQE2hew3RurV7l7zURgOHAd/1I1ZdPpe3GUsXNXAcN8TFKSIg==", + "version": "29.2.4", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.4.tgz", + "integrity": "sha512-3d6tgDyhCI29HlpwIq87sNuI+3Q6GLTTCeYRHCs7vDz+/3GCMwEtV9jezLyl4ZtnBgx00I7hm8PCP8cTksMGrw==", "dev": true, "license": "MIT", "dependencies": { "bs-logger": "0.x", + "ejs": "^3.1.10", "fast-json-stable-stringify": "2.x", "jest-util": "^29.0.0", "json5": "^2.2.3", @@ -7103,9 +7210,9 @@ "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" }, "node_modules/typescript": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", - "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, "license": "Apache-2.0", "bin": { @@ -7150,9 +7257,10 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.11.1.tgz", + "integrity": "sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ==", + "license": "MIT" }, "node_modules/universalify": { "version": "0.1.2", @@ -7357,10 +7465,11 @@ } }, "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index b3a3b92..25d19f9 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "url": "git+https://github.com/BoltzExchange/boltz-core.git" }, "engines": { - "node": ">=14" + "node": ">=18" }, "files": [ "LICENSE", @@ -54,7 +54,7 @@ "@vulpemventures/secp256k1-zkp": "^3.2.1", "bip32": "^4.0.0", "bip65": "^1.0.3", - "bip66": "^1.1.5", + "bip66": "^2.0.0", "bitcoinjs-lib": "^6.1.6", "bn.js": "^5.2.1", "ecpair": "^2.1.0", @@ -64,8 +64,8 @@ "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@typechain/ethers-v6": "^0.5.1", "@types/jest": "^29.5.12", - "@types/node": "^20.14.7", - "@types/ws": "^8.5.10", + "@types/node": "^22.0.2", + "@types/ws": "^8.5.12", "@typescript-eslint/eslint-plugin": "^7.13.1", "@typescript-eslint/parser": "^7.13.1", "bip174": "^2.1.1", @@ -74,17 +74,17 @@ "eslint-plugin-import": "^2.29.1", "eslint-plugin-jest": "^28.6.0", "eslint-plugin-node": "^11.1.0", - "ethers": "^6.13.1", - "git-cliff": "^2.3.0", + "ethers": "^6.13.2", + "git-cliff": "^2.4.0", "jest": "^29.7.0", - "prettier": "^3.3.2", + "prettier": "^3.3.3", "slip77": "^0.2.0", "tiny-secp256k1": "^2.2.3", - "ts-jest": "^29.1.5", + "ts-jest": "^29.2.4", "ts-node": "^10.9.2", "typechain": "^8.3.2", - "typescript": "^5.5.2", - "ws": "^8.17.1" + "typescript": "^5.5.4", + "ws": "^8.18.0" }, "peerDependencies": { "liquidjs-lib": "^6.0.2-liquid.35"