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,276 @@
|
|
|
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 "./utils/RLPReader.sol";
|
|
7
|
+
import "./interfaces/IUnlockStrategy.sol";
|
|
8
|
+
import "./coinbase-and-stake/invokeCoinbase.sol";
|
|
9
|
+
|
|
10
|
+
contract ChainYVaultV2CB is CoinbaseOperator, ReentrancyGuard, Ownable {
|
|
11
|
+
using RLPReader for bytes;
|
|
12
|
+
using RLPReader for RLPReader.RLPItem;
|
|
13
|
+
|
|
14
|
+
struct AuctionConfig {
|
|
15
|
+
uint32 auctionType; // 4字节竞标类型: 0000 || 00 || 00 竞价方式||单次||公开竞标
|
|
16
|
+
uint256 baseAmount; // 基础金额
|
|
17
|
+
bool isSystemExpiration; // 是否系统时间
|
|
18
|
+
bool isActive; // 是否激活
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
struct Auction {
|
|
22
|
+
uint32 auctionType; // 应该和config保持一致
|
|
23
|
+
uint256 baseAmount; // 基础金额
|
|
24
|
+
uint256 revealTime; // 揭示时间
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
struct AuctionLock {
|
|
28
|
+
address owner;
|
|
29
|
+
bytes32 hashSecret;
|
|
30
|
+
uint256 amount;
|
|
31
|
+
uint32 auctionType; // 4字节竞标类型
|
|
32
|
+
bool isLocked;
|
|
33
|
+
bool secretRevealed;
|
|
34
|
+
uint256 revealTime; // 揭示时间
|
|
35
|
+
uint256 auctionId; // 竞标ID
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
uint256 public activeAuctionsCount;
|
|
39
|
+
uint256 public activeConfigsCount;
|
|
40
|
+
uint256 public onlyOneAuction;
|
|
41
|
+
|
|
42
|
+
mapping(bytes32 => AuctionLock) public lockedTokens;
|
|
43
|
+
mapping(uint256 => AuctionConfig) public auctionConfigs;
|
|
44
|
+
mapping(uint32 => address) public unlockStrategies;
|
|
45
|
+
mapping(uint256 => Auction) public auctions;
|
|
46
|
+
|
|
47
|
+
event AuctionConfigCreated(
|
|
48
|
+
uint256 indexed configId,
|
|
49
|
+
uint32 auctionType,
|
|
50
|
+
uint256 baseAmount,
|
|
51
|
+
uint256 extension
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
event AuctionCreated(
|
|
55
|
+
uint256 indexed auctionId,
|
|
56
|
+
uint32 auctionType,
|
|
57
|
+
uint256 nounce,
|
|
58
|
+
uint256 revealTime
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
event TokensLocked(
|
|
62
|
+
bytes32 indexed lockId,
|
|
63
|
+
address indexed owner,
|
|
64
|
+
uint256 amount,
|
|
65
|
+
uint32 auctionType,
|
|
66
|
+
uint256 auctionId
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
event TokensUnlocked(
|
|
70
|
+
bytes32 indexed lockId,
|
|
71
|
+
address indexed recipient,
|
|
72
|
+
uint256 amount
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
event UnlockStrategyUpdated(uint32 indexed auctionType, address strategy);
|
|
76
|
+
|
|
77
|
+
constructor() ReentrancyGuard() Ownable(msg.sender) {}
|
|
78
|
+
|
|
79
|
+
function isSingleAuction(uint32 auctionType) internal pure returns (bool) {
|
|
80
|
+
return (auctionType & 0x0000FF00) == 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function setUnlockStrategy(uint32 auctionType, address strategy) external onlyOwner {
|
|
84
|
+
require(strategy != address(0), "Invalid strategy address");
|
|
85
|
+
unlockStrategies[auctionType] = strategy;
|
|
86
|
+
emit UnlockStrategyUpdated(auctionType, strategy);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function createAuctionConfig(
|
|
90
|
+
uint32 auctionType,
|
|
91
|
+
uint256 baseAmount,
|
|
92
|
+
uint256 extension
|
|
93
|
+
) external onlyOwner {
|
|
94
|
+
uint256 configId = activeConfigsCount;
|
|
95
|
+
auctionConfigs[configId] = AuctionConfig({
|
|
96
|
+
auctionType: auctionType,
|
|
97
|
+
baseAmount: baseAmount,
|
|
98
|
+
isSystemExpiration: false,
|
|
99
|
+
isActive: true
|
|
100
|
+
});
|
|
101
|
+
activeConfigsCount++;
|
|
102
|
+
emit AuctionConfigCreated(configId, auctionType, baseAmount, extension);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getTodayEndTimestamp() public view returns (uint256) {
|
|
106
|
+
// 获取当前时间戳
|
|
107
|
+
uint256 timestamp = block.timestamp;
|
|
108
|
+
|
|
109
|
+
// 计算当天结束时间 (UTC 23:59:59)
|
|
110
|
+
// 1 天 = 86400 秒
|
|
111
|
+
// 将时间戳除以86400得到天数,加1后乘以86400得到下一天的开始
|
|
112
|
+
// 然后减去1秒得到当天的结束时间
|
|
113
|
+
return ((timestamp / 86400) * 86400) + 86400 - 1;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function createAuction(
|
|
117
|
+
uint256 configId,
|
|
118
|
+
bytes32 hashSecret,
|
|
119
|
+
uint256 expiration
|
|
120
|
+
) external payable nonReentrant returns (bytes32 lockId) {
|
|
121
|
+
AuctionConfig memory config = auctionConfigs[configId];
|
|
122
|
+
require(config.isActive, "Auction not active");
|
|
123
|
+
|
|
124
|
+
Auction memory auction = auctions[onlyOneAuction];
|
|
125
|
+
if (!isSingleAuction(auction.auctionType)) {
|
|
126
|
+
require(auction.revealTime < block.timestamp, "Only one auction is allowed");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 生成唯一的 auctionId
|
|
130
|
+
uint256 auctionId = uint256(keccak256(abi.encodePacked(
|
|
131
|
+
msg.sender,
|
|
132
|
+
block.chainid,
|
|
133
|
+
address(this),
|
|
134
|
+
activeAuctionsCount
|
|
135
|
+
)));
|
|
136
|
+
|
|
137
|
+
uint32 auctionType = config.auctionType;
|
|
138
|
+
uint256 revealTime = isSingleAuction(auctionType) && !config.isSystemExpiration ?
|
|
139
|
+
expiration : getTodayEndTimestamp();
|
|
140
|
+
|
|
141
|
+
onlyOneAuction = isSingleAuction(config.auctionType) ? onlyOneAuction : auctionId;
|
|
142
|
+
|
|
143
|
+
auctions[auctionId] = Auction({
|
|
144
|
+
auctionType: auctionType,
|
|
145
|
+
baseAmount: config.baseAmount,
|
|
146
|
+
revealTime: revealTime
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
lockId = lockTokens(hashSecret, auctionId);
|
|
150
|
+
activeAuctionsCount++;
|
|
151
|
+
|
|
152
|
+
emit AuctionCreated(auctionId, auctionType, activeAuctionsCount-1, revealTime);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function lockTokens(
|
|
156
|
+
bytes32 hashSecret,
|
|
157
|
+
uint256 auctionId
|
|
158
|
+
) public payable nonReentrant returns (bytes32 lockId) {
|
|
159
|
+
Auction storage auction = auctions[auctionId];
|
|
160
|
+
uint32 auctionType = auction.auctionType;
|
|
161
|
+
require(auctionType != 0, "Auction not exist");
|
|
162
|
+
require(msg.value > 0, "Amount must be greater than 0");
|
|
163
|
+
require(msg.value % auction.baseAmount == 0, "Amount must be multiple of baseAmount");
|
|
164
|
+
|
|
165
|
+
lockId = keccak256(abi.encodePacked(
|
|
166
|
+
block.chainid,
|
|
167
|
+
address(this),
|
|
168
|
+
auctionId,
|
|
169
|
+
msg.sender,
|
|
170
|
+
hashSecret,
|
|
171
|
+
auctionType
|
|
172
|
+
));
|
|
173
|
+
|
|
174
|
+
require(!lockedTokens[lockId].isLocked, "Lock ID exists");
|
|
175
|
+
|
|
176
|
+
lockedTokens[lockId] = AuctionLock({
|
|
177
|
+
owner: msg.sender,
|
|
178
|
+
hashSecret: hashSecret,
|
|
179
|
+
amount: msg.value,
|
|
180
|
+
auctionType: auctionType,
|
|
181
|
+
isLocked: true,
|
|
182
|
+
secretRevealed: false,
|
|
183
|
+
revealTime: auction.revealTime,
|
|
184
|
+
auctionId: auctionId
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
emit TokensLocked(lockId, msg.sender, msg.value, auctionType, auctionId);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function unlockTokens(bytes32 lockId, bytes memory receipt) external nonReentrant {
|
|
191
|
+
AuctionLock storage lock = lockedTokens[lockId];
|
|
192
|
+
require(lock.isLocked, "Tokens not locked");
|
|
193
|
+
|
|
194
|
+
address strategy = unlockStrategies[lock.auctionType];
|
|
195
|
+
require(strategy != address(0), "No unlock strategy");
|
|
196
|
+
|
|
197
|
+
RLPReader.RLPItem[] memory items = receipt.toRlpItem().toList();
|
|
198
|
+
require(items.length >= 4, "Invalid receipt format");
|
|
199
|
+
|
|
200
|
+
bytes memory logs = items[3].toBytes();
|
|
201
|
+
RLPReader.RLPItem[] memory logItems = logs.toRlpItem().toList();
|
|
202
|
+
require(logItems.length > 0, "No logs found");
|
|
203
|
+
|
|
204
|
+
RLPReader.RLPItem[] memory logParts = logItems[0].toList();
|
|
205
|
+
require(logParts.length >= 3, "Invalid log format");
|
|
206
|
+
|
|
207
|
+
bytes32 eventTopic = bytes32(logParts[1].toUint());
|
|
208
|
+
bytes memory eventData = logParts[2].toBytes();
|
|
209
|
+
|
|
210
|
+
(address recipient, uint256 unlockAmount) = IUnlockStrategy(strategy).processUnlock(
|
|
211
|
+
eventTopic,
|
|
212
|
+
eventData,
|
|
213
|
+
lock.amount
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
require(unlockAmount <= lock.amount, "Invalid unlock amount");
|
|
217
|
+
lock.isLocked = false;
|
|
218
|
+
|
|
219
|
+
uint256 rewardAmount = 0;
|
|
220
|
+
if (recipient == address(0)) {
|
|
221
|
+
// 竞拍者地址为0,退回给原主人
|
|
222
|
+
recipient = lock.owner;
|
|
223
|
+
rewardAmount = lock.amount;
|
|
224
|
+
} else if (recipient != address(this)) {
|
|
225
|
+
rewardAmount = unlockAmount;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/*
|
|
229
|
+
distributeRewardDirectly(
|
|
230
|
+
"ChainYVaultV2",
|
|
231
|
+
0,
|
|
232
|
+
new address[](1, lock.owner),
|
|
233
|
+
new uint256[](1, rewardAmount)
|
|
234
|
+
);*/
|
|
235
|
+
|
|
236
|
+
emit TokensUnlocked(lockId, recipient, unlockAmount);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function getAuctionInfo(uint256 auctionId)
|
|
240
|
+
external
|
|
241
|
+
view
|
|
242
|
+
returns (
|
|
243
|
+
uint32 auctionType,
|
|
244
|
+
uint256 baseAmount,
|
|
245
|
+
uint256 revealTime
|
|
246
|
+
)
|
|
247
|
+
{
|
|
248
|
+
Auction storage auction = auctions[auctionId];
|
|
249
|
+
return (
|
|
250
|
+
auction.auctionType,
|
|
251
|
+
auction.baseAmount,
|
|
252
|
+
auction.revealTime
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function getAuctionLockInfo(bytes32 lockId)
|
|
257
|
+
external
|
|
258
|
+
view
|
|
259
|
+
returns (
|
|
260
|
+
address owner,
|
|
261
|
+
uint256 amount,
|
|
262
|
+
bool isLocked,
|
|
263
|
+
uint256 revealTime
|
|
264
|
+
)
|
|
265
|
+
{
|
|
266
|
+
AuctionLock storage lock = lockedTokens[lockId];
|
|
267
|
+
return (
|
|
268
|
+
lock.owner,
|
|
269
|
+
lock.amount,
|
|
270
|
+
lock.isLocked,
|
|
271
|
+
lock.revealTime
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
receive() external payable {}
|
|
276
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
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 "./utils/RLPReader.sol";
|
|
7
|
+
import "./interfaces/IUnlockStrategy.sol";
|
|
8
|
+
import "./libraries/TransactionParser.sol";
|
|
9
|
+
import "./coinbase-and-stake/invokeCoinbase.sol";
|
|
10
|
+
|
|
11
|
+
// CoinBaseOperator继承属性,支持Coinbase激励发放
|
|
12
|
+
contract ChainYVaultV2 is ReentrancyGuard, Ownable,CoinbaseOperator {
|
|
13
|
+
using RLPReader for bytes;
|
|
14
|
+
using RLPReader for RLPReader.RLPItem;
|
|
15
|
+
|
|
16
|
+
struct AuctionConfig {
|
|
17
|
+
uint32 auctionType; // 4字节竞标类型: 00 || 00 || 单次/多次 || 公开竞标
|
|
18
|
+
uint256 baseAmount; // 拍卖的最小单位金额
|
|
19
|
+
bool isSystemExpiration; // 是否系统时间
|
|
20
|
+
bool isActive; // 是否激活
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
struct Auction {
|
|
24
|
+
uint32 auctionType; // 应该和config保持一致
|
|
25
|
+
uint256 baseAmount;
|
|
26
|
+
uint256 revealTime; // 揭示时间
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
struct AuctionLock {
|
|
30
|
+
address owner;
|
|
31
|
+
bytes32 hashSecret;
|
|
32
|
+
uint256 amount;
|
|
33
|
+
uint32 auctionType; // 4字节竞标类型
|
|
34
|
+
bool isLocked;
|
|
35
|
+
bool secretRevealed;
|
|
36
|
+
uint256 revealTime; // 揭示时间
|
|
37
|
+
uint256 auctionId; // 竞标ID
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
uint256 public activeAuctionsCount;
|
|
41
|
+
uint256 public activeConfigsCount;
|
|
42
|
+
uint256 public onlyOneAuction;
|
|
43
|
+
CoinbaseOperator public coinbase;
|
|
44
|
+
|
|
45
|
+
mapping(bytes32 => AuctionLock) public lockedTokens;
|
|
46
|
+
mapping(uint256 => AuctionConfig) public auctionConfigs;
|
|
47
|
+
mapping(uint32 => address) public unlockStrategies;
|
|
48
|
+
mapping(uint256 => Auction) public auctions;
|
|
49
|
+
|
|
50
|
+
event AuctionConfigCreated(
|
|
51
|
+
uint256 indexed configId,
|
|
52
|
+
uint32 auctionType,
|
|
53
|
+
uint256 baseAmount,
|
|
54
|
+
uint256 extension
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
event AuctionCreated(
|
|
58
|
+
uint256 indexed auctionId,
|
|
59
|
+
uint32 auctionType,
|
|
60
|
+
uint256 activeAuctionCount,
|
|
61
|
+
uint256 revealTime
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
event TokensLocked(
|
|
65
|
+
bytes32 indexed lockId,
|
|
66
|
+
address indexed owner,
|
|
67
|
+
uint256 amount,
|
|
68
|
+
uint32 auctionType,
|
|
69
|
+
uint256 auctionId
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
event TokensUnlocked(
|
|
73
|
+
bytes32 indexed lockId,
|
|
74
|
+
address indexed recipient,
|
|
75
|
+
uint256 amount
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
constructor(address coinBaseAddress) ReentrancyGuard() Ownable(msg.sender) {
|
|
80
|
+
coinbase = CoinbaseOperator(coinBaseAddress);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function isSingleAuction(uint32 auctionType) internal pure returns (bool) {
|
|
84
|
+
return (auctionType & 0x0000FF00) == 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 创建Config,创建config,createAuction创建必须指定ID
|
|
88
|
+
function createAuctionConfig(
|
|
89
|
+
uint32 auctionType,
|
|
90
|
+
uint256 baseAmount,
|
|
91
|
+
uint256 extension
|
|
92
|
+
) external onlyOwner {
|
|
93
|
+
uint256 configId = activeConfigsCount;
|
|
94
|
+
auctionConfigs[configId] = AuctionConfig({
|
|
95
|
+
auctionType: auctionType,
|
|
96
|
+
baseAmount: baseAmount,
|
|
97
|
+
isSystemExpiration: false,
|
|
98
|
+
isActive: true
|
|
99
|
+
});
|
|
100
|
+
activeConfigsCount++;
|
|
101
|
+
emit AuctionConfigCreated(configId, auctionType, baseAmount, extension);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function getTodayEndTimestamp() public view returns (uint256) {
|
|
105
|
+
// 获取当前时间戳
|
|
106
|
+
uint256 timestamp = block.timestamp;
|
|
107
|
+
|
|
108
|
+
// 计算当天结束时间 (UTC 23:59:59)
|
|
109
|
+
// 1 天 = 86400 秒
|
|
110
|
+
// 将时间戳除以86400得到天数,加1后乘以86400得到下一天的开始
|
|
111
|
+
// 然后减去1秒得到当天的结束时间
|
|
112
|
+
return ((timestamp / 86400) * 86400) + 86400 - 1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function createAuction(
|
|
116
|
+
uint256 configId,
|
|
117
|
+
bytes32 hashSecret, // 哈希时间锁.
|
|
118
|
+
uint256 expiration
|
|
119
|
+
) external payable returns (bytes32 lockId) { // nonReentrant
|
|
120
|
+
AuctionConfig memory config = auctionConfigs[configId];
|
|
121
|
+
require(config.isActive, "Auction not active");
|
|
122
|
+
|
|
123
|
+
Auction memory auction = auctions[onlyOneAuction];
|
|
124
|
+
if (auction.baseAmount != 0 && !isSingleAuction(auction.auctionType)) {
|
|
125
|
+
require(auction.revealTime < block.timestamp, "Only one auction is allowed");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 生成唯一的 auctionId
|
|
129
|
+
uint256 auctionId = uint256(keccak256(abi.encodePacked(
|
|
130
|
+
msg.sender,
|
|
131
|
+
block.chainid,
|
|
132
|
+
address(this),
|
|
133
|
+
activeAuctionsCount
|
|
134
|
+
)));
|
|
135
|
+
uint256 currentCount=activeAuctionsCount;
|
|
136
|
+
activeAuctionsCount++;
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
uint32 auctionType = config.auctionType;
|
|
140
|
+
uint256 revealTime = isSingleAuction(auctionType) && !config.isSystemExpiration ? expiration : getTodayEndTimestamp();
|
|
141
|
+
|
|
142
|
+
onlyOneAuction = isSingleAuction(config.auctionType) ? onlyOneAuction : auctionId;
|
|
143
|
+
|
|
144
|
+
auctions[auctionId] = Auction({
|
|
145
|
+
auctionType: auctionType,
|
|
146
|
+
baseAmount: config.baseAmount,
|
|
147
|
+
revealTime: revealTime
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
lockId = lockTokens(hashSecret, auctionId);
|
|
151
|
+
emit AuctionCreated(auctionId, auctionType, currentCount, revealTime);
|
|
152
|
+
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function lockTokens(
|
|
156
|
+
bytes32 hashSecret,
|
|
157
|
+
uint256 auctionId
|
|
158
|
+
) public payable nonReentrant returns (bytes32 lockId) {
|
|
159
|
+
Auction storage auction = auctions[auctionId];
|
|
160
|
+
uint32 auctionType = auction.auctionType;
|
|
161
|
+
require(auctionType != 0, "Auction not exist");
|
|
162
|
+
require(msg.value > 0, "Amount must be greater than 0");
|
|
163
|
+
require(msg.value % auction.baseAmount == 0, "Amount must be multiple of baseAmount");
|
|
164
|
+
|
|
165
|
+
lockId = keccak256(abi.encodePacked(
|
|
166
|
+
block.chainid,
|
|
167
|
+
address(this),
|
|
168
|
+
auctionId,
|
|
169
|
+
msg.sender,
|
|
170
|
+
hashSecret,
|
|
171
|
+
auctionType
|
|
172
|
+
));
|
|
173
|
+
|
|
174
|
+
require(!lockedTokens[lockId].isLocked, "Lock ID exists");
|
|
175
|
+
|
|
176
|
+
lockedTokens[lockId] = AuctionLock({
|
|
177
|
+
owner: msg.sender,
|
|
178
|
+
hashSecret: hashSecret,
|
|
179
|
+
amount: msg.value,
|
|
180
|
+
auctionType: auctionType,
|
|
181
|
+
isLocked: true,
|
|
182
|
+
secretRevealed: false,
|
|
183
|
+
revealTime: auction.revealTime,
|
|
184
|
+
auctionId: auctionId
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
emit TokensLocked(lockId, msg.sender, msg.value, auctionType, auctionId);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 接受 auction链的receipt事件才可以解锁。
|
|
191
|
+
// 例如: MatchResultWithdrawn(uint256,bytes32,address,uint256)
|
|
192
|
+
function unlockTokens(bytes32 lockId, bytes memory receipt) external nonReentrant {
|
|
193
|
+
AuctionLock storage lock = lockedTokens[lockId];
|
|
194
|
+
require(lock.isLocked, "Tokens not locked");
|
|
195
|
+
|
|
196
|
+
TransactionParser.RecptLog memory log = TransactionParser.parseRecptLog(receipt);
|
|
197
|
+
// 从log中解析出:中标成功者,和解锁金额
|
|
198
|
+
(address recipient, uint256 unlockAmount) = processUnlock(
|
|
199
|
+
log.eventTopic,
|
|
200
|
+
log.eventData,
|
|
201
|
+
lock.amount
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
require(unlockAmount <= lock.amount, "Invalid unlock amount");
|
|
205
|
+
lock.isLocked = false;
|
|
206
|
+
|
|
207
|
+
// CoinBase逻辑,通过CoinBase发送奖励
|
|
208
|
+
uint256 rewardAmount = 0;
|
|
209
|
+
if (recipient == address(0)) {
|
|
210
|
+
// 竞拍者地址为0,退回给原主人
|
|
211
|
+
recipient = lock.owner;
|
|
212
|
+
rewardAmount = lock.amount;
|
|
213
|
+
} else if (recipient != address(this)) {
|
|
214
|
+
rewardAmount = unlockAmount;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (address(coinbase) == address(0)){
|
|
218
|
+
// 方法1 :直接发起转账
|
|
219
|
+
if (recipient == address(0)) {
|
|
220
|
+
// 竞拍者地址为0,退回给原主人
|
|
221
|
+
(bool success, ) = lock.owner.call{value: lock.amount}("");
|
|
222
|
+
require(success, "Transfer failed");
|
|
223
|
+
} else if (recipient != address(this)) {
|
|
224
|
+
// 直接转账
|
|
225
|
+
(bool success, ) = recipient.call{value: unlockAmount}("");
|
|
226
|
+
require(success, "Transfer failed");
|
|
227
|
+
}
|
|
228
|
+
}else{
|
|
229
|
+
// 方法2: CoinBase合约发放
|
|
230
|
+
address[] memory recipients = new address[](1);
|
|
231
|
+
recipients[0] = recipient; // 或 lock.owner,看你的业务
|
|
232
|
+
uint256[] memory amounts = new uint256[](1);
|
|
233
|
+
amounts[0] = rewardAmount;
|
|
234
|
+
|
|
235
|
+
// 调用CoinBase分发奖励接口
|
|
236
|
+
coinbase.distributeRewardDirectly(
|
|
237
|
+
"ChainYVaultV2",
|
|
238
|
+
0, // rewardTypeId,确保 0 对应你定义的类型
|
|
239
|
+
recipients,
|
|
240
|
+
amounts
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
emit TokensUnlocked(lockId, recipient, unlockAmount);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function processUnlock(
|
|
247
|
+
bytes32 eventTopic,
|
|
248
|
+
bytes memory eventData,
|
|
249
|
+
uint256 lockedAmount
|
|
250
|
+
) internal returns (address recipient, uint256 unlockAmount) {
|
|
251
|
+
// 验证事件主题是否为 MatchResultWithdrawn
|
|
252
|
+
require(
|
|
253
|
+
eventTopic == keccak256("MatchResultWithdrawn(uint256,bytes32,address,uint256)"),
|
|
254
|
+
"Invalid event topic"
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
// 解析事件数据
|
|
258
|
+
(
|
|
259
|
+
uint256 auctionId,
|
|
260
|
+
bytes32 lockId,
|
|
261
|
+
address bidder,
|
|
262
|
+
uint256 transferAmount
|
|
263
|
+
) = abi.decode(eventData, (uint256, bytes32, address, uint256));
|
|
264
|
+
|
|
265
|
+
// 返回解锁信息
|
|
266
|
+
return (
|
|
267
|
+
bidder, // 接收者为中标者
|
|
268
|
+
transferAmount // 解锁金额为转账金额
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
function getAuctionInfo(uint256 auctionId)
|
|
274
|
+
external
|
|
275
|
+
view
|
|
276
|
+
returns (
|
|
277
|
+
uint32 auctionType,
|
|
278
|
+
uint256 baseAmount,
|
|
279
|
+
uint256 revealTime
|
|
280
|
+
)
|
|
281
|
+
{
|
|
282
|
+
Auction storage auction = auctions[auctionId];
|
|
283
|
+
return (
|
|
284
|
+
auction.auctionType,
|
|
285
|
+
auction.baseAmount,
|
|
286
|
+
auction.revealTime
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function getAuctionLockInfo(bytes32 lockId)
|
|
291
|
+
external
|
|
292
|
+
view
|
|
293
|
+
returns (
|
|
294
|
+
address owner,
|
|
295
|
+
uint256 amount,
|
|
296
|
+
bool isLocked,
|
|
297
|
+
uint256 revealTime
|
|
298
|
+
)
|
|
299
|
+
{
|
|
300
|
+
AuctionLock storage lock = lockedTokens[lockId];
|
|
301
|
+
return (
|
|
302
|
+
lock.owner,
|
|
303
|
+
lock.amount,
|
|
304
|
+
lock.isLocked,
|
|
305
|
+
lock.revealTime
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function getActiveAuctionsCount() external view returns (uint256){
|
|
310
|
+
return activeAuctionsCount;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function getActiveConfigsCount() external view returns (uint256){
|
|
314
|
+
return activeConfigsCount;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
receive() external payable {}
|
|
318
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
##### 两个合约的地址
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
coinbase 合约地址:"F02F87F086a53cb793E7e920bea648bb52A7d75D"
|
|
5
|
+
|
|
6
|
+
stake 合约地址:"63BC05BC6FCAb99AF9A4c215B2e92a9C6f45D41F"
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
##### 合约接口说明
|
|
10
|
+
|
|
11
|
+
```solidity
|
|
12
|
+
interface ICoinbase {
|
|
13
|
+
// 设置Coinbase合约的白名单(目前是没有限制该方法的调用者,任何人都可以设置)
|
|
14
|
+
function addToWhitelist(address _address) external;
|
|
15
|
+
|
|
16
|
+
// 带抽签的激励记录
|
|
17
|
+
function addCoinbase(
|
|
18
|
+
string calldata source, // 激励来源区
|
|
19
|
+
address[] calldata rewardAddresses,// 激励地址
|
|
20
|
+
string calldata rewardType, // 激励类型,质押、投票等
|
|
21
|
+
uint256 totalAmount, // 激励总额
|
|
22
|
+
uint256 numWinners // 最终可获得激励的人数
|
|
23
|
+
) external;
|
|
24
|
+
|
|
25
|
+
// 不带抽签的激励记录
|
|
26
|
+
function addCoinbaseDirectly(
|
|
27
|
+
string calldata source, // 来源区
|
|
28
|
+
address[] calldata rewardAddresses,// 激励地址
|
|
29
|
+
uint256[] calldata rewardAmounts, // 每个地址对应的金额
|
|
30
|
+
string calldata rewardType // 激励类型
|
|
31
|
+
) external;
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```solidity
|
|
36
|
+
// Stake合约仅作记录,Deposit 和 Withdraw 具体方法自定义
|
|
37
|
+
interface IStake {
|
|
38
|
+
// 单地址的提现:用于大额提现,不会经过抽签
|
|
39
|
+
function withdraw(
|
|
40
|
+
string calldata source, // 来源区
|
|
41
|
+
address account, // 提现地址
|
|
42
|
+
uint256 amount // 提现金额
|
|
43
|
+
) external;
|
|
44
|
+
|
|
45
|
+
// 多地址的提现:用于小额提现,会经过抽签
|
|
46
|
+
function withdrawMultiple(
|
|
47
|
+
string calldata source, // 来源区
|
|
48
|
+
address[] calldata accounts, // 提现地址
|
|
49
|
+
uint256[] calldata amounts // 每个提现地址对应的金额
|
|
50
|
+
) external;
|
|
51
|
+
|
|
52
|
+
// 设置Stake合约的白名单(目前是没有限制该方法的调用者,任何人都可以设置)
|
|
53
|
+
function addToWhitelist(address _address) external;
|
|
54
|
+
}
|
|
55
|
+
```
|