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.
- package/.env +47 -0
- package/.gitmodules +3 -0
- package/README.md +158 -0
- package/config/auction.config.ts +40 -0
- package/config/env.config.ts +204 -0
- package/config/uniswap.config.ts +107 -0
- package/config/voucher.config.ts +10 -0
- package/contracts/MutiVoucher.sol +78 -0
- package/contracts/auction/ChainXAuction.sol +177 -0
- package/contracts/auction/ChainXAuctionV2.sol +672 -0
- package/contracts/auction/ChainXWrappedETH.sol +80 -0
- package/contracts/auction/ChainYLiquidityManager.sol +57 -0
- package/contracts/auction/ChainYShadowETH.sol +148 -0
- package/contracts/auction/ChainYVault.sol +195 -0
- package/contracts/auction/ChainYVaultCoinbase.sol +276 -0
- package/contracts/auction/ChainYVaultV2.sol +318 -0
- package/contracts/auction/coinbase-and-stake/README.md +55 -0
- package/contracts/auction/coinbase-and-stake/coinbase.sol +142 -0
- package/contracts/auction/coinbase-and-stake/invokeCoinbase.sol +159 -0
- package/contracts/auction/coinbase-and-stake/invokeStake.sol +82 -0
- package/contracts/auction/coinbase-and-stake/stake.sol +92 -0
- package/contracts/auction/interfaces/IUniswapV2Factory.sol +15 -0
- package/contracts/auction/interfaces/IUniswapV2Pair.sol +53 -0
- package/contracts/auction/interfaces/IUniswapV2Router02.sol +25 -0
- package/contracts/auction/interfaces/IUnlockStrategy.sol +18 -0
- package/contracts/auction/libraries/EventParser.sol +32 -0
- package/contracts/auction/libraries/TransactionParser.sol +70 -0
- package/contracts/auction/strategies/MatchResultWithdrawnStrategy.sol +33 -0
- package/contracts/auction/utils/BytesLib.sol +180 -0
- package/contracts/auction/utils/RLPReader.sol +355 -0
- package/contracts/uniswap/Create2.sol +80 -0
- package/contracts/uniswap/DynamicFee.sol +100 -0
- package/contracts/uniswap/Example.sol +35 -0
- package/contracts/uniswap/HookMiner.sol +52 -0
- package/contracts/uniswap/LimitOrder.sol +486 -0
- package/contracts/uniswap/LiquidPool.sol +179 -0
- package/contracts/uniswap/MockERC20.sol +20 -0
- package/hardhat.config.ts +35 -0
- package/ignition/modules/LimitOrder.ts +33 -0
- package/package.json +32 -0
- package/scripts/auction/deploy.ts +23 -0
- package/scripts/auction/deployCoinbase.ts +21 -0
- package/scripts/auction/deployXAuction.ts +23 -0
- package/scripts/auction/deployYVault.ts +22 -0
- package/scripts/deploy_voucher.ts +20 -0
- package/scripts/uniswap/deploy/deploy.ts +65 -0
- package/scripts/uniswap/deploy/deploy_create2.ts +11 -0
- package/scripts/uniswap/deploy/deploy_example.ts +35 -0
- package/scripts/uniswap/deploy/deploy_hooks.ts +74 -0
- package/scripts/uniswap/deploy/deploy_mockERC20.ts +42 -0
- package/scripts/uniswap/deploy/help.ts +96 -0
- package/scripts/uniswap/deploy/init.ts +70 -0
- package/src/auction/chainXAuction.ts +209 -0
- package/src/auction/chainYVault.ts +153 -0
- package/src/auction/event.ts +19 -0
- package/src/auction/serialize.ts +162 -0
- package/src/auction/type.ts +71 -0
- package/src/lib/signer.ts +20 -0
- package/src/lib/unlock.ts +14 -0
- package/src/uniswap/1-marketprice/addLiquidity.ts +80 -0
- package/src/uniswap/1-marketprice/removeLiquidity.ts +63 -0
- package/src/uniswap/1-marketprice/swap.ts +100 -0
- package/src/uniswap/2-limitorder/kill.ts +70 -0
- package/src/uniswap/2-limitorder/place.ts +93 -0
- package/src/uniswap/2-limitorder/withdraw.ts +78 -0
- package/src/uniswap/3-dynamicfee/dynamicfee.ts +321 -0
- package/src/uniswap/lib/ERC20.ts +49 -0
- package/src/uniswap/lib/contract.ts +18 -0
- package/src/uniswap/lib/limitOrder.ts +40 -0
- package/src/uniswap/lib/liqCalculation.ts +152 -0
- package/src/uniswap/lib/listen.ts +57 -0
- package/src/uniswap/lib/pool.ts +62 -0
- package/src/uniswap/lib/swap.ts +8 -0
- package/src/uniswap/lib/types.ts +21 -0
- package/src/uniswap/lib/utils.ts +26 -0
- package/src/uniswap/playgroud/abiencode.ts +21 -0
- package/src/uniswap/playgroud/amount0.ts +47 -0
- package/src/uniswap/playgroud/errordecode.ts +54 -0
- package/src/uniswap/playgroud/errorsigs.ts +86 -0
- package/src/voucher.ts +122 -0
- package/test/auction/ChainXAuctionV2.test.ts +265 -0
- package/test/auction/ChainYVaultV2.test.js +163 -0
- package/test/auction/ChainYVaultV2.test.ts +183 -0
- package/test/auction/auction.test.ts +106 -0
- package/test/connect_punk.test.ts +26 -0
- package/test/create2.test.ts +44 -0
- package/test/normal.ts +43 -0
- package/test/test-config.ts +18 -0
- package/test/uniswap/example.test.ts +62 -0
- package/test/uniswap/limitOrder.test.ts +184 -0
- package/test/uniswap/mockERC20.test.ts +142 -0
- package/test/voucher_hardhat.test.ts +120 -0
- package/test/voucher_punk.test.ts +83 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
|
5
|
+
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
6
|
+
import "./libraries/TransactionParser.sol";
|
|
7
|
+
import "./libraries/EventParser.sol";
|
|
8
|
+
import "hardhat/console.sol";
|
|
9
|
+
|
|
10
|
+
contract ChainXAuctionV2 is ReentrancyGuard, Ownable {
|
|
11
|
+
// ============ 状态变量 ============
|
|
12
|
+
address public vaultAddress;
|
|
13
|
+
mapping(uint256 => Auction) public auctions;
|
|
14
|
+
|
|
15
|
+
// Auction Start: Auction | REVEAL_PERIOD_SECRECT | REVEAL_PERIOD_BID | CHALLENGE_PERIOD | WITHDRAW_PERIOD | End
|
|
16
|
+
uint256 public constant CHALLENGE_PERIOD = 1 hours;
|
|
17
|
+
uint256 public constant REVEAL_PERIOD_SECRECT = 1 hours;
|
|
18
|
+
uint256 public constant REVEAL_PERIOD_BID = 1 hours;
|
|
19
|
+
uint256 public constant WITHDRAW_PERIOD = 1 hours;
|
|
20
|
+
|
|
21
|
+
// ============ 结构体定义 ============
|
|
22
|
+
struct Vault {
|
|
23
|
+
bool exists;
|
|
24
|
+
uint256 revealTime; // 实际的揭示时间,并代表isRevealed(e.g. lockInfo.revealTime == 0)
|
|
25
|
+
bytes32 vaultSecretHash;
|
|
26
|
+
uint256 value;
|
|
27
|
+
bytes32 salt;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
struct Bid {
|
|
31
|
+
bytes32 bidSecretHash;
|
|
32
|
+
uint256 bidValue;
|
|
33
|
+
bool isSealed;
|
|
34
|
+
bytes32[] targetVaults; // 一个bid可以对应多个vault,即竞标多个资产
|
|
35
|
+
uint256 revealTime;
|
|
36
|
+
uint256 depositAmount;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
struct Auction {
|
|
40
|
+
uint256 chainid;
|
|
41
|
+
uint32 auctionType; // 竞标类型
|
|
42
|
+
uint256 revealTime;
|
|
43
|
+
bool isActive; // 是否激活
|
|
44
|
+
mapping(bytes32 => Vault) vaults;
|
|
45
|
+
uint256 vaultCount;
|
|
46
|
+
mapping(address => Bid) auctionBid;
|
|
47
|
+
uint256 bidCount;
|
|
48
|
+
mapping(bytes32 => address) vaultToBidder;
|
|
49
|
+
mapping(address => MatchResult) auctionMatches; // 修改: 从 lockId 改为 bidder 作为 key
|
|
50
|
+
uint256 challengeEndTime; // 挑战期结束时间
|
|
51
|
+
mapping(address => mapping(bytes32 => bool)) sellerLocks; // 新增: seller -> lockId -> exists
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 跨链数据,包含原始交易和收据
|
|
55
|
+
struct CrossChainData {
|
|
56
|
+
uint256 sourceChainId;
|
|
57
|
+
bytes rawTransaction;
|
|
58
|
+
bytes rawRecpt;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
struct MatchResult {
|
|
62
|
+
bytes32 vaultId; // 新增: 记录对应的 lockId
|
|
63
|
+
uint256 bidValue; // 新增: 记录中标金额
|
|
64
|
+
bool isChallenged;
|
|
65
|
+
bool isWithdrawn;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ============ 事件定义 ============
|
|
69
|
+
event AuctionCreated(
|
|
70
|
+
uint256 indexed auctionId,
|
|
71
|
+
uint32 auctionType,
|
|
72
|
+
uint256 endTime
|
|
73
|
+
);
|
|
74
|
+
event LockRevealed(
|
|
75
|
+
uint256 indexed auctionId,
|
|
76
|
+
bytes32 indexed lockId,
|
|
77
|
+
uint256 revealTime
|
|
78
|
+
);
|
|
79
|
+
event BidPlaced(
|
|
80
|
+
uint256 indexed auctionId,
|
|
81
|
+
bytes32 indexed lockId,
|
|
82
|
+
address indexed bidder,
|
|
83
|
+
uint256 depositAmount
|
|
84
|
+
);
|
|
85
|
+
event BidRevealed(
|
|
86
|
+
uint256 indexed auctionId,
|
|
87
|
+
address indexed bidder,
|
|
88
|
+
uint256 bidRevealValue
|
|
89
|
+
);
|
|
90
|
+
event MatchResultSubmitted(
|
|
91
|
+
uint256 indexed auctionId,
|
|
92
|
+
bytes32 indexed lockId,
|
|
93
|
+
address indexed bidder,
|
|
94
|
+
uint256 bidValue
|
|
95
|
+
);
|
|
96
|
+
event MatchResultChallenged(
|
|
97
|
+
uint256 indexed auctionId,
|
|
98
|
+
bytes32 indexed lockId,
|
|
99
|
+
address indexed challenger,
|
|
100
|
+
string reason
|
|
101
|
+
);
|
|
102
|
+
event MatchResultWithdrawn(
|
|
103
|
+
uint256 indexed auctionId,
|
|
104
|
+
bytes32 indexed lockId,
|
|
105
|
+
address indexed recipient,
|
|
106
|
+
uint256 amount
|
|
107
|
+
);
|
|
108
|
+
event LockCreated(
|
|
109
|
+
uint256 indexed auctionId,
|
|
110
|
+
bytes32 indexed lockId,
|
|
111
|
+
uint256 revealTime
|
|
112
|
+
);
|
|
113
|
+
event BidUpdated(
|
|
114
|
+
uint256 indexed auctionId,
|
|
115
|
+
bytes32 indexed lockId,
|
|
116
|
+
address indexed bidder
|
|
117
|
+
);
|
|
118
|
+
event VaultAddressUpdated(
|
|
119
|
+
address indexed oldVault,
|
|
120
|
+
address indexed newVault
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// ============ 拍卖核心功能 ============
|
|
124
|
+
constructor(
|
|
125
|
+
address initialVaultAddress
|
|
126
|
+
) ReentrancyGuard() Ownable(msg.sender) {
|
|
127
|
+
require(
|
|
128
|
+
initialVaultAddress != address(0),
|
|
129
|
+
"Invalid initial vault address"
|
|
130
|
+
);
|
|
131
|
+
vaultAddress = initialVaultAddress;
|
|
132
|
+
emit VaultAddressUpdated(address(0), initialVaultAddress);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function setVaultAddress(address newVaultAddress) external onlyOwner {
|
|
136
|
+
require(newVaultAddress != address(0), "Invalid vault address");
|
|
137
|
+
address oldVault = vaultAddress;
|
|
138
|
+
vaultAddress = newVaultAddress;
|
|
139
|
+
emit VaultAddressUpdated(oldVault, newVaultAddress);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 盘古上部署 X
|
|
143
|
+
// Y 拍卖 auction,
|
|
144
|
+
// X 上同步启动,X开始竞价 ETH
|
|
145
|
+
function createAuction(bytes memory crossChainMessage) external {
|
|
146
|
+
// 无需验证,直接反向传输
|
|
147
|
+
CrossChainData memory ccData = abi.decode(
|
|
148
|
+
crossChainMessage,
|
|
149
|
+
(CrossChainData)
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// 解析原始交易
|
|
153
|
+
TransactionParser.RawTransaction memory rawTx = TransactionParser
|
|
154
|
+
.parseRawTransaction(ccData.rawTransaction);
|
|
155
|
+
TransactionParser.RecptLog memory log = TransactionParser.parseRecptLog(
|
|
156
|
+
ccData.rawRecpt
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// 解析拍卖参数
|
|
160
|
+
uint256 auctionId = uint256(log.topics[1]); // auctionID 是 indexed修饰因此再topic中。
|
|
161
|
+
uint32 auctionType;
|
|
162
|
+
uint256 activeAuctionCount;
|
|
163
|
+
uint256 revealStartTime;
|
|
164
|
+
|
|
165
|
+
(auctionType, activeAuctionCount, revealStartTime) = abi.decode(
|
|
166
|
+
log.eventData,
|
|
167
|
+
(uint32, uint256, uint256)
|
|
168
|
+
);
|
|
169
|
+
require(auctionId != 0, "Invalid auction, auctionID=0");
|
|
170
|
+
console.log("activeAuctionCount:", activeAuctionCount);
|
|
171
|
+
|
|
172
|
+
// ! 版本1:解析拍卖参数
|
|
173
|
+
// (uint256 auctionId, uint32 auctionType, uint256 nonce, uint256 revealStartTime) = EventParser.parseAuctionCreatedEvent(log.eventData);
|
|
174
|
+
// 校验auctionId与message sender的绑定关系
|
|
175
|
+
// 恶意sender无法伪造其他sender的auctionId
|
|
176
|
+
// 恶意sender无法在其他链重放自己的auction
|
|
177
|
+
address sender = signerRecover(rawTx);
|
|
178
|
+
|
|
179
|
+
uint256 auctionCheckId = uint256(
|
|
180
|
+
keccak256(
|
|
181
|
+
abi.encodePacked(
|
|
182
|
+
sender,
|
|
183
|
+
ccData.sourceChainId,
|
|
184
|
+
vaultAddress,
|
|
185
|
+
activeAuctionCount
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
console.log("auctionCheckId:", auctionCheckId);
|
|
191
|
+
console.log("auctionId:", auctionId);
|
|
192
|
+
|
|
193
|
+
require(auctionCheckId == auctionId, "Invalid auctionId");
|
|
194
|
+
|
|
195
|
+
auctions[auctionId].chainid = ccData.sourceChainId;
|
|
196
|
+
auctions[auctionId].auctionType = auctionType;
|
|
197
|
+
auctions[auctionId].revealTime =
|
|
198
|
+
revealStartTime +
|
|
199
|
+
REVEAL_PERIOD_SECRECT;
|
|
200
|
+
auctions[auctionId].isActive = true;
|
|
201
|
+
auctions[auctionId].challengeEndTime =
|
|
202
|
+
revealStartTime +
|
|
203
|
+
REVEAL_PERIOD_SECRECT +
|
|
204
|
+
REVEAL_PERIOD_BID +
|
|
205
|
+
CHALLENGE_PERIOD;
|
|
206
|
+
|
|
207
|
+
emit AuctionCreated(
|
|
208
|
+
auctionId,
|
|
209
|
+
auctionType,
|
|
210
|
+
auctions[auctionId].revealTime
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function signerRecover(TransactionParser.RawTransaction memory rawTx) internal pure returns (address sender)
|
|
215
|
+
{
|
|
216
|
+
uint8 vAdj = uint8(rawTx.v);
|
|
217
|
+
if (vAdj >= 35) {
|
|
218
|
+
vAdj = 27 + uint8((vAdj - 35) % 2); // normalize EIP-155 v to 27/28
|
|
219
|
+
}
|
|
220
|
+
uint256 chainIdSig = 0;
|
|
221
|
+
if (rawTx.v >= 35) {
|
|
222
|
+
chainIdSig = (uint256(rawTx.v) - 35) / 2;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
bytes[] memory payload = new bytes[](9);
|
|
226
|
+
payload[0] = _encodeUint(rawTx.nonce);
|
|
227
|
+
payload[1] = _encodeUint(rawTx.gasPrice);
|
|
228
|
+
payload[2] = _encodeUint(rawTx.gasLimit);
|
|
229
|
+
payload[3] = _encodeAddress(rawTx.to);
|
|
230
|
+
payload[4] = _encodeUint(rawTx.value);
|
|
231
|
+
payload[5] = _encodeBytes(rawTx.data);
|
|
232
|
+
payload[6] = _encodeUint(chainIdSig);
|
|
233
|
+
payload[7] = _encodeUint(0);
|
|
234
|
+
payload[8] = _encodeUint(0);
|
|
235
|
+
|
|
236
|
+
bytes32 txHash = keccak256(_encodeList(payload));
|
|
237
|
+
sender = ecrecover(txHash, vAdj, rawTx.r, rawTx.s);
|
|
238
|
+
console.logBytes32(txHash);
|
|
239
|
+
console.log("vAdj:", vAdj);
|
|
240
|
+
console.log("chainID:",chainIdSig);
|
|
241
|
+
console.log("sender address:", sender);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// 调试接口:根据 rawTx 计算恢复的签名者
|
|
245
|
+
function debugRecover(
|
|
246
|
+
bytes memory rawTransaction
|
|
247
|
+
)
|
|
248
|
+
external
|
|
249
|
+
pure
|
|
250
|
+
returns (
|
|
251
|
+
address signer,
|
|
252
|
+
bytes32 txHash,
|
|
253
|
+
uint256 chainIdSig,
|
|
254
|
+
uint8 vAdj,
|
|
255
|
+
uint256 nonce,
|
|
256
|
+
uint256 gasPrice,
|
|
257
|
+
uint256 gasLimit,
|
|
258
|
+
uint256 value
|
|
259
|
+
)
|
|
260
|
+
{
|
|
261
|
+
TransactionParser.RawTransaction memory rawTx = TransactionParser
|
|
262
|
+
.parseRawTransaction(rawTransaction);
|
|
263
|
+
|
|
264
|
+
vAdj = uint8(rawTx.v);
|
|
265
|
+
if (vAdj >= 35) {
|
|
266
|
+
vAdj = 27 + uint8((vAdj - 35) % 2);
|
|
267
|
+
chainIdSig = (uint256(rawTx.v) - 35) / 2;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
nonce = rawTx.nonce;
|
|
271
|
+
gasPrice = rawTx.gasPrice;
|
|
272
|
+
gasLimit = rawTx.gasLimit;
|
|
273
|
+
value = rawTx.value;
|
|
274
|
+
|
|
275
|
+
bytes[] memory payload = new bytes[](9);
|
|
276
|
+
payload[0] = _encodeUint(rawTx.nonce);
|
|
277
|
+
payload[1] = _encodeUint(rawTx.gasPrice);
|
|
278
|
+
payload[2] = _encodeUint(rawTx.gasLimit);
|
|
279
|
+
payload[3] = _encodeAddress(rawTx.to);
|
|
280
|
+
payload[4] = _encodeUint(rawTx.value);
|
|
281
|
+
payload[5] = _encodeBytes(rawTx.data);
|
|
282
|
+
payload[6] = _encodeUint(chainIdSig);
|
|
283
|
+
payload[7] = _encodeUint(0);
|
|
284
|
+
payload[8] = _encodeUint(0);
|
|
285
|
+
|
|
286
|
+
txHash = keccak256(_encodeList(payload));
|
|
287
|
+
signer = ecrecover(txHash, vAdj, rawTx.r, rawTx.s);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function placeBid(
|
|
291
|
+
uint256 auctionId,
|
|
292
|
+
address seller,
|
|
293
|
+
bytes32 vaultSecretHash,
|
|
294
|
+
bytes32 bidSecretHash
|
|
295
|
+
) external payable {
|
|
296
|
+
Auction storage auction = auctions[auctionId];
|
|
297
|
+
require(auction.isActive, "Auction not active");
|
|
298
|
+
require(block.timestamp < auction.revealTime, "Auction ended");
|
|
299
|
+
|
|
300
|
+
// auction可以支持多个vault竞标,每个vault对应一个lockId
|
|
301
|
+
bytes32 targetLockId = keccak256(
|
|
302
|
+
abi.encodePacked(
|
|
303
|
+
auction.chainid,
|
|
304
|
+
vaultAddress,
|
|
305
|
+
auctionId,
|
|
306
|
+
seller,
|
|
307
|
+
vaultSecretHash,
|
|
308
|
+
auction.auctionType
|
|
309
|
+
)
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
if (!auction.vaults[targetLockId].exists) {
|
|
313
|
+
Vault storage newLock = auction.vaults[targetLockId];
|
|
314
|
+
newLock.exists = true;
|
|
315
|
+
newLock.vaultSecretHash = vaultSecretHash;
|
|
316
|
+
auction.vaultCount++;
|
|
317
|
+
emit LockCreated(auctionId, targetLockId, auction.revealTime);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (auction.auctionBid[msg.sender].revealTime == 0) {
|
|
321
|
+
console.log("New bid from bidder:", msg.sender);
|
|
322
|
+
Bid storage newBid = auction.auctionBid[msg.sender];
|
|
323
|
+
newBid.bidSecretHash = bidSecretHash;
|
|
324
|
+
newBid.isSealed = isSealedAuction(auction.auctionType);
|
|
325
|
+
newBid.targetVaults.push(targetLockId);
|
|
326
|
+
newBid.revealTime =
|
|
327
|
+
auction.revealTime +
|
|
328
|
+
REVEAL_PERIOD_SECRECT +
|
|
329
|
+
REVEAL_PERIOD_BID;
|
|
330
|
+
newBid.depositAmount = msg.value; // Bid出价时质押的值,需要比真实出价值高,bid结束后,可以通过claim提取未使用部分
|
|
331
|
+
|
|
332
|
+
console.log("Deposit amount:", msg.value);
|
|
333
|
+
emit BidPlaced(auctionId, targetLockId, msg.sender, msg.value);
|
|
334
|
+
} else {
|
|
335
|
+
// 已经出价bid,但是可以追加竞标多个vault
|
|
336
|
+
console.log("Old bid from bidder:", msg.sender);
|
|
337
|
+
Bid storage existingBid = auction.auctionBid[msg.sender];
|
|
338
|
+
existingBid.targetVaults.push(targetLockId);
|
|
339
|
+
emit BidUpdated(auctionId, targetLockId, msg.sender);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// 卖方揭示Lock信息,通过公开value和salt证明其secretHash的正确性。
|
|
344
|
+
function revealLock(
|
|
345
|
+
uint256 auctionId,
|
|
346
|
+
bytes32 lockId,
|
|
347
|
+
uint256 value,
|
|
348
|
+
bytes32 salt
|
|
349
|
+
) external {
|
|
350
|
+
Auction storage auction = auctions[auctionId];
|
|
351
|
+
require(auction.isActive, "Auction not active");
|
|
352
|
+
require(block.timestamp < auction.revealTime, "Auction ended");
|
|
353
|
+
|
|
354
|
+
Vault storage lockInfo = auction.vaults[lockId];
|
|
355
|
+
require(lockInfo.exists, "Lock does not exist");
|
|
356
|
+
require(lockInfo.revealTime == 0, "Lock already revealed");
|
|
357
|
+
require(
|
|
358
|
+
keccak256(abi.encodePacked(value, salt)) ==
|
|
359
|
+
lockInfo.vaultSecretHash,
|
|
360
|
+
"Invalid lock reveal, hash secret is not match"
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
lockInfo.revealTime = block.timestamp;
|
|
364
|
+
lockInfo.value = value;
|
|
365
|
+
lockInfo.salt = salt;
|
|
366
|
+
auction.vaultCount++;
|
|
367
|
+
|
|
368
|
+
emit LockRevealed(auctionId, lockId, block.timestamp);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// 竞标者揭示bid信息
|
|
372
|
+
function revealBid(
|
|
373
|
+
uint256 auctionId,
|
|
374
|
+
uint256 value,
|
|
375
|
+
bytes32 salt
|
|
376
|
+
) external {
|
|
377
|
+
Auction storage auction = auctions[auctionId];
|
|
378
|
+
require(auction.isActive, "Auction not active");
|
|
379
|
+
Bid storage bid = auction.auctionBid[msg.sender];
|
|
380
|
+
require(bid.bidValue == 0, "Bid already revealed");
|
|
381
|
+
|
|
382
|
+
if (bid.isSealed) {
|
|
383
|
+
// 密封竞标,需要验证hash
|
|
384
|
+
require(block.timestamp < bid.revealTime, "Bid reveal time ended");
|
|
385
|
+
require(
|
|
386
|
+
keccak256(abi.encodePacked(value, salt)) == bid.bidSecretHash,
|
|
387
|
+
"Invalid bid reveal"
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
bid.bidValue = value;
|
|
391
|
+
bid.revealTime = block.timestamp;
|
|
392
|
+
auction.bidCount++;
|
|
393
|
+
emit BidRevealed(auctionId, msg.sender, bid.bidValue);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// 卖家未揭示,出价方可提取押金
|
|
397
|
+
function claimBidDepositAfterReveal(uint256 auctionId) external {
|
|
398
|
+
Auction storage auction = auctions[auctionId];
|
|
399
|
+
require(
|
|
400
|
+
block.timestamp > auction.revealTime,
|
|
401
|
+
"Reveal period not ended"
|
|
402
|
+
);
|
|
403
|
+
require(auction.auctionType == 0, "Auction is not a sealed auction");
|
|
404
|
+
|
|
405
|
+
// bid.bidLocks所有对应的locks均未reveal
|
|
406
|
+
Bid storage bid = auction.auctionBid[msg.sender];
|
|
407
|
+
for (uint256 i = 0; i < bid.targetVaults.length; i++) {
|
|
408
|
+
bytes32 vaultId = bid.targetVaults[i];
|
|
409
|
+
require(
|
|
410
|
+
auction.vaults[vaultId].salt != bytes32(0),
|
|
411
|
+
"Vault is not revealed"
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
uint256 depositAmount = bid.depositAmount;
|
|
416
|
+
bid.depositAmount = 0;
|
|
417
|
+
payable(msg.sender).transfer(depositAmount);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function claimBidDepositAfterChallenge(uint256 auctionId) external {
|
|
421
|
+
Auction storage auction = auctions[auctionId];
|
|
422
|
+
require(
|
|
423
|
+
block.timestamp > auction.challengeEndTime,
|
|
424
|
+
"Challenge period not ended"
|
|
425
|
+
);
|
|
426
|
+
// 判断bid可提取,未竞价成功
|
|
427
|
+
require(
|
|
428
|
+
auction.auctionMatches[msg.sender].isChallenged ||
|
|
429
|
+
auction.auctionMatches[msg.sender].vaultId == bytes32(0),
|
|
430
|
+
"Bid is challenged or not win the auction"
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
Bid storage bid = auction.auctionBid[msg.sender];
|
|
434
|
+
uint256 depositAmount = bid.depositAmount;
|
|
435
|
+
bid.depositAmount = 0;
|
|
436
|
+
payable(msg.sender).transfer(depositAmount);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function claimBidDepositAfterWithdraw(uint256 auctionId) external {
|
|
440
|
+
Auction storage auction = auctions[auctionId];
|
|
441
|
+
require(
|
|
442
|
+
block.timestamp > auction.challengeEndTime + WITHDRAW_PERIOD,
|
|
443
|
+
"Withdraw period not ended"
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
Bid storage bid = auction.auctionBid[msg.sender];
|
|
447
|
+
uint256 depositAmount = bid.depositAmount;
|
|
448
|
+
bid.depositAmount = 0;
|
|
449
|
+
payable(msg.sender).transfer(depositAmount);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// 缺少步骤:竞标成功的判断在链上不好做?如果是简单的价高者可以实现,但是复杂逻辑链上实现复杂。
|
|
453
|
+
// 如果要实现竞标成功判断? 挑战机制,链外计算
|
|
454
|
+
// submitMatchResults 只负责拍卖结果上传(已经确定好中标人以后的操作)
|
|
455
|
+
|
|
456
|
+
// ============ 匹配结果管理 ============
|
|
457
|
+
function submitMatchResults(
|
|
458
|
+
uint256 auctionId,
|
|
459
|
+
bytes32[] calldata lockIds,
|
|
460
|
+
address[] calldata bidders,
|
|
461
|
+
uint256[] calldata finalValues
|
|
462
|
+
) external {
|
|
463
|
+
require(
|
|
464
|
+
lockIds.length == bidders.length &&
|
|
465
|
+
bidders.length == finalValues.length,
|
|
466
|
+
"Length mismatch"
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
Auction storage auction = auctions[auctionId];
|
|
470
|
+
require(lockIds.length == auction.vaultCount, "Count mismatch");
|
|
471
|
+
require(auction.isActive, "Auction not active");
|
|
472
|
+
require(
|
|
473
|
+
block.timestamp >= auction.revealTime + REVEAL_PERIOD_BID,
|
|
474
|
+
"Bid reveal period not ended"
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
for (uint256 i = 0; i < lockIds.length; i++) {
|
|
478
|
+
bytes32 lockId = lockIds[i];
|
|
479
|
+
address bidder = bidders[i];
|
|
480
|
+
uint256 finalValue = finalValues[i];
|
|
481
|
+
require(auction.vaults[lockId].exists, "Invalid lock ID");
|
|
482
|
+
require(
|
|
483
|
+
auction.vaults[lockId].revealTime == 0,
|
|
484
|
+
"Lock not revealed"
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
auction.auctionMatches[bidder] = MatchResult({
|
|
488
|
+
vaultId: lockId,
|
|
489
|
+
isChallenged: false,
|
|
490
|
+
isWithdrawn: false,
|
|
491
|
+
bidValue: finalValue
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
auction.vaultToBidder[lockId] = bidder;
|
|
495
|
+
|
|
496
|
+
emit MatchResultSubmitted(auctionId, lockId, bidder, finalValue);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function challengeMatchResult(uint256 auctionId, address bidder) external {
|
|
501
|
+
Auction storage auction = auctions[auctionId];
|
|
502
|
+
require(auction.isActive, "Auction not active");
|
|
503
|
+
require(
|
|
504
|
+
block.timestamp < auction.challengeEndTime,
|
|
505
|
+
"Challenge period ended"
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
MatchResult storage matching = auction.auctionMatches[bidder];
|
|
509
|
+
require(!matching.isChallenged, "Already challenged");
|
|
510
|
+
|
|
511
|
+
// todo: 如何进行挑战?
|
|
512
|
+
|
|
513
|
+
matching.isChallenged = true;
|
|
514
|
+
emit MatchResultChallenged(auctionId, matching.vaultId, msg.sender, "");
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// 提现方法,挑战期结束以后执行。 lockID来源于创建Auction时候的秘密值,需要通过ChainY的提款日志获取
|
|
518
|
+
// 通过withdrawMatchResult来触发chainYVaultV2的unlock
|
|
519
|
+
function withdrawMatchResult(
|
|
520
|
+
uint256 auctionId,
|
|
521
|
+
bytes32 vaultId
|
|
522
|
+
) external nonReentrant {
|
|
523
|
+
Auction storage auction = auctions[auctionId];
|
|
524
|
+
require(auction.isActive, "Auction not active");
|
|
525
|
+
require(
|
|
526
|
+
block.timestamp >= auction.challengeEndTime,
|
|
527
|
+
"Challenge period not ended"
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
address bidder = auction.vaultToBidder[vaultId];
|
|
531
|
+
uint256 transferAmount = 0;
|
|
532
|
+
Vault storage vaultInfo = auction.vaults[vaultId];
|
|
533
|
+
require(vaultInfo.exists, "Vault does not exist");
|
|
534
|
+
|
|
535
|
+
if (vaultInfo.revealTime == 0) {
|
|
536
|
+
// 没有揭示,返还给vault
|
|
537
|
+
bidder = vaultAddress;
|
|
538
|
+
} else if (bidder != address(0)) {
|
|
539
|
+
MatchResult storage matching = auction.auctionMatches[bidder];
|
|
540
|
+
require(!matching.isChallenged, "Match was challenged");
|
|
541
|
+
require(!matching.isWithdrawn, "Already withdrawn");
|
|
542
|
+
|
|
543
|
+
Bid storage bid = auction.auctionBid[bidder];
|
|
544
|
+
bid.depositAmount -= transferAmount;
|
|
545
|
+
require(bid.depositAmount >= 0, "Deposit amount is negative");
|
|
546
|
+
|
|
547
|
+
transferAmount = matching.bidValue;
|
|
548
|
+
payable(msg.sender).transfer(transferAmount);
|
|
549
|
+
matching.isWithdrawn = true;
|
|
550
|
+
}
|
|
551
|
+
// else {} bidder = address(0)
|
|
552
|
+
|
|
553
|
+
emit MatchResultWithdrawn(auctionId, vaultId, bidder, transferAmount);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// ============ 查询功能 ============
|
|
557
|
+
function getLockInfo(
|
|
558
|
+
uint256 auctionId,
|
|
559
|
+
bytes32 lockId
|
|
560
|
+
) external view returns (bool exists, uint256 revealTime, bytes32 secret) {
|
|
561
|
+
Vault memory lockInfo = auctions[auctionId].vaults[lockId];
|
|
562
|
+
return (
|
|
563
|
+
lockInfo.exists,
|
|
564
|
+
lockInfo.revealTime,
|
|
565
|
+
keccak256(abi.encodePacked(lockInfo.value, lockInfo.salt))
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function getMatchResult(
|
|
570
|
+
uint256 auctionId,
|
|
571
|
+
address bidder
|
|
572
|
+
)
|
|
573
|
+
external
|
|
574
|
+
view
|
|
575
|
+
returns (
|
|
576
|
+
bytes32 lockId, // 修改返回值顺序
|
|
577
|
+
bool isChallenged,
|
|
578
|
+
bool isWithdrawn
|
|
579
|
+
)
|
|
580
|
+
{
|
|
581
|
+
MatchResult storage matching = auctions[auctionId].auctionMatches[
|
|
582
|
+
bidder
|
|
583
|
+
];
|
|
584
|
+
return (matching.vaultId, matching.isChallenged, matching.isWithdrawn);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// ============ 内部功能 ============
|
|
588
|
+
// 是否为秘密竞拍
|
|
589
|
+
function isSealedAuction(uint32 auctionType) internal pure returns (bool) {
|
|
590
|
+
return (auctionType & 0x000000FF) == 0x000000FF;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// ============ RLP helpers (minimal encode) ============
|
|
594
|
+
function _encodeUint(uint256 value) internal pure returns (bytes memory) {
|
|
595
|
+
if (value == 0) {
|
|
596
|
+
return hex"80"; // empty string per RLP for zero
|
|
597
|
+
}
|
|
598
|
+
bytes memory b = _toMinimalBytes(value);
|
|
599
|
+
if (b.length == 1 && uint8(b[0]) < 0x80) {
|
|
600
|
+
return b; // single byte, no length prefix
|
|
601
|
+
}
|
|
602
|
+
return abi.encodePacked(bytes1(uint8(0x80 + b.length)), b);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function _encodeAddress(address addr) internal pure returns (bytes memory) {
|
|
606
|
+
bytes memory b = abi.encodePacked(addr);
|
|
607
|
+
return abi.encodePacked(bytes1(uint8(0x80 + b.length)), b);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function _encodeBytes(
|
|
611
|
+
bytes memory data
|
|
612
|
+
) internal pure returns (bytes memory) {
|
|
613
|
+
if (data.length == 1 && uint8(data[0]) < 0x80) {
|
|
614
|
+
return data;
|
|
615
|
+
}
|
|
616
|
+
if (data.length <= 55) {
|
|
617
|
+
return abi.encodePacked(bytes1(uint8(0x80 + data.length)), data);
|
|
618
|
+
}
|
|
619
|
+
bytes memory lenBytes = _toMinimalBytes(data.length);
|
|
620
|
+
return
|
|
621
|
+
abi.encodePacked(
|
|
622
|
+
bytes1(uint8(0xb7 + lenBytes.length)),
|
|
623
|
+
lenBytes,
|
|
624
|
+
data
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function _encodeList(
|
|
629
|
+
bytes[] memory list
|
|
630
|
+
) internal pure returns (bytes memory) {
|
|
631
|
+
uint256 totalLen;
|
|
632
|
+
for (uint256 i = 0; i < list.length; i++) {
|
|
633
|
+
totalLen += list[i].length;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
bytes memory payload;
|
|
637
|
+
if (totalLen <= 55) {
|
|
638
|
+
payload = abi.encodePacked(bytes1(uint8(0xc0 + totalLen)));
|
|
639
|
+
} else {
|
|
640
|
+
bytes memory lenBytes = _toMinimalBytes(totalLen);
|
|
641
|
+
payload = abi.encodePacked(
|
|
642
|
+
bytes1(uint8(0xf7 + lenBytes.length)),
|
|
643
|
+
lenBytes
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
for (uint256 i = 0; i < list.length; i++) {
|
|
648
|
+
payload = abi.encodePacked(payload, list[i]);
|
|
649
|
+
}
|
|
650
|
+
return payload;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function _toMinimalBytes(
|
|
654
|
+
uint256 value
|
|
655
|
+
) internal pure returns (bytes memory) {
|
|
656
|
+
if (value == 0) {
|
|
657
|
+
return hex"00";
|
|
658
|
+
}
|
|
659
|
+
uint256 temp = value;
|
|
660
|
+
uint256 len;
|
|
661
|
+
while (temp != 0) {
|
|
662
|
+
len++;
|
|
663
|
+
temp >>= 8;
|
|
664
|
+
}
|
|
665
|
+
bytes memory out = new bytes(len);
|
|
666
|
+
for (uint256 i = len; i > 0; i--) {
|
|
667
|
+
out[i - 1] = bytes1(uint8(value));
|
|
668
|
+
value >>= 8;
|
|
669
|
+
}
|
|
670
|
+
return out;
|
|
671
|
+
}
|
|
672
|
+
}
|