irongit
defi_property/test/HomeTransaction.test.js
350 lines13 KBJavaScript
1const { expect } = require("chai");
2const { ethers } = require("hardhat");
3
4describe("HomeTransaction", function () {
5 let HomeTransaction;
6 let contract;
7 let realtor, seller, buyer, stranger;
8
9 // Test property details
10 const homeAddress = "123 Main Street";
11 const zip = "90210";
12 const city = "Beverly Hills";
13 const price = ethers.utils.parseEther("10"); // 10 ETH
14 const realtorFee = ethers.utils.parseEther("0.5"); // 0.5 ETH
15
16 // Contract states (matching enum order)
17 const States = {
18 WaitingSellerSignature: 0,
19 WaitingBuyerSignature: 1,
20 WaitingRealtorReview: 2,
21 WaitingFinalization: 3,
22 Finalized: 4,
23 Rejected: 5
24 };
25
26 beforeEach(async function () {
27 [realtor, seller, buyer, stranger] = await ethers.getSigners();
28
29 HomeTransaction = await ethers.getContractFactory("HomeTransaction");
30 contract = await HomeTransaction.deploy(
31 homeAddress,
32 zip,
33 city,
34 realtorFee,
35 price,
36 realtor.address,
37 seller.address,
38 buyer.address
39 );
40 await contract.deployed();
41 });
42
43 describe("Deployment", function () {
44 it("should set correct initial state", async function () {
45 expect(await contract.contractState()).to.equal(States.WaitingSellerSignature);
46 });
47
48 it("should set correct property details", async function () {
49 expect(await contract.homeAddress()).to.equal(homeAddress);
50 expect(await contract.zip()).to.equal(zip);
51 expect(await contract.city()).to.equal(city);
52 expect(await contract.price()).to.equal(price);
53 expect(await contract.realtorFee()).to.equal(realtorFee);
54 });
55
56 it("should set correct roles", async function () {
57 expect(await contract.realtor()).to.equal(realtor.address);
58 expect(await contract.seller()).to.equal(seller.address);
59 expect(await contract.buyer()).to.equal(buyer.address);
60 });
61
62 it("should reject if price < realtor fee", async function () {
63 await expect(
64 HomeTransaction.deploy(
65 homeAddress, zip, city,
66 ethers.utils.parseEther("2"), // realtorFee
67 ethers.utils.parseEther("1"), // price (less than fee)
68 realtor.address, seller.address, buyer.address
69 )
70 ).to.be.revertedWith("Price must be >= realtor fee");
71 });
72
73 it("should reject zero addresses", async function () {
74 await expect(
75 HomeTransaction.deploy(
76 homeAddress, zip, city, realtorFee, price,
77 ethers.constants.AddressZero, seller.address, buyer.address
78 )
79 ).to.be.revertedWith("Invalid realtor address");
80 });
81 });
82
83 describe("Seller Signing", function () {
84 it("should allow seller to sign", async function () {
85 await expect(contract.connect(seller).sellerSignContract())
86 .to.emit(contract, "SellerSigned")
87 .withArgs(seller.address);
88
89 expect(await contract.contractState()).to.equal(States.WaitingBuyerSignature);
90 });
91
92 it("should reject non-seller from signing", async function () {
93 await expect(
94 contract.connect(buyer).sellerSignContract()
95 ).to.be.revertedWith("Only seller can call");
96 });
97
98 it("should reject signing in wrong state", async function () {
99 await contract.connect(seller).sellerSignContract();
100 await expect(
101 contract.connect(seller).sellerSignContract()
102 ).to.be.revertedWith("Invalid contract state");
103 });
104 });
105
106 describe("Buyer Signing & Deposit", function () {
107 beforeEach(async function () {
108 await contract.connect(seller).sellerSignContract();
109 });
110
111 it("should allow buyer to sign with 10% deposit", async function () {
112 const deposit = price.mul(10).div(100); // 10%
113
114 await expect(
115 contract.connect(buyer).buyerSignContractAndPayDeposit({ value: deposit })
116 ).to.emit(contract, "BuyerSignedAndDeposited")
117 .withArgs(buyer.address, deposit);
118
119 expect(await contract.contractState()).to.equal(States.WaitingRealtorReview);
120 expect(await contract.deposit()).to.equal(deposit);
121 });
122
123 it("should allow buyer to sign with 100% deposit", async function () {
124 await contract.connect(buyer).buyerSignContractAndPayDeposit({ value: price });
125 expect(await contract.deposit()).to.equal(price);
126 });
127
128 it("should reject deposit less than 10%", async function () {
129 const lowDeposit = price.mul(9).div(100); // 9%
130
131 await expect(
132 contract.connect(buyer).buyerSignContractAndPayDeposit({ value: lowDeposit })
133 ).to.be.revertedWith("Deposit must be 10-100% of price");
134 });
135
136 it("should reject deposit more than 100%", async function () {
137 const highDeposit = price.mul(101).div(100); // 101%
138
139 await expect(
140 contract.connect(buyer).buyerSignContractAndPayDeposit({ value: highDeposit })
141 ).to.be.revertedWith("Deposit must be 10-100% of price");
142 });
143
144 it("should reject non-buyer from signing", async function () {
145 await expect(
146 contract.connect(stranger).buyerSignContractAndPayDeposit({ value: price.div(10) })
147 ).to.be.revertedWith("Only buyer can call");
148 });
149
150 it("should set finalize deadline", async function () {
151 const deposit = price.mul(10).div(100);
152 await contract.connect(buyer).buyerSignContractAndPayDeposit({ value: deposit });
153
154 const deadline = await contract.finalizeDeadline();
155 expect(deadline).to.be.gt(0);
156 });
157 });
158
159 describe("Realtor Review", function () {
160 const deposit = ethers.utils.parseEther("1"); // 10%
161
162 beforeEach(async function () {
163 await contract.connect(seller).sellerSignContract();
164 await contract.connect(buyer).buyerSignContractAndPayDeposit({ value: deposit });
165 });
166
167 it("should allow realtor to accept", async function () {
168 await expect(
169 contract.connect(realtor).realtorReviewClosingConditions(true)
170 ).to.emit(contract, "RealtorReviewed")
171 .withArgs(realtor.address, true);
172
173 expect(await contract.contractState()).to.equal(States.WaitingFinalization);
174 });
175
176 it("should allow realtor to reject and refund buyer", async function () {
177 const buyerBalanceBefore = await buyer.getBalance();
178
179 await contract.connect(realtor).realtorReviewClosingConditions(false);
180
181 expect(await contract.contractState()).to.equal(States.Rejected);
182
183 const buyerBalanceAfter = await buyer.getBalance();
184 expect(buyerBalanceAfter.sub(buyerBalanceBefore)).to.equal(deposit);
185 });
186
187 it("should reject non-realtor from reviewing", async function () {
188 await expect(
189 contract.connect(seller).realtorReviewClosingConditions(true)
190 ).to.be.revertedWith("Only realtor can call");
191 });
192 });
193
194 describe("Transaction Finalization", function () {
195 const deposit = ethers.utils.parseEther("1"); // 10%
196 const remainingBalance = price.sub(deposit);
197
198 beforeEach(async function () {
199 await contract.connect(seller).sellerSignContract();
200 await contract.connect(buyer).buyerSignContractAndPayDeposit({ value: deposit });
201 await contract.connect(realtor).realtorReviewClosingConditions(true);
202 });
203
204 it("should allow buyer to finalize with correct payment", async function () {
205 const sellerBalanceBefore = await seller.getBalance();
206 const realtorBalanceBefore = await realtor.getBalance();
207
208 await expect(
209 contract.connect(buyer).buyerFinalizeTransaction({ value: remainingBalance })
210 ).to.emit(contract, "TransactionFinalized")
211 .withArgs(buyer.address, price);
212
213 expect(await contract.contractState()).to.equal(States.Finalized);
214
215 // Check payments
216 const sellerBalanceAfter = await seller.getBalance();
217 const realtorBalanceAfter = await realtor.getBalance();
218
219 expect(sellerBalanceAfter.sub(sellerBalanceBefore)).to.equal(price.sub(realtorFee));
220 expect(realtorBalanceAfter.sub(realtorBalanceBefore)).to.equal(realtorFee);
221 });
222
223 it("should reject incorrect payment amount", async function () {
224 const wrongAmount = remainingBalance.sub(1);
225
226 await expect(
227 contract.connect(buyer).buyerFinalizeTransaction({ value: wrongAmount })
228 ).to.be.revertedWith("Must pay remaining balance");
229 });
230
231 it("should reject non-buyer from finalizing", async function () {
232 await expect(
233 contract.connect(stranger).buyerFinalizeTransaction({ value: remainingBalance })
234 ).to.be.revertedWith("Only buyer can call");
235 });
236 });
237
238 describe("Withdrawal", function () {
239 const deposit = ethers.utils.parseEther("1"); // 10%
240
241 beforeEach(async function () {
242 await contract.connect(seller).sellerSignContract();
243 await contract.connect(buyer).buyerSignContractAndPayDeposit({ value: deposit });
244 await contract.connect(realtor).realtorReviewClosingConditions(true);
245 });
246
247 it("should allow buyer to withdraw before deadline", async function () {
248 const sellerBalanceBefore = await seller.getBalance();
249 const realtorBalanceBefore = await realtor.getBalance();
250
251 await expect(
252 contract.connect(buyer).withdrawFromTransaction()
253 ).to.emit(contract, "TransactionWithdrawn")
254 .withArgs(buyer.address);
255
256 expect(await contract.contractState()).to.equal(States.Rejected);
257
258 // Seller gets deposit minus realtor fee
259 const sellerBalanceAfter = await seller.getBalance();
260 expect(sellerBalanceAfter.sub(sellerBalanceBefore)).to.equal(deposit.sub(realtorFee));
261
262 // Realtor still gets fee
263 const realtorBalanceAfter = await realtor.getBalance();
264 expect(realtorBalanceAfter.sub(realtorBalanceBefore)).to.equal(realtorFee);
265 });
266
267 it("should allow anyone to withdraw after deadline", async function () {
268 // Fast forward time past deadline (5 minutes)
269 await ethers.provider.send("evm_increaseTime", [6 * 60]); // 6 minutes
270 await ethers.provider.send("evm_mine");
271
272 await expect(
273 contract.connect(stranger).withdrawFromTransaction()
274 ).to.emit(contract, "TransactionWithdrawn");
275
276 expect(await contract.contractState()).to.equal(States.Rejected);
277 });
278
279 it("should reject stranger withdrawal before deadline", async function () {
280 await expect(
281 contract.connect(stranger).withdrawFromTransaction()
282 ).to.be.revertedWith("Only buyer can withdraw before deadline");
283 });
284 });
285
286 describe("View Functions", function () {
287 it("should return correct contract details", async function () {
288 const details = await contract.getContractDetails();
289
290 expect(details._homeAddress).to.equal(homeAddress);
291 expect(details._zip).to.equal(zip);
292 expect(details._city).to.equal(city);
293 expect(details._price).to.equal(price);
294 expect(details._realtorFee).to.equal(realtorFee);
295 expect(details._state).to.equal(States.WaitingSellerSignature);
296 });
297
298 it("should return correct remaining balance", async function () {
299 // Before deposit, should be 0
300 expect(await contract.getRemainingBalance()).to.equal(0);
301
302 // After going through flow
303 await contract.connect(seller).sellerSignContract();
304 const deposit = price.mul(10).div(100);
305 await contract.connect(buyer).buyerSignContractAndPayDeposit({ value: deposit });
306 await contract.connect(realtor).realtorReviewClosingConditions(true);
307
308 expect(await contract.getRemainingBalance()).to.equal(price.sub(deposit));
309 });
310 });
311
312 describe("Full Transaction Flow", function () {
313 it("should complete full happy path", async function () {
314 const deposit = price.mul(20).div(100); // 20% deposit
315 const remaining = price.sub(deposit);
316
317 // Track balances AFTER seller signs (to avoid gas cost accounting)
318 // 1. Seller signs
319 await contract.connect(seller).sellerSignContract();
320 expect(await contract.contractState()).to.equal(States.WaitingBuyerSignature);
321
322 // Now track balances
323 const sellerInitial = await seller.getBalance();
324 const realtorInitial = await realtor.getBalance();
325
326 // 2. Buyer deposits
327 await contract.connect(buyer).buyerSignContractAndPayDeposit({ value: deposit });
328 expect(await contract.contractState()).to.equal(States.WaitingRealtorReview);
329
330 // 3. Realtor approves
331 await contract.connect(realtor).realtorReviewClosingConditions(true);
332 expect(await contract.contractState()).to.equal(States.WaitingFinalization);
333
334 // 4. Buyer finalizes
335 await contract.connect(buyer).buyerFinalizeTransaction({ value: remaining });
336 expect(await contract.contractState()).to.equal(States.Finalized);
337
338 // Verify final balances (seller receives price minus fee, no gas spent after initial)
339 const sellerFinal = await seller.getBalance();
340 const realtorFinal = await realtor.getBalance();
341
342 expect(sellerFinal.sub(sellerInitial)).to.equal(price.sub(realtorFee));
343 // Realtor spent gas on review, so check they received at least the fee
344 expect(realtorFinal.sub(realtorInitial)).to.be.closeTo(
345 realtorFee,
346 ethers.utils.parseEther("0.001") // Allow 0.001 ETH for gas
347 );
348 });
349 });
350});