Mã nguồn mở hoàn toàn · Có thể kiểm chứng trên chuỗi
Giữ ký quỹ, ràng buộc cam kết, sinh số ngẫu nhiên, phân phối quỹ —
tất cả đều được hợp đồng thông minh thực thi tự động, không cần trung gian đáng tin cậy.
Địa chỉ hợp đồng
0x0717DEB12839FE8D0DA8c6869E66d658ECc0b240
Etherscan ↗
[01] — Kiến trúc mô-đun
[02] — Các hàm cốt lõi
Đối tác tạo một trò chơi mua chung. Khóa toàn bộ khoản ký quỹ (giá vé × số lượng) và đồng thời gửi một cam kết băm — cả hai được ràng buộc nguyên tử và không thể tách rời.
function createSession(SessionConfig memory partialConfig) returns (address) {
if (partialConfig.sessionCommitment == bytes32(0)) revert MissingCommitment();
uint16 totalFee = partialConfig.partnerShareBps + platformFeeBps;
if (totalFee > BPS_DENOMINATOR) revert FeeTooHigh(totalFee);
uint256 requiredDeposit = partialConfig.ticketPrice * partialConfig.totalTickets;
uint256 currentBalance = treasury.balances(msg.sender);
if (currentBalance < requiredDeposit) {
revert InsufficientDeposit(currentBalance, requiredDeposit);
}
partialConfig.admin = admin;
partialConfig.creator = msg.sender;
partialConfig.treasury = payable(address(treasury));
partialConfig.platformFeeBps = platformFeeBps;
partialConfig.creatorAbsentPartnerDepositSlashBps = creatorAbsentPartnerDepositSlashBps;
CommitRevealSession session = new CommitRevealSession(partialConfig);
treasury.registerSession(address(session), msg.sender);
treasury.lockPartnerDeposit(address(session), msg.sender, requiredDeposit);
emit SessionCreated(msg.sender, address(session), partialConfig);
return address(session);
}
Người chơi tham gia trò chơi. Một hành động nguyên tử duy nhất sẽ đồng thời gửi tiền vé và số ngẫu nhiên cá nhân. Không cần bước Reveal thứ hai, loại bỏ hoàn toàn cửa sổ tấn công hậu thủ.
function playerBuyAndCommitTicket(uint256 quantity, bytes32 secret, bool useBalance) external payable {
uint256 endTimestamp = unlockTimestamp + commitDurationSeconds;
if (block.timestamp < unlockTimestamp) {
revert TimeConstraintError(block.timestamp, unlockTimestamp);
}
if (block.timestamp > endTimestamp) {
revert TimeConstraintError(block.timestamp, endTimestamp);
}
if (quantity == 0) revert InvalidZeroInput();
if (nextTicketIndex + quantity > totalTickets) revert SoldOut(nextTicketIndex, quantity);
uint256 cost = ticketPrice * quantity;
if (useBalance) {
uint256 treasuryBalance = treasury.balances(msg.sender);
if (treasuryBalance < cost) revert IncorrectETHAmount(cost, treasuryBalance);
treasury.playerPayTicketUseBalance(msg.sender, cost);
} else {
if (msg.value != cost) revert IncorrectETHAmount(cost, msg.value);
treasury.playerPayTicket{value: msg.value}(msg.sender);
}
for (uint256 i = 0; i < quantity; i++) {
ticketToPlayer[nextTicketIndex + i] = msg.sender;
}
nextTicketIndex += quantity;
ticketCounts[msg.sender] += quantity;
playerCommitment ^= secret ^ blockhash(block.number - 1);
emit TicketsPurchased(msg.sender, quantity, nextTicketIndex);
}
Đối tác reveal seed gốc, kích hoạt việc trộn số ngẫu nhiên và quyết toán cuối cùng. Nếu không reveal trong thời hạn, bất kỳ ai cũng có thể gọi hàm phạt để cắt khoản ký quỹ.
function reveal(bytes calldata revealData, bytes32 salt) external onlyCreator {
bytes32 calculatedResult = keccak256(abi.encodePacked(revealData, salt));
if (calculatedResult != sessionCommitment) revert InvalidReveal(sessionCommitment, calculatedResult);
_settle(SettlementType.Normal);
bytes32 combinedResult = keccak256(abi.encodePacked(revealData, playerCommitment));
uint256 winnerIndex = uint256(combinedResult) % totalTickets;
uint256 totalAmount = ticketPrice * totalTickets;
uint256 partnerShare = (totalAmount * partnerShareBps) / BPS_DENOMINATOR;
uint256 platformFee = (totalAmount * platformFeeBps) / BPS_DENOMINATOR;
uint256 rewardAmount = totalAmount - partnerShare - platformFee;
address[] memory users = new address[](3);
users[0] = creator;
users[1] = admin;
users[2] = ticketToPlayer[winnerIndex];
uint256[] memory amounts = new uint256[](3);
amounts[0] = partnerShare;
amounts[1] = platformFee;
amounts[2] = rewardAmount;
treasury.distributeFundsBatch(users, amounts);
treasury.unlockPartnerDeposit(ticketPrice * totalTickets);
emit WinnerSelected(ticketToPlayer[winnerIndex], winnerIndex);
emit SessionSettled(SettlementType.Normal);
}
[03] — Phân tích bảo mật
| # | Vector đe dọa | Trạng thái | Cơ chế phòng vệ |
|---|---|---|---|
| 01 | Second-Mover Attack | Mitigated | Nếu đối tác không Reveal trong thời hạn, hợp đồng sẽ tự động kích hoạt hình phạt, cắt toàn bộ khoản ký quỹ và phân phối cho những người chơi bị ảnh hưởng. Lợi nhuận kỳ vọng của cuộc tấn công E=G−D≤0 |
| 02 | Random Number Manipulation | Mitigated | Số ngẫu nhiên cuối cùng được tạo bằng cách trộn lặp seed của đối tác + dữ liệu khối + N số ngẫu nhiên của người chơi thông qua keccak256; không một bên đơn lẻ nào có thể tự mình kiểm soát kết quả |
| 03 | Miner / Validator Block Manipulation | Mitigated | blockhash của nhiều khối độc lập được dùng như một nguồn entropy; chi phí để miner chọn lọc bỏ khối cao hơn bất kỳ lợi ích thao túng tiềm năng nào |
| 04 | Partner Refuses to Reveal | Mitigated | createSession() bắt buộc msg.value == ticketPrice × ticketCount, bảo đảm khoản ký quỹ D ≥ lợi ích thao túng tối đa G |
| 05 | Sybil Attack | Mitigated | mapping trong playerBuyAndCommitTicket ngăn cùng một địa chỉ mua vé nhiều hơn một lần |
| 06 | Reentrancy Attack | Mitigated | Quyết toán sử dụng mẫu CEI (ghi trạng thái trước khi chuyển) + ReentrancyGuard để ngăn reentrancy |
| 07 | Integer Overflow | Mitigated | Solidity ^0.8 có kiểm tra tràn số tích hợp áp dụng toàn cục; không cần SafeMath |
[04] — Tài nguyên