diff --git a/script/20231123-upgrade-auction-claim-unbidded-names/20231123_UpgradeAuctionClaimUnbiddedNames.s.sol b/script/20231123-upgrade-auction-claim-unbidded-names/20231123_UpgradeAuctionClaimUnbiddedNames.s.sol new file mode 100644 index 00000000..7d7d81cf --- /dev/null +++ b/script/20231123-upgrade-auction-claim-unbidded-names/20231123_UpgradeAuctionClaimUnbiddedNames.s.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { console2 as console } from "forge-std/console2.sol"; +import { ContractKey } from "foundry-deployment-kit/configs/ContractConfig.sol"; +import { RNSDeploy } from "script/RNSDeploy.s.sol"; +import { RNSUnified } from "@rns-contracts/RNSUnified.sol"; +import { INSAuction, RNSAuction } from "@rns-contracts/RNSAuction.sol"; + +contract Migration__20231123_UpgradeAuctionClaimeUnbiddedNames is RNSDeploy { + function run() public trySetUp { + _upgradeProxy(ContractKey.RNSAuction, EMPTY_ARGS); + _validataBulkClaimUnbiddedNames({ size: 50 }); + } + + function _validataBulkClaimUnbiddedNames(uint256 size) internal logFn("_validataBulkClaimUnbiddedNames") { + RNSAuction auction = RNSAuction(_config.getAddressFromCurrentNetwork(ContractKey.RNSAuction)); + RNSUnified rns = RNSUnified(_config.getAddressFromCurrentNetwork(ContractKey.RNSUnified)); + + uint256 auctionBalance = size; + console.log("auctionBalance", auctionBalance); + INSAuction.DomainAuction[] memory domainAuctions = new INSAuction.DomainAuction[](auctionBalance); + uint256[] memory reservedIds = new uint256[](auctionBalance); + for (uint256 i; i < auctionBalance; ++i) { + reservedIds[i] = rns.tokenOfOwnerByIndex(address(auction), i); + (domainAuctions[i],) = auction.getAuction(reservedIds[i]); + console.log(reservedIds[i], domainAuctions[i].bid.bidder); + } + + address to = makeAddr("to"); + address[] memory tos = new address[](reservedIds.length); + for (uint256 i; i < tos.length; ++i) { + tos[i] = to; + } + + address operator = auction.getRoleMember(auction.OPERATOR_ROLE(), 0); + uint256 snapshotId = vm.snapshot(); + // allowFailure + vm.prank(operator); + bool[] memory claimeds = auction.bulkClaimUnbiddedNames(tos, reservedIds, true); + for (uint256 i; i < claimeds.length; ++i) { + // flag claimed is true if bidder is null + assertTrue(claimeds[i] == (domainAuctions[i].bid.bidder == address(0x0))); + if (claimeds[i]) assertEq(rns.ownerOf(reservedIds[i]), to); + } + + vm.revertTo(snapshotId); + uint256 firstFailId; + for (uint256 i; i < domainAuctions.length; ++i) { + if (domainAuctions[i].bid.bidder != address(0x0)) { + firstFailId = reservedIds[i]; + break; + } + } + // !allowFailure + vm.prank(operator); + vm.expectRevert(abi.encodeWithSelector(INSAuction.AlreadyBidding.selector, firstFailId)); + claimeds = auction.bulkClaimUnbiddedNames(tos, reservedIds, false); + } +} diff --git a/src/RNSAuction.sol b/src/RNSAuction.sol index 585d68db..86f66cfa 100644 --- a/src/RNSAuction.sol +++ b/src/RNSAuction.sol @@ -165,7 +165,7 @@ contract RNSAuction is Initializable, AccessControlEnumerable, INSAuction { sAuction = _domainAuction[id]; mAuctionId = sAuction.auctionId; if (!(mAuctionId == 0 || mAuctionId == auctionId || sAuction.bid.timestamp == 0)) { - revert AlreadyBidding(); + revert AlreadyBidding(id); } sAuction.auctionId = auctionId; @@ -205,6 +205,42 @@ contract RNSAuction is Initializable, AccessControlEnumerable, INSAuction { if (prvPrice != 0) RONTransferHelper.safeTransfer(prvBidder, prvPrice); } + /** + * @inheritdoc INSAuction + */ + function bulkClaimUnbiddedNames(address[] calldata tos, uint256[] calldata ids, bool allowFailure) + external + onlyRole(OPERATOR_ROLE) + returns (bool[] memory claimeds) + { + if (tos.length != ids.length) revert InvalidArrayLength(); + + uint64 expiry = uint64(block.timestamp.addWithUpperbound(DOMAIN_EXPIRY_DURATION, MAX_EXPIRY)); + claimeds = new bool[](ids.length); + INSUnified rnsUnified = _rnsUnified; + DomainAuction memory auction; + uint256 id; + + for (uint256 i; i < ids.length;) { + id = ids[i]; + auction = _domainAuction[id]; + + if (auction.bid.bidder == address(0x0)) { + // remove id from auction + delete _domainAuction[id]; + rnsUnified.setExpiry(id, expiry); + rnsUnified.transferFrom(address(this), tos[i], id); + claimeds[i] = true; + } else if (!allowFailure) { + revert AlreadyBidding(id); + } + + unchecked { + ++i; + } + } + } + /** * @inheritdoc INSAuction */ diff --git a/src/interfaces/INSAuction.sol b/src/interfaces/INSAuction.sol index e01d3a7d..8f254cdf 100644 --- a/src/interfaces/INSAuction.sol +++ b/src/interfaces/INSAuction.sol @@ -8,7 +8,7 @@ interface INSAuction { error NotYetEnded(); error NoOneBidded(); error NullAssignment(); - error AlreadyBidding(); + error AlreadyBidding(uint256 id); error RatioIsTooLarge(); error NameNotReserved(); error InvalidEventRange(); @@ -91,6 +91,21 @@ interface INSAuction { */ function reserved(uint256 id) external view returns (bool); + /** + * @dev Claim unbidded names and transfer them to the specified addresses. + * + * Requirements: + * - The method caller must be contract operator. + * + * @param tos The array of addresses to transfer domain names to. + * @param ids The id corresponding for namehash of domain names. + * @param allowFailure Flag to indicate whether to allow failure if a domain is already being bid on. + * @return claimeds An array indicating whether each domain name was successfully claimed. + */ + function bulkClaimUnbiddedNames(address[] calldata tos, uint256[] calldata ids, bool allowFailure) + external + returns (bool[] memory claimeds); + /** * @dev Creates a new auction to sale with a specific time period. *