punkkit-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/.env +47 -0
  2. package/.gitmodules +3 -0
  3. package/README.md +158 -0
  4. package/config/auction.config.ts +40 -0
  5. package/config/env.config.ts +204 -0
  6. package/config/uniswap.config.ts +107 -0
  7. package/config/voucher.config.ts +10 -0
  8. package/contracts/MutiVoucher.sol +78 -0
  9. package/contracts/auction/ChainXAuction.sol +177 -0
  10. package/contracts/auction/ChainXAuctionV2.sol +672 -0
  11. package/contracts/auction/ChainXWrappedETH.sol +80 -0
  12. package/contracts/auction/ChainYLiquidityManager.sol +57 -0
  13. package/contracts/auction/ChainYShadowETH.sol +148 -0
  14. package/contracts/auction/ChainYVault.sol +195 -0
  15. package/contracts/auction/ChainYVaultCoinbase.sol +276 -0
  16. package/contracts/auction/ChainYVaultV2.sol +318 -0
  17. package/contracts/auction/coinbase-and-stake/README.md +55 -0
  18. package/contracts/auction/coinbase-and-stake/coinbase.sol +142 -0
  19. package/contracts/auction/coinbase-and-stake/invokeCoinbase.sol +159 -0
  20. package/contracts/auction/coinbase-and-stake/invokeStake.sol +82 -0
  21. package/contracts/auction/coinbase-and-stake/stake.sol +92 -0
  22. package/contracts/auction/interfaces/IUniswapV2Factory.sol +15 -0
  23. package/contracts/auction/interfaces/IUniswapV2Pair.sol +53 -0
  24. package/contracts/auction/interfaces/IUniswapV2Router02.sol +25 -0
  25. package/contracts/auction/interfaces/IUnlockStrategy.sol +18 -0
  26. package/contracts/auction/libraries/EventParser.sol +32 -0
  27. package/contracts/auction/libraries/TransactionParser.sol +70 -0
  28. package/contracts/auction/strategies/MatchResultWithdrawnStrategy.sol +33 -0
  29. package/contracts/auction/utils/BytesLib.sol +180 -0
  30. package/contracts/auction/utils/RLPReader.sol +355 -0
  31. package/contracts/uniswap/Create2.sol +80 -0
  32. package/contracts/uniswap/DynamicFee.sol +100 -0
  33. package/contracts/uniswap/Example.sol +35 -0
  34. package/contracts/uniswap/HookMiner.sol +52 -0
  35. package/contracts/uniswap/LimitOrder.sol +486 -0
  36. package/contracts/uniswap/LiquidPool.sol +179 -0
  37. package/contracts/uniswap/MockERC20.sol +20 -0
  38. package/hardhat.config.ts +35 -0
  39. package/ignition/modules/LimitOrder.ts +33 -0
  40. package/package.json +32 -0
  41. package/scripts/auction/deploy.ts +23 -0
  42. package/scripts/auction/deployCoinbase.ts +21 -0
  43. package/scripts/auction/deployXAuction.ts +23 -0
  44. package/scripts/auction/deployYVault.ts +22 -0
  45. package/scripts/deploy_voucher.ts +20 -0
  46. package/scripts/uniswap/deploy/deploy.ts +65 -0
  47. package/scripts/uniswap/deploy/deploy_create2.ts +11 -0
  48. package/scripts/uniswap/deploy/deploy_example.ts +35 -0
  49. package/scripts/uniswap/deploy/deploy_hooks.ts +74 -0
  50. package/scripts/uniswap/deploy/deploy_mockERC20.ts +42 -0
  51. package/scripts/uniswap/deploy/help.ts +96 -0
  52. package/scripts/uniswap/deploy/init.ts +70 -0
  53. package/src/auction/chainXAuction.ts +209 -0
  54. package/src/auction/chainYVault.ts +153 -0
  55. package/src/auction/event.ts +19 -0
  56. package/src/auction/serialize.ts +162 -0
  57. package/src/auction/type.ts +71 -0
  58. package/src/lib/signer.ts +20 -0
  59. package/src/lib/unlock.ts +14 -0
  60. package/src/uniswap/1-marketprice/addLiquidity.ts +80 -0
  61. package/src/uniswap/1-marketprice/removeLiquidity.ts +63 -0
  62. package/src/uniswap/1-marketprice/swap.ts +100 -0
  63. package/src/uniswap/2-limitorder/kill.ts +70 -0
  64. package/src/uniswap/2-limitorder/place.ts +93 -0
  65. package/src/uniswap/2-limitorder/withdraw.ts +78 -0
  66. package/src/uniswap/3-dynamicfee/dynamicfee.ts +321 -0
  67. package/src/uniswap/lib/ERC20.ts +49 -0
  68. package/src/uniswap/lib/contract.ts +18 -0
  69. package/src/uniswap/lib/limitOrder.ts +40 -0
  70. package/src/uniswap/lib/liqCalculation.ts +152 -0
  71. package/src/uniswap/lib/listen.ts +57 -0
  72. package/src/uniswap/lib/pool.ts +62 -0
  73. package/src/uniswap/lib/swap.ts +8 -0
  74. package/src/uniswap/lib/types.ts +21 -0
  75. package/src/uniswap/lib/utils.ts +26 -0
  76. package/src/uniswap/playgroud/abiencode.ts +21 -0
  77. package/src/uniswap/playgroud/amount0.ts +47 -0
  78. package/src/uniswap/playgroud/errordecode.ts +54 -0
  79. package/src/uniswap/playgroud/errorsigs.ts +86 -0
  80. package/src/voucher.ts +122 -0
  81. package/test/auction/ChainXAuctionV2.test.ts +265 -0
  82. package/test/auction/ChainYVaultV2.test.js +163 -0
  83. package/test/auction/ChainYVaultV2.test.ts +183 -0
  84. package/test/auction/auction.test.ts +106 -0
  85. package/test/connect_punk.test.ts +26 -0
  86. package/test/create2.test.ts +44 -0
  87. package/test/normal.ts +43 -0
  88. package/test/test-config.ts +18 -0
  89. package/test/uniswap/example.test.ts +62 -0
  90. package/test/uniswap/limitOrder.test.ts +184 -0
  91. package/test/uniswap/mockERC20.test.ts +142 -0
  92. package/test/voucher_hardhat.test.ts +120 -0
  93. package/test/voucher_punk.test.ts +83 -0
  94. package/tsconfig.json +11 -0
@@ -0,0 +1,265 @@
1
+
2
+ import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs";
3
+ import { getAuctionID, buildAuctionCreatedReceipt, buildAuctionCreatedTx } from "../../src/auction/serialize";
4
+
5
+ import { expect } from "chai";
6
+ import { ethers, network } from "hardhat";
7
+ import { Contract, Wallet } from "ethers";
8
+
9
+ describe("ChainXAuctionV2", function () {
10
+ let chainXAuction: Contract;
11
+ let owner: any;
12
+ let seller: any;
13
+ let sellerWallet: Wallet;
14
+ let bidder1: any;
15
+ let bidder2: any;
16
+ let vaultAddress: string;
17
+
18
+ // 测试数据
19
+ const auctionType = 0x00000001;
20
+ const sourceChainId = 31337;
21
+ const activeAuctionsCount = BigInt(0);
22
+ const gasPrice = ethers.utils.parseUnits("1", "gwei").toBigInt();
23
+ const gasLimit = BigInt(52_200);
24
+
25
+ beforeEach(async function () {
26
+ [owner, bidder1, bidder2] = await ethers.getSigners();
27
+ seller = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266";
28
+ let sellerPk = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
29
+ sellerWallet = new ethers.Wallet(sellerPk);
30
+
31
+ vaultAddress = "0x59b670e9fA9D0A427751Af201D676719a970857b";
32
+
33
+ const ChainXAuctionV2 = await ethers.getContractFactory("ChainXAuctionV2");
34
+ chainXAuction = await ChainXAuctionV2.deploy(vaultAddress);
35
+ await chainXAuction.deployed();
36
+ });
37
+
38
+ describe("拍卖创建", function () {
39
+ it("应该正确创建拍卖", async function () {
40
+ const nonce = await ethers.provider.getTransactionCount(seller);
41
+ const latestTs = (await ethers.provider.getBlock("latest")).timestamp;
42
+ const revealStartTime = latestTs + 1;
43
+
44
+ const auctionId: string = getAuctionID(seller, sourceChainId, vaultAddress, activeAuctionsCount);
45
+
46
+ // 构造链Y的createAuciton交易sellerWallet
47
+ const rawTx = await buildAuctionCreatedTx({nonce,gasPrice,gasLimit,to: vaultAddress,value: BigInt(0),data: "0x",chainId: sourceChainId});
48
+ const signedTx = await sellerWallet.signTransaction(rawTx);
49
+
50
+ const parsedTx = ethers.utils.parseTransaction(signedTx);
51
+ console.log("parsed from:", parsedTx.from, "v:", parsedTx.v, "chainId:", parsedTx.chainId,"txHash:",parsedTx.hash);
52
+
53
+ const debug = await chainXAuction.debugRecover(signedTx);
54
+ console.log(
55
+ "debug signer", debug[0],
56
+ "txHash", debug[1],
57
+ "chainIdSig", debug[2].toString(),
58
+ "vAdj", debug[3],
59
+ "nonce", debug[4].toString(),
60
+ "gasPrice", debug[5].toString(),
61
+ "gasLimit", debug[6].toString(),
62
+ "value", debug[7].toString(),
63
+ );
64
+
65
+
66
+ // 构造Tx对应的Receipt
67
+ const logAddress = "0x3078333838433831384341384239323531623339";
68
+ const rawRecpt = buildAuctionCreatedReceipt({
69
+ auctionId,
70
+ auctionType: Number(auctionType),
71
+ activeAuctionCount: BigInt(activeAuctionsCount),
72
+ revealTime: BigInt(revealStartTime)
73
+ });
74
+
75
+ const crossChainMessage = ethers.utils.defaultAbiCoder.encode(
76
+ ["tuple(uint256 sourceChainId, bytes rawTransaction, bytes rawRecpt)"],
77
+ [[sourceChainId, signedTx, rawRecpt]]
78
+ );
79
+
80
+ await expect(chainXAuction.createAuction(crossChainMessage))
81
+ .to.emit(chainXAuction, "AuctionCreated")
82
+ .withArgs(auctionId, auctionType, anyValue);
83
+ });
84
+ });
85
+
86
+ describe("投标流程", function () {
87
+ let auctionId: string;
88
+
89
+ beforeEach(async function () {
90
+ const nonce = await ethers.provider.getTransactionCount(seller);
91
+ const latestTs = (await ethers.provider.getBlock("latest")).timestamp;
92
+ const revealTime = BigInt(latestTs + 1);
93
+ let activeAuctionCount = BigInt(0);
94
+ auctionId = getAuctionID(seller, sourceChainId, vaultAddress, activeAuctionCount);
95
+
96
+ const rawTx = await buildAuctionCreatedTx({nonce,gasPrice,gasLimit,to: vaultAddress,value: BigInt(0),data: "0x",chainId: sourceChainId});
97
+ const signedTx= await sellerWallet.signTransaction(rawTx);
98
+
99
+ const logAddress = "0x307833383843383138434138423932353162333933313331433038613733364136376363423139323937";
100
+ const rawRecpt = buildAuctionCreatedReceipt({ auctionId, auctionType, activeAuctionCount, revealTime });
101
+ const crossChainMessage = ethers.utils.defaultAbiCoder.encode(
102
+ ["tuple(uint256 sourceChainId, bytes rawTransaction, bytes rawRecpt)"],
103
+ [[sourceChainId, signedTx, rawRecpt]]
104
+ );
105
+ await chainXAuction.createAuction(crossChainMessage);
106
+ });
107
+
108
+ it("应该正确提交投标和揭示", async function () {
109
+ // 准备投标数据
110
+ const value = ethers.utils.parseEther("1.0");
111
+ const salt = ethers.utils.randomBytes(32);
112
+
113
+ // 公开竞价
114
+ const bidValue = ethers.utils.hexZeroPad(value.toHexString(), 32);
115
+
116
+ const secretHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("secret"));
117
+ const depositAmount = ethers.utils.parseEther("1.5");
118
+
119
+ // 提交投标
120
+ await expect(
121
+ chainXAuction.connect(bidder1).placeBid(
122
+ auctionId,
123
+ seller,
124
+ secretHash,
125
+ bidValue,
126
+ { value: depositAmount }
127
+ )
128
+ ).to.emit(chainXAuction, "BidPlaced")
129
+ .withArgs(auctionId, anyValue, bidder1.address, depositAmount);
130
+
131
+ // 等待揭示期
132
+ await network.provider.send("evm_increaseTime", [3600]);
133
+ await network.provider.send("evm_mine");
134
+
135
+ await expect(
136
+ chainXAuction.connect(bidder1).revealBid(
137
+ auctionId,
138
+ value,
139
+ salt
140
+ )
141
+ ).to.emit(chainXAuction, "BidRevealed")
142
+ .withArgs(auctionId, bidder1.address, value);
143
+ });
144
+ });
145
+
146
+ describe("匹配结果管理", function () {
147
+ let auctionId: string;
148
+ let lockId: string;
149
+
150
+ beforeEach(async function () {
151
+ const latestTs = (await ethers.provider.getBlock("latest")).timestamp;
152
+ const revealTime = BigInt(latestTs + 1);
153
+ const activeAuctionCount = BigInt(0);
154
+ const nonce = await ethers.provider.getTransactionCount(seller);
155
+
156
+ auctionId = getAuctionID(seller, sourceChainId, vaultAddress, activeAuctionCount);
157
+
158
+ const rawTx = await buildAuctionCreatedTx({nonce,gasPrice,gasLimit,to: vaultAddress,value: BigInt(0),data: "0x",chainId: sourceChainId});
159
+ const signedTx = await sellerWallet.signTransaction(rawTx);
160
+ const logAddress = "0x + chainYaddress"
161
+ const rawRecpt = buildAuctionCreatedReceipt({
162
+ auctionId,
163
+ auctionType,
164
+ activeAuctionCount,
165
+ revealTime,
166
+ });
167
+
168
+ const crossChainMessage = ethers.utils.defaultAbiCoder.encode(
169
+ ["tuple(uint256 sourceChainId, bytes rawTransaction, bytes rawRecpt)"],
170
+ [[sourceChainId, signedTx, rawRecpt]]
171
+ );
172
+
173
+ await chainXAuction.createAuction(crossChainMessage);
174
+
175
+ // place one bid to create a lockId
176
+ const value = ethers.utils.parseEther("1.0");
177
+ const salt = ethers.utils.randomBytes(32);
178
+ const bidValue = ethers.utils.hexZeroPad(value.toHexString(), 32);
179
+ const secretHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("secret"));
180
+ const depositAmount = ethers.utils.parseEther("1.5");
181
+
182
+ await chainXAuction.connect(bidder1).placeBid(
183
+ auctionId,
184
+ seller,
185
+ secretHash,
186
+ bidValue,
187
+ { value: depositAmount }
188
+ );
189
+
190
+ lockId = ethers.utils.keccak256(
191
+ ethers.utils.solidityPack(
192
+ ["uint256", "address", "uint256", "address", "bytes32", "uint32"],
193
+ [sourceChainId, vaultAddress, auctionId, seller, secretHash, auctionType]
194
+ )
195
+ );
196
+
197
+ // fast-forward beyond reveal + bid periods so submitMatchResults is allowed
198
+ await network.provider.send("evm_increaseTime", [7200]);
199
+ await network.provider.send("evm_mine");
200
+ });
201
+
202
+ it("应该正确提交匹配结果", async function () {
203
+ const lockIds = [lockId];
204
+ const bidders = [await bidder1.address];
205
+ const finalValues = [ethers.utils.parseEther("1.0")];
206
+
207
+ await expect(
208
+ chainXAuction.submitMatchResults(
209
+ auctionId,
210
+ lockIds,
211
+ bidders,
212
+ finalValues
213
+ )
214
+ ).to.emit(chainXAuction, "MatchResultSubmitted")
215
+ .withArgs(auctionId, lockId, await bidder1.address, finalValues[0]);
216
+ });
217
+
218
+ // 该方法在合约层并没有完善流程,也没有业务代码,挑战主要适用于复杂拍卖逻辑
219
+ it("应该允许挑战匹配结果", async function () {
220
+ const lockIds = [lockId];
221
+ const bidders = [await bidder1.address];
222
+ const finalValues = [ethers.utils.parseEther("1.0")];
223
+
224
+ await chainXAuction.submitMatchResults(
225
+ auctionId,
226
+ lockIds,
227
+ bidders,
228
+ finalValues
229
+ );
230
+
231
+ await expect(
232
+ chainXAuction.connect(bidder2).challengeMatchResult(
233
+ auctionId,
234
+ await bidder1.address
235
+ )
236
+ ).to.emit(chainXAuction, "MatchResultChallenged")
237
+ .withArgs(auctionId, lockId, await bidder2.address, "");
238
+ });
239
+
240
+ it("应该正确处理提现请求", async function () {
241
+ const lockIds = [lockId];
242
+ const bidders = [await bidder1.address];
243
+ const finalValues = [ethers.utils.parseEther("1.0")];
244
+
245
+ await chainXAuction.submitMatchResults(
246
+ auctionId,
247
+ lockIds,
248
+ bidders,
249
+ finalValues
250
+ );
251
+
252
+ // fast-forward beyond challenge period (secret + bid + challenge = 3h)
253
+ await network.provider.send("evm_increaseTime", [3 * 3600 + 60]);
254
+ await network.provider.send("evm_mine");
255
+
256
+ await expect(
257
+ chainXAuction.connect(bidder1).withdrawMatchResult(
258
+ auctionId,
259
+ lockId
260
+ )
261
+ ).to.emit(chainXAuction, "MatchResultWithdrawn")
262
+ .withArgs(auctionId, lockId, vaultAddress, anyValue);
263
+ });
264
+ });
265
+ });
@@ -0,0 +1,163 @@
1
+ const { expect } = require("chai");
2
+ const { ethers } = require("hardhat");
3
+
4
+ describe("ChainYVaultV2", function() {
5
+ let chainYVault;
6
+ let owner;
7
+ let mockStrategy;
8
+
9
+ // 测试数据
10
+ const auctionType = 0x00000001; // 普通公开竞标
11
+ const baseAmount = ethers.utils.parseEther("1.0");
12
+
13
+ beforeEach(async function() {
14
+ [owner, seller] = await ethers.getSigners();
15
+ console.log("signers: ", owner.address, seller.address)
16
+
17
+ // 部署合约
18
+ const ChainYVaultV2 = await ethers.getContractFactory("ChainYVaultV2");
19
+ // Coinbase地址设置为0,用作本地测试.
20
+ chainYVault = await ChainYVaultV2.deploy(ethers.constants.AddressZero);
21
+ await chainYVault.deployed();
22
+ console.log("chainYVault: ", chainYVault.address)
23
+
24
+ // 部署模拟解锁策略合约
25
+ const MockUnlockStrategy = await ethers.getContractFactory("MatchResultWithdrawnStrategy");
26
+ mockStrategy = await MockUnlockStrategy.deploy();
27
+
28
+ // 设置解锁策略
29
+ await chainYVault.setUnlockStrategy(auctionType, await mockStrategy.address);
30
+ });
31
+
32
+ describe("竞拍配置管理", function() {
33
+ it("应该正确创建竞拍配置", async function() {
34
+ await chainYVault.createAuctionConfig(
35
+ auctionType,
36
+ baseAmount,
37
+ 86400 // 1天的扩展时间
38
+ );
39
+
40
+ const config = await chainYVault.auctionConfigs(0);
41
+ expect(config.auctionType).to.equal(auctionType);
42
+ expect(config.baseAmount).to.equal(baseAmount);
43
+ expect(config.isActive).to.be.true;
44
+ });
45
+
46
+ it("只有管理员可以创建竞拍配置", async function() {
47
+ await expect(
48
+ chainYVault.connect(seller).createAuctionConfig(
49
+ auctionType,
50
+ baseAmount,
51
+ 86400
52
+ )
53
+ ).to.be.revertedWithCustomError(chainYVault, "OwnableUnauthorizedAccount").withArgs(await seller.address);
54
+ // * 另一种写法
55
+ // try {
56
+ // await chainYVault.connect(seller).createAuctionConfig(auctionType, baseAmount, 86400);
57
+ // } catch (err) {
58
+ // console.log("errorName:", err.errorName); // OwnableUnauthorizedAccount
59
+ // console.log("errorSignature:", err.errorSignature); // OwnableUnauthorizedAccount(address)
60
+ // console.log("args:", err.args); // [seller.address]
61
+ // }
62
+ });
63
+ });
64
+
65
+ describe("竞拍创建与锁定", function() {
66
+ beforeEach(async function() {
67
+ await chainYVault.createAuctionConfig(
68
+ auctionType,
69
+ baseAmount,
70
+ 86400
71
+ );
72
+ });
73
+
74
+ it("应该正确创建竞拍并锁定资金", async function() {
75
+ const hashSecret = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("secret"));
76
+ const expiration = Math.floor(Date.now() / 1000) + 3600; // 1小时后
77
+
78
+ const tx = await chainYVault.connect(seller).createAuction(
79
+ 0, // configId
80
+ hashSecret,
81
+ expiration,
82
+ { value: baseAmount }
83
+ );
84
+
85
+ const receipt = await tx.wait();
86
+ const event = receipt.events?.find(e => e.event === "AuctionCreated");
87
+
88
+ expect(event).to.not.be.undefined;
89
+
90
+ const auctionId = event.args.auctionId;
91
+ const auction = await chainYVault.auctions(auctionId);
92
+ expect(auction.auctionType).to.equal(auctionType);
93
+ expect(auction.baseAmount).to.equal(baseAmount);
94
+ });
95
+
96
+ it("应该验证锁定金额是基础金额的整数倍", async function() {
97
+ const hashSecret = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("secret"));
98
+ const expiration = Math.floor(Date.now() / 1000) + 3600;
99
+
100
+ await expect(
101
+ chainYVault.connect(seller).createAuction(
102
+ 0,
103
+ hashSecret,
104
+ expiration,
105
+ { value: baseAmount + 1n } // 非整数倍金额
106
+ )
107
+ ).to.be.revertedWith("Amount must be multiple of baseAmount");
108
+ });
109
+ });
110
+
111
+ describe("解锁流程", function() {
112
+ let lockId;
113
+ let auctionId;
114
+
115
+ beforeEach(async function() {
116
+ // 创建竞拍并锁定资金
117
+ await chainYVault.createAuctionConfig(auctionType, baseAmount, 86400);
118
+
119
+ const hashSecret = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("secret"));
120
+ const expiration = Math.floor(Date.now() / 1000) + 3600;
121
+
122
+ const tx = await chainYVault.connect(seller).createAuction(
123
+ 0,
124
+ hashSecret,
125
+ expiration,
126
+ { value: baseAmount }
127
+ );
128
+
129
+ const receipt = await tx.wait();
130
+ const event = receipt.events.find(e => e.event === "TokensLocked");
131
+ lockId = event.args.lockId;
132
+ });
133
+
134
+ it("应该正确解锁资金", async function() {
135
+ // 构造模拟收据
136
+ const mockReceipt = "0xf9016f833078318312d687825208b8d1f8cfaa307833383843383138434138423932353162333933313331433038613733364136376363423139323937e1a02f098022e2dd8f759370794b8abd98fb76df4b14f2f8680cc897dea22debc029b8800a57ee3a1381880626494e291d6b330ee30954940f1d95b3550123c731b71e4b2ec29a81be0bc2b26da13786533941314bd380b6021b34e0b26fec154828521300000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000000000000000000000000000000000000000003e88080800184014ed444b842307864383461663130646364393836633330336435323162386630303031333739383634313931366364613132306166316666343834323839613863316162353663b842307834363864313534386536366464623534626665356535346335306639663237643231663134323930303932363935313538623931343534393833333131633431"
137
+ // const mockReceipt = ethers.encodeRlp([]);
138
+
139
+ const balanceBefore = await ethers.provider.getBalance(await seller.address);
140
+ console.log("balanceBefore:", balanceBefore.toString());
141
+
142
+ const tx= await chainYVault.connect(seller).unlockTokens(lockId, mockReceipt);
143
+ const receipt = await tx.wait();
144
+ const event = receipt.events.find(e => e.event =="TokensUnlocked");
145
+
146
+ console.log("event:",event.args);
147
+ // 下述expect断言验证参数都是在mockReceipt封装的。可以利用scripts/rlp.js解析查看细节
148
+ expect(event.args.lockId).to.equal(lockId);
149
+ expect(event.args.recipient).to.equal("0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
150
+ expect(event.args.amount).to.equal(ethers.utils.hexlify(1000));
151
+
152
+
153
+ const balanceAfter = await ethers.provider.getBalance(await seller.address);
154
+ console.log("balanceAfter:", balanceAfter.toString());
155
+
156
+ // * 利用余额判断是否执行,但是gas费会影响结果,不太准确
157
+ // expect(balanceAfter - balanceBefore).to.be.closeTo(
158
+ // baseAmount,
159
+ // ethers.utils.parseEther("0.01") // 考虑gas费用的误差
160
+ // );
161
+ });
162
+ });
163
+ });
@@ -0,0 +1,183 @@
1
+ import { expect } from "chai";
2
+ import { ethers } from "hardhat";
3
+ import { Contract } from "ethers";
4
+
5
+ describe("ChainYVaultV2", function () {
6
+ let chainYVault: Contract;
7
+ let owner: any;
8
+ let seller: any;
9
+ let mockStrategy: Contract;
10
+
11
+ // 测试数据
12
+ const auctionType = 0x00000001; // 普通公开竞标
13
+ const baseAmount = ethers.utils.parseEther("1.0");
14
+
15
+ beforeEach(async function () {
16
+ [owner, seller] = await ethers.getSigners();
17
+ console.log("signers: ", owner.address, seller.address);
18
+
19
+ // 部署合约
20
+ const ChainYVaultV2 = await ethers.getContractFactory("ChainYVaultV2");
21
+ // Coinbase地址设置为0,用作本地测试.
22
+ chainYVault = await ChainYVaultV2.deploy(ethers.constants.AddressZero);
23
+ await chainYVault.deployed();
24
+ console.log("chainYVault: ", chainYVault.address);
25
+
26
+ // 部署模拟解锁策略合约
27
+ // const MockUnlockStrategy = await ethers.getContractFactory(
28
+ // "MatchResultWithdrawnStrategy"
29
+ // );
30
+ // mockStrategy = await MockUnlockStrategy.deploy();
31
+
32
+ // 设置解锁策略
33
+ // await chainYVault.setUnlockStrategy(auctionType, mockStrategy.address);
34
+ });
35
+
36
+ describe("竞拍配置管理", function () {
37
+ it("应该正确创建竞拍配置", async function () {
38
+ await chainYVault.createAuctionConfig(
39
+ auctionType,
40
+ baseAmount,
41
+ 86400 // 1天的扩展时间
42
+ );
43
+
44
+ const config = await chainYVault.auctionConfigs(0);
45
+ expect(config.auctionType).to.equal(auctionType);
46
+ expect(config.baseAmount).to.equal(baseAmount);
47
+ expect(config.isActive).to.be.true;
48
+ });
49
+
50
+ it("只有管理员可以创建竞拍配置", async function () {
51
+ await expect(
52
+ chainYVault.connect(seller).createAuctionConfig(
53
+ auctionType,
54
+ baseAmount,
55
+ 86400
56
+ )
57
+ )
58
+ .to.be.revertedWithCustomError(
59
+ chainYVault,
60
+ "OwnableUnauthorizedAccount"
61
+ )
62
+ .withArgs(seller.address);
63
+ // * 另一种写法
64
+ // try {
65
+ // await chainYVault.connect(seller).createAuctionConfig(auctionType, baseAmount, 86400);
66
+ // } catch (err) {
67
+ // console.log("errorName:", err.errorName); // OwnableUnauthorizedAccount
68
+ // console.log("errorSignature:", err.errorSignature); // OwnableUnauthorizedAccount(address)
69
+ // console.log("args:", err.args); // [seller.address]
70
+ // }
71
+ });
72
+ });
73
+
74
+ describe("竞拍创建与锁定", function () {
75
+ beforeEach(async function () {
76
+ await chainYVault.createAuctionConfig(auctionType, baseAmount, 86400);
77
+ });
78
+
79
+ it("应该正确创建竞拍并锁定资金", async function () {
80
+ const hashSecret = ethers.utils.keccak256(
81
+ ethers.utils.toUtf8Bytes("secret")
82
+ );
83
+ const expiration = Math.floor(Date.now() / 1000) + 3600; // 1小时后
84
+
85
+ const tx = await chainYVault.connect(seller).createAuction(
86
+ 0, // configId
87
+ hashSecret,
88
+ expiration,
89
+ { value: baseAmount }
90
+ );
91
+
92
+ const receipt = await tx.wait();
93
+ const event = receipt.events?.find(
94
+ (e: any) => e.event === "AuctionCreated"
95
+ );
96
+
97
+ expect(event).to.not.be.undefined;
98
+
99
+ const auctionId = event.args.auctionId;
100
+ const auction = await chainYVault.auctions(auctionId);
101
+ expect(auction.auctionType).to.equal(auctionType);
102
+ expect(auction.baseAmount).to.equal(baseAmount);
103
+ });
104
+
105
+ it("应该验证锁定金额是基础金额的整数倍", async function () {
106
+ const hashSecret = ethers.utils.keccak256(
107
+ ethers.utils.toUtf8Bytes("secret")
108
+ );
109
+ const expiration = Math.floor(Date.now() / 1000) + 3600;
110
+
111
+ await expect(
112
+ chainYVault.connect(seller).createAuction(
113
+ 0,
114
+ hashSecret,
115
+ expiration,
116
+ { value: baseAmount.add(1) } // 非整数倍金额
117
+ )
118
+ ).to.be.revertedWith("Amount must be multiple of baseAmount");
119
+ });
120
+ });
121
+
122
+ describe("解锁流程", function () {
123
+ let lockId: string;
124
+
125
+ beforeEach(async function () {
126
+ // 创建竞拍并锁定资金
127
+ await chainYVault.createAuctionConfig(auctionType, baseAmount, 86400);
128
+
129
+ const hashSecret = ethers.utils.keccak256(
130
+ ethers.utils.toUtf8Bytes("secret")
131
+ );
132
+ const expiration = Math.floor(Date.now() / 1000) + 3600;
133
+
134
+ const tx = await chainYVault.connect(seller).createAuction(
135
+ 0,
136
+ hashSecret,
137
+ expiration,
138
+ { value: baseAmount }
139
+ );
140
+
141
+ const receipt = await tx.wait();
142
+ const event = receipt.events?.find(
143
+ (e: any) => e.event === "TokensLocked"
144
+ );
145
+ lockId = event.args.lockId;
146
+ });
147
+
148
+ it("应该正确解锁资金", async function () {
149
+ // 构造模拟收据
150
+ const mockReceipt =
151
+ "0xf9016f833078318312d687825208b8d1f8cfaa307833383843383138434138423932353162333933313331433038613733364136376363423139323937e1a02f098022e2dd8f759370794b8abd98fb76df4b14f2f8680cc897dea22debc029b8800a57ee3a1381880626494e291d6b330ee30954940f1d95b3550123c731b71e4b2ec29a81be0bc2b26da13786533941314bd380b6021b34e0b26fec154828521300000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000000000000000000000000000000000000000003e88080800184014ed444b842307864383461663130646364393836633330336435323162386630303031333739383634313931366364613132306166316666343834323839613863316162353663b842307834363864313534386536366464623534626665356535346335306639663237643231663134323930303932363935313538623931343534393833333131633431";
152
+ // const mockReceipt = ethers.utils.encodeRlp([]);
153
+
154
+ const balanceBefore = await ethers.provider.getBalance(seller.address);
155
+ console.log("balanceBefore:", balanceBefore.toString());
156
+
157
+ const tx = await chainYVault
158
+ .connect(seller)
159
+ .unlockTokens(lockId, mockReceipt);
160
+ const receipt = await tx.wait();
161
+ const event = receipt.events?.find(
162
+ (e: any) => e.event == "TokensUnlocked"
163
+ );
164
+
165
+ console.log("event:", event.args);
166
+ // 下述expect断言验证参数都是在mockReceipt封装的。可以利用scripts/rlp.js解析查看细节
167
+ expect(event.args.lockId).to.equal(lockId);
168
+ expect(event.args.recipient).to.equal(
169
+ "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
170
+ );
171
+ expect(event.args.amount).to.equal(ethers.utils.hexlify(1000));
172
+
173
+ const balanceAfter = await ethers.provider.getBalance(seller.address);
174
+ console.log("balanceAfter:", balanceAfter.toString());
175
+
176
+ // * 利用余额判断是否执行,但是gas费会影响结果,不太准确
177
+ // expect(balanceAfter - balanceBefore).to.be.closeTo(
178
+ // baseAmount,
179
+ // ethers.utils.parseEther("0.01") // 考虑gas费用的误差
180
+ // );
181
+ });
182
+ });
183
+ });