Skip to content

Commit

Permalink
Merge pull request #8 from gardenfi/feat/instantRefund
Browse files Browse the repository at this point in the history
Instant Refund
  • Loading branch information
susruth authored Jun 7, 2024
2 parents 16eb2a9 + ff1593d commit 9264b4c
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 4 deletions.
24 changes: 24 additions & 0 deletions contracts/htlc/GardenHTLC.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
}
72 changes: 68 additions & 4 deletions test/htlc/gardenHTLC.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe("--- HTLC ---", () => {
secretHash: BytesLike;
};

const TYPES: Record<string, TypedDataField[]> = {
const INITIATE_TYPE: Record<string, TypedDataField[]> = {
Initiate: [
{ name: "redeemer", type: "address" },
{ name: "expiry", type: "uint256" },
Expand All @@ -30,6 +30,10 @@ describe("--- HTLC ---", () => {
],
};

const REFUND_TYPE: Record<string, TypedDataField[]> = {
Refund: [{ name: "orderId", type: "bytes32" }],
};

let owner: HardhatEthersSigner;
let alice: HardhatEthersSigner;
let bob: HardhatEthersSigner;
Expand All @@ -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;

Expand All @@ -74,6 +80,7 @@ describe("--- HTLC ---", () => {
secret3 = randomBytes(32);
secret4 = randomBytes(32);
secret5 = randomBytes(32);
secret6 = randomBytes(32);
});

describe("- Pre-Conditions -", () => {
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 be 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);
});
});
});

0 comments on commit 9264b4c

Please sign in to comment.