编译器
0.8.20+commit.a1b79de6
文件 1 的 6:ExtensionsDraw.sol
pragma solidity ^0.8.19;
import {IERC1155Burnable} from "../interfaces/IERC1155Burnable.sol";
import {IERC1155Transfer} from "../interfaces/IERC1155Transfer.sol";
import {IERC721Transfer} from "../interfaces/IERC721Transfer.sol";
import {IERC20Transfer} from "../interfaces/IERC20Transfer.sol";
import {Ownable} from "solady/src/auth/Ownable.sol";
interface IExtensions is IERC1155Burnable, IERC1155Transfer {}
struct Submission {
address owner;
uint96 balance;
}
contract ExtensionsDraw is Ownable {
error SubmissionsPaused();
error InvalidExtensionId();
error InvalidQuantity();
error ArrayLengthMismatch();
error NoSubmissionForUser();
error CantRecoverSubmittedTokens();
error NotTokenSubmission();
event Winners(uint256 indexed extensionId, uint256 indexed round, address[] winners);
IExtensions public immutable EXTENSIONS;
uint248 public validTokenIds;
bool public submissionEnabled;
mapping(uint256 => uint256) public currentRound;
mapping(uint256 => Submission[]) public submissionsByExtension;
mapping(address => mapping(uint256 => uint256)) private submissionIndex;
constructor(address extensions) {
EXTENSIONS = IExtensions(extensions);
_initializeOwner(tx.origin);
currentRound[1] = 4;
currentRound[2] = 2;
currentRound[3] = 3;
currentRound[4] = 1;
validTokenIds = 15;
}
function submit(uint256 extensionId, uint96 quantity) external {
if (!submissionEnabled) {
revert SubmissionsPaused();
}
createOrUpdateSubmission(extensionId, quantity);
EXTENSIONS.safeTransferFrom(msg.sender, address(this), extensionId, quantity, "");
}
function batchSubmit(uint256[] calldata extensionIds, uint256[] calldata quantities) external {
if (!submissionEnabled) {
revert SubmissionsPaused();
}
uint256 length = extensionIds.length;
if (length != quantities.length) {
revert ArrayLengthMismatch();
}
for (uint256 i; i < length;) {
createOrUpdateSubmission(extensionIds[i], uint96(quantities[i]));
unchecked {
++i;
}
}
EXTENSIONS.safeBatchTransferFrom(msg.sender, address(this), extensionIds, quantities, "");
}
function createOrUpdateSubmission(uint256 extensionId, uint96 quantity) private {
if (!isValidTokenId(extensionId)) {
revert InvalidExtensionId();
}
if (quantity == 0) {
revert InvalidQuantity();
}
uint256 userSubmissionIndex = submissionIndex[msg.sender][extensionId];
unchecked {
if (userSubmissionIndex == 0) {
submissionsByExtension[extensionId].push(Submission(msg.sender, quantity));
uint256 newIndex = submissionsByExtension[extensionId].length;
submissionIndex[msg.sender][extensionId] = newIndex;
} else {
Submission storage submission =
submissionsByExtension[extensionId][userSubmissionIndex - 1];
submission.balance = uint96(submission.balance + quantity);
}
}
}
function revokeSubmission(uint256 extensionId) external {
uint256 balance = removeUserSubmission(extensionId);
EXTENSIONS.safeTransferFrom(address(this), msg.sender, extensionId, balance, "");
}
function batchRevokeSubmissions(uint256[] calldata extensionIds) external {
uint256 length = extensionIds.length;
uint256[] memory balances = new uint256[](length);
for (uint256 i; i < length;) {
uint256 extensionId = extensionIds[i];
balances[i] = removeUserSubmission(extensionId);
unchecked {
++i;
}
}
EXTENSIONS.safeBatchTransferFrom(address(this), msg.sender, extensionIds, balances, "");
}
function removeUserSubmission(uint256 extensionId) private returns (uint256) {
if (!isValidTokenId(extensionId)) {
revert InvalidExtensionId();
}
uint256 rawSubmissionIndex = submissionIndex[msg.sender][extensionId];
if (rawSubmissionIndex == 0) {
revert NoSubmissionForUser();
}
uint256 userSubmissionIndex = rawSubmissionIndex - 1;
Submission[] storage submissions = submissionsByExtension[extensionId];
uint256 balance = submissions[userSubmissionIndex].balance;
if (rawSubmissionIndex < submissions.length) {
submissions[userSubmissionIndex] = submissions[submissions.length - 1];
}
submissions.pop();
delete submissionIndex[msg.sender][extensionId];
return balance;
}
function setSubmissionEnabled(bool enabled) external onlyOwner {
submissionEnabled = enabled;
}
function enableTokenId(uint248 tokenId) external onlyOwner {
if (tokenId > 255) {
revert InvalidExtensionId();
}
if (!isValidTokenId(tokenId)) {
currentRound[tokenId] = 1;
validTokenIds |= uint248(1 << (tokenId - 1));
}
}
function draw(uint256 extensionId, uint256 maxWinners) external onlyOwner {
if (maxWinners == 0) {
revert InvalidQuantity();
}
Submission[] storage submissions = submissionsByExtension[extensionId];
uint256 length = submissions.length;
uint256 startIndex;
if (length < maxWinners) {
maxWinners = length;
} else {
startIndex = _random(length);
}
processDraw(extensionId, maxWinners, startIndex, submissions);
EXTENSIONS.burn(address(this), extensionId, maxWinners);
}
function processDraw(
uint256 tokenId,
uint256 winners,
uint256 startIndex,
Submission[] storage submissions
) internal {
unchecked {
address[] memory winnersArray = new address[](winners);
uint256 extensionRound = currentRound[tokenId];
currentRound[tokenId] = extensionRound + 1;
uint256 length = submissions.length;
uint256 index = startIndex;
for (uint256 i; i < winners;) {
Submission memory submission = submissions[index];
winnersArray[i] = submission.owner;
if (submission.balance == 1) {
delete submissionIndex[submission.owner][tokenId];
--length;
if (index < length) {
Submission memory lastItem = submissions[length];
submissions[index] = lastItem;
submissionIndex[lastItem.owner][tokenId] = (index + 1);
}
submissions.pop();
} else {
submissions[index].balance = submission.balance - 1;
++index;
}
++i;
if (index >= length) {
index = 0;
}
}
emit Winners(tokenId, extensionRound, winnersArray);
}
}
function getSubmissionIndex(address user, uint256 tokenId) external view returns (uint256) {
uint256 index = submissionIndex[user][tokenId];
if (index == 0) {
revert NoSubmissionForUser();
}
return index - 1;
}
function getAllSubmissions(uint256 tokenId) external view returns (Submission[] memory) {
return submissionsByExtension[tokenId];
}
function _random(uint256 max) internal view returns (uint256) {
return uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao))) % max;
}
function isValidTokenId(uint256 tokenId) internal view returns (bool) {
if (tokenId == 0) {
return false;
}
return (1 << (tokenId - 1) & validTokenIds) != 0;
}
function recoverERC721(address token, uint256 tokenId) external onlyOwner {
IERC721Transfer(token).transferFrom(address(this), msg.sender, tokenId);
}
function recoverERC20(address token, uint256 amount) external onlyOwner {
IERC20Transfer(token).transfer(msg.sender, amount);
}
function onERC1155Received(address operator, address, uint256, uint256, bytes calldata)
external
view
returns (bytes4)
{
if (operator != address(this)) {
revert NotTokenSubmission();
}
return 0xf23a6e61;
}
function onERC1155BatchReceived(
address operator,
address,
uint256[] calldata,
uint256[] calldata,
bytes calldata
) external view returns (bytes4) {
if (operator != address(this)) {
revert NotTokenSubmission();
}
return 0xbc197c81;
}
}
文件 2 的 6:IERC1155Burnable.sol
pragma solidity ^0.8.20;
interface IERC1155Burnable {
function burn(address from, uint256 id, uint256 amount) external;
function batchBurn(address from, uint256[] memory ids, uint256[] memory amounts) external;
}
文件 3 的 6:IERC1155Transfer.sol
pragma solidity ^0.8.20;
interface IERC1155Transfer {
function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;
function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external;
}
文件 4 的 6:IERC20Transfer.sol
pragma solidity ^0.8.13;
interface IERC20Transfer {
function transfer(address, uint256) external;
}
文件 5 的 6:IERC721Transfer.sol
pragma solidity ^0.8.13;
interface IERC721Transfer {
function transferFrom(address, address, uint256) external;
}
文件 6 的 6:Ownable.sol
pragma solidity ^0.8.4;
abstract contract Ownable {
error Unauthorized();
error NewOwnerIsZeroAddress();
error NoHandoverRequest();
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
event OwnershipHandoverRequested(address indexed pendingOwner);
event OwnershipHandoverCanceled(address indexed pendingOwner);
uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE =
0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0;
uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE =
0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d;
uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE =
0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92;
uint256 private constant _OWNER_SLOT_NOT = 0x8b78c6d8;
uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1;
function _initializeOwner(address newOwner) internal virtual {
assembly {
newOwner := shr(96, shl(96, newOwner))
sstore(not(_OWNER_SLOT_NOT), newOwner)
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
}
function _setOwner(address newOwner) internal virtual {
assembly {
let ownerSlot := not(_OWNER_SLOT_NOT)
newOwner := shr(96, shl(96, newOwner))
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
sstore(ownerSlot, newOwner)
}
}
function _checkOwner() internal view virtual {
assembly {
if iszero(eq(caller(), sload(not(_OWNER_SLOT_NOT)))) {
mstore(0x00, 0x82b42900)
revert(0x1c, 0x04)
}
}
}
function _ownershipHandoverValidFor() internal view virtual returns (uint64) {
return 48 * 3600;
}
function transferOwnership(address newOwner) public payable virtual onlyOwner {
assembly {
if iszero(shl(96, newOwner)) {
mstore(0x00, 0x7448fbae)
revert(0x1c, 0x04)
}
}
_setOwner(newOwner);
}
function renounceOwnership() public payable virtual onlyOwner {
_setOwner(address(0));
}
function requestOwnershipHandover() public payable virtual {
unchecked {
uint256 expires = block.timestamp + _ownershipHandoverValidFor();
assembly {
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), expires)
log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller())
}
}
}
function cancelOwnershipHandover() public payable virtual {
assembly {
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), 0)
log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller())
}
}
function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner {
assembly {
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
let handoverSlot := keccak256(0x0c, 0x20)
if gt(timestamp(), sload(handoverSlot)) {
mstore(0x00, 0x6f5e8818)
revert(0x1c, 0x04)
}
sstore(handoverSlot, 0)
}
_setOwner(pendingOwner);
}
function owner() public view virtual returns (address result) {
assembly {
result := sload(not(_OWNER_SLOT_NOT))
}
}
function ownershipHandoverExpiresAt(address pendingOwner)
public
view
virtual
returns (uint256 result)
{
assembly {
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
result := sload(keccak256(0x0c, 0x20))
}
}
modifier onlyOwner() virtual {
_checkOwner();
_;
}
}
{
"compilationTarget": {
"src/extensions/ExtensionsDraw.sol": "ExtensionsDraw"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":ERC1155P/=lib/ERC1155P/contracts/",
":closedsea/=lib/closedsea/src/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
":erc721a-upgradeable/=lib/closedsea/lib/erc721a-upgradeable/contracts/",
":erc721a/=lib/closedsea/lib/erc721a/contracts/",
":forge-std/=lib/forge-std/src/",
":openzeppelin-contracts-upgradeable/=lib/closedsea/lib/openzeppelin-contracts-upgradeable/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/",
":openzeppelin/=lib/openzeppelin-contracts/contracts/",
":operator-filter-registry/=lib/closedsea/lib/operator-filter-registry/",
":solady/=lib/solady/",
":solmate/=lib/solmate/src/"
]
}
[{"inputs":[{"internalType":"address","name":"extensions","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ArrayLengthMismatch","type":"error"},{"inputs":[],"name":"CantRecoverSubmittedTokens","type":"error"},{"inputs":[],"name":"InvalidExtensionId","type":"error"},{"inputs":[],"name":"InvalidQuantity","type":"error"},{"inputs":[],"name":"NewOwnerIsZeroAddress","type":"error"},{"inputs":[],"name":"NoHandoverRequest","type":"error"},{"inputs":[],"name":"NoSubmissionForUser","type":"error"},{"inputs":[],"name":"NotTokenSubmission","type":"error"},{"inputs":[],"name":"SubmissionsPaused","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"extensionId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"winners","type":"address[]"}],"name":"Winners","type":"event"},{"inputs":[],"name":"EXTENSIONS","outputs":[{"internalType":"contract IExtensions","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"extensionIds","type":"uint256[]"}],"name":"batchRevokeSubmissions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"extensionIds","type":"uint256[]"},{"internalType":"uint256[]","name":"quantities","type":"uint256[]"}],"name":"batchSubmit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"cancelOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"completeOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"currentRound","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"extensionId","type":"uint256"},{"internalType":"uint256","name":"maxWinners","type":"uint256"}],"name":"draw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint248","name":"tokenId","type":"uint248"}],"name":"enableTokenId","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getAllSubmissions","outputs":[{"components":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint96","name":"balance","type":"uint96"}],"internalType":"struct Submission[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getSubmissionIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155BatchReceived","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"result","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"ownershipHandoverExpiresAt","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"recoverERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"recoverERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"requestOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"extensionId","type":"uint256"}],"name":"revokeSubmission","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"enabled","type":"bool"}],"name":"setSubmissionEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"submissionEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"submissionsByExtension","outputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint96","name":"balance","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"extensionId","type":"uint256"},{"internalType":"uint96","name":"quantity","type":"uint96"}],"name":"submit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"validTokenIds","outputs":[{"internalType":"uint248","name":"","type":"uint248"}],"stateMutability":"view","type":"function"}]