From 1b1059e9eb44868a55d41df830547981fd528afd Mon Sep 17 00:00:00 2001 From: yash1io Date: Fri, 7 Jun 2024 11:13:52 +0530 Subject: [PATCH 1/3] feat : added instant refund --- contracts/htlc/GardenHTLC.sol | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/contracts/htlc/GardenHTLC.sol b/contracts/htlc/GardenHTLC.sol index d674e66..7f76994 100644 --- a/contracts/htlc/GardenHTLC.sol +++ b/contracts/htlc/GardenHTLC.sol @@ -35,6 +35,8 @@ contract GardenHTLC is EIP712 { bytes32 private constant _INITIATE_TYPEHASH = keccak256("Initiate(address redeemer,uint256 expiry,uint256 amount,bytes32 secretHash)"); + bytes32 private constant _REFUND_TYPEHASH = keccak256("Refund(bytes32 orderId)"); + event Initiated(bytes32 indexed orderID, bytes32 indexed secretHash, uint256 amount); event Redeemed(bytes32 indexed orderID, bytes32 indexed secretHash, bytes secret); event Refunded(bytes32 indexed orderID); @@ -195,4 +197,26 @@ contract GardenHTLC is EIP712 { token.safeTransferFrom(initiator_, address(this), orders[orderID].amount); } + + /** + * @notice Redeemers can let initiator refund the locked assets before expiry block number + * @dev Signers cannot refund the the same order multiple times. + * Funds will be SafeTransferred to the initiator. + * + * @param orderID orderID of the htlc order + * @param signature EIP712 signature provided by redeemer for instant refund. + */ + function instantRefund(bytes32 orderID, bytes calldata signature) external { + address redeemer = _hashTypedDataV4(keccak256(abi.encode(_REFUND_TYPEHASH, orderID))).recover(signature); + Order storage order = orders[orderID]; + + require(order.redeemer == redeemer, "HTLC: invalid redeemer signature"); + require(!order.isFulfilled, "HTLC: order fulfilled"); + + order.isFulfilled = true; + + emit Refunded(orderID); + + token.safeTransfer(order.initiator, order.amount); + } } From ee97f26b99d9447e3c5860120a49fc8e083b6c1e Mon Sep 17 00:00:00 2001 From: yash1io Date: Fri, 7 Jun 2024 11:55:27 +0530 Subject: [PATCH 2/3] added tests for instant refund --- test/htlc/gardenHTLC.test.ts | 72 ++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/test/htlc/gardenHTLC.test.ts b/test/htlc/gardenHTLC.test.ts index 132bbf7..e4461f9 100644 --- a/test/htlc/gardenHTLC.test.ts +++ b/test/htlc/gardenHTLC.test.ts @@ -21,7 +21,7 @@ describe("--- HTLC ---", () => { secretHash: BytesLike; }; - const TYPES: Record = { + const INITIATE_TYPE: Record = { Initiate: [ { name: "redeemer", type: "address" }, { name: "expiry", type: "uint256" }, @@ -30,6 +30,10 @@ describe("--- HTLC ---", () => { ], }; + const REFUND_TYPE: Record = { + Refund: [{ name: "orderId", type: "bytes32" }], + }; + let owner: HardhatEthersSigner; let alice: HardhatEthersSigner; let bob: HardhatEthersSigner; @@ -45,12 +49,14 @@ describe("--- HTLC ---", () => { let secret3: BytesLike; let secret4: BytesLike; let secret5: BytesLike; + let secret6: BytesLike; let orderID1: BytesLike; let orderID2: BytesLike; let orderID3: BytesLike; let orderID4: BytesLike; let orderID5: BytesLike; + let orderID6: BytesLike; let expiry_: BigNumberish; @@ -74,6 +80,7 @@ describe("--- HTLC ---", () => { secret3 = randomBytes(32); secret4 = randomBytes(32); secret5 = randomBytes(32); + secret6 = randomBytes(32); }); describe("- Pre-Conditions -", () => { @@ -359,7 +366,7 @@ describe("--- HTLC ---", () => { secretHash: ethers.sha256(secret5), }; - const signature = await bob.signTypedData(DOMAIN, TYPES, initiate); + const signature = await bob.signTypedData(DOMAIN, INITIATE_TYPE, initiate); await expect( gardenHTLC @@ -383,7 +390,7 @@ describe("--- HTLC ---", () => { secretHash: ethers.sha256(secret5), }; - const signature = await alice.signTypedData(DOMAIN, TYPES, initiate); + const signature = await alice.signTypedData(DOMAIN, INITIATE_TYPE, initiate); await expect( gardenHTLC @@ -415,7 +422,7 @@ describe("--- HTLC ---", () => { ) ); - const signature = await alice.signTypedData(DOMAIN, TYPES, initiate); + const signature = await alice.signTypedData(DOMAIN, INITIATE_TYPE, initiate); await expect( gardenHTLC @@ -533,4 +540,61 @@ describe("--- HTLC ---", () => { expect(await seed.balanceOf(alice.address)).to.equal(ethers.parseEther("500")); }); }); + + describe("- HTLC - Instant Refund -", () => { + let instantRefund: { + orderId: string; + }; + it("Should not able to instant refund a swap with an invalid signature.", async () => { + expiry_ = (await ethers.provider.getBlockNumber()) + 7200; + const initiate: Initiate = { + redeemer: bob.address, + expiry: expiry_, + amount: ethers.parseEther("100"), + secretHash: ethers.sha256(secret6), + }; + + orderID6 = ethers.sha256( + ethers.AbiCoder.defaultAbiCoder().encode( + ["bytes32", "address"], + [initiate.secretHash, alice.address] + ) + ); + await expect( + gardenHTLC + .connect(alice) + .initiate( + initiate.redeemer, + initiate.expiry, + initiate.amount, + initiate.secretHash + ) + ) + .to.emit(gardenHTLC, "Initiated") + .withArgs(orderID6, initiate.secretHash, initiate.amount); + + instantRefund = { orderId: orderID6 }; + + const instantRefundSig = await alice.signTypedData( + DOMAIN, + REFUND_TYPE, + instantRefund + ); + + await expect( + gardenHTLC.connect(charlie).instantRefund(orderID6, instantRefundSig) + ).to.be.revertedWith("HTLC: invalid redeemer signature"); + }); + it("Should not able to instant refund a swap with an valid signature.", async () => { + const instantRefundSig = await bob.signTypedData( + DOMAIN, + REFUND_TYPE, + instantRefund + ); + + await expect(gardenHTLC.connect(charlie).instantRefund(orderID6, instantRefundSig)) + .to.emit(gardenHTLC, "Refunded") + .withArgs(orderID6); + }); + }); }); From ff1593de6682ab5b97b31e4d303a78becc35c143 Mon Sep 17 00:00:00 2001 From: yash1io Date: Fri, 7 Jun 2024 12:04:54 +0530 Subject: [PATCH 3/3] fix test case description --- test/htlc/gardenHTLC.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/htlc/gardenHTLC.test.ts b/test/htlc/gardenHTLC.test.ts index e4461f9..9c246fb 100644 --- a/test/htlc/gardenHTLC.test.ts +++ b/test/htlc/gardenHTLC.test.ts @@ -585,7 +585,7 @@ describe("--- HTLC ---", () => { gardenHTLC.connect(charlie).instantRefund(orderID6, instantRefundSig) ).to.be.revertedWith("HTLC: invalid redeemer signature"); }); - it("Should not able to instant refund a swap with an valid signature.", async () => { + it("Should be able to instant refund a swap with an valid signature.", async () => { const instantRefundSig = await bob.signTypedData( DOMAIN, REFUND_TYPE,