Skip to content

Add batch setter to ERC1155Sale #28

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions src/tokens/ERC1155/utility/sale/ERC1155Sale.sol
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,52 @@ contract ERC1155Sale is IERC1155Sale, WithdrawControlled, MerkleProofSingleUse {
emit TokenSaleDetailsUpdated(tokenId, cost, supplyCap, startTime, endTime, merkleRoot);
}

/**
* Set the sale details for a batch of tokens.
* @param tokenIds The token IDs to set the sale details for.
* @param costs The amount of payment tokens to accept for each token minted.
* @param supplyCaps The maximum number of tokens that can be minted.
* @param startTimes The start time of the sale. Tokens cannot be minted before this time.
* @param endTimes The end time of the sale. Tokens cannot be minted after this time.
* @param merkleRoots The merkle root for allowlist minting.
* @dev A zero end time indicates an inactive sale.
* @notice The payment token is set globally.
* @dev tokenIds must be sorted ascending without duplicates.
*/
function setTokenSaleDetailsBatch(
uint256[] calldata tokenIds,
uint256[] calldata costs,
uint256[] calldata supplyCaps,
uint64[] calldata startTimes,
uint64[] calldata endTimes,
bytes32[] calldata merkleRoots
) public onlyRole(MINT_ADMIN_ROLE) {
if (
tokenIds.length != costs.length || tokenIds.length != supplyCaps.length
|| tokenIds.length != startTimes.length || tokenIds.length != endTimes.length
|| tokenIds.length != merkleRoots.length
) {
revert InvalidSaleDetails();
}

uint256 lastTokenId;
for (uint256 i = 0; i < tokenIds.length; i++) {
uint256 tokenId = tokenIds[i];
if (i != 0 && lastTokenId >= tokenId) {
revert InvalidTokenIds();
}
lastTokenId = tokenId;

// solhint-disable-next-line not-rely-on-time
if (endTimes[i] < startTimes[i] || endTimes[i] <= block.timestamp) {
revert InvalidSaleDetails();
}
_tokenSaleDetails[tokenId] =
SaleDetails(costs[i], supplyCaps[i], startTimes[i], endTimes[i], merkleRoots[i]);
emit TokenSaleDetailsUpdated(tokenId, costs[i], supplyCaps[i], startTimes[i], endTimes[i], merkleRoots[i]);
}
}

//
// Views
//
Expand Down
119 changes: 118 additions & 1 deletion test/tokens/ERC1155/utility/sale/ERC1155SaleBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.so

// solhint-disable not-rely-on-time

contract ERC1155SaleTest is TestHelper, IERC1155SaleSignals, IERC1155SupplySignals {
contract ERC1155SaleBaseTest is TestHelper, IERC1155SaleSignals, IERC1155SupplySignals {

// Redeclare events
event TransferSingle(
Expand Down Expand Up @@ -85,8 +85,10 @@ contract ERC1155SaleTest is TestHelper, IERC1155SaleSignals, IERC1155SupplySigna
checkSelectorCollision(0x97559600); // setGlobalSaleDetails(uint256,uint256,uint64,uint64,bytes32)
checkSelectorCollision(0x6a326ab1); // setPaymentToken(address)
checkSelectorCollision(0x4f651ccd); // setTokenSaleDetails(uint256,uint256,uint256,uint64,uint64,bytes32)
checkSelectorCollision(0xf07f04ff); // setTokenSaleDetailsBatch(uint256[],uint256[],uint256[],uint64[],uint64[],bytes32[])
checkSelectorCollision(0x01ffc9a7); // supportsInterface(bytes4)
checkSelectorCollision(0x0869678c); // tokenSaleDetails(uint256)
checkSelectorCollision(0xff81434e); // tokenSaleDetailsBatch(uint256[])
checkSelectorCollision(0x44004cc1); // withdrawERC20(address,address,uint256)
checkSelectorCollision(0x4782f779); // withdrawETH(address,uint256)
}
Expand All @@ -100,6 +102,121 @@ contract ERC1155SaleTest is TestHelper, IERC1155SaleSignals, IERC1155SupplySigna
assertEq(deployedAddr, predictedAddr);
}

//
// Setter and getter
//
function testGlobalSaleDetails(
uint256 cost,
uint256 supplyCap,
uint64 startTime,
uint64 endTime,
bytes32 merkleRoot
) public {
endTime = uint64(bound(endTime, block.timestamp + 1, type(uint64).max));
endTime = uint64(bound(endTime, startTime, type(uint64).max));

// Setter
vm.expectEmit(true, true, true, true, address(sale));
emit GlobalSaleDetailsUpdated(cost, supplyCap, startTime, endTime, merkleRoot);
sale.setGlobalSaleDetails(cost, supplyCap, startTime, endTime, merkleRoot);

// Getter
IERC1155SaleFunctions.SaleDetails memory _saleDetails = sale.globalSaleDetails();
assertEq(cost, _saleDetails.cost);
assertEq(supplyCap, _saleDetails.supplyCap);
assertEq(startTime, _saleDetails.startTime);
assertEq(endTime, _saleDetails.endTime);
assertEq(merkleRoot, _saleDetails.merkleRoot);
}

function testTokenSaleDetails(
uint256 tokenId,
uint256 cost,
uint256 supplyCap,
uint64 startTime,
uint64 endTime,
bytes32 merkleRoot
) public {
endTime = uint64(bound(endTime, block.timestamp + 1, type(uint64).max));
endTime = uint64(bound(endTime, startTime, type(uint64).max));

// Setter
vm.expectEmit(true, true, true, true, address(sale));
emit TokenSaleDetailsUpdated(tokenId, cost, supplyCap, startTime, endTime, merkleRoot);
sale.setTokenSaleDetails(tokenId, cost, supplyCap, startTime, endTime, merkleRoot);

// Getter
IERC1155SaleFunctions.SaleDetails memory _saleDetails = sale.tokenSaleDetails(tokenId);
assertEq(cost, _saleDetails.cost);
assertEq(supplyCap, _saleDetails.supplyCap);
assertEq(startTime, _saleDetails.startTime);
assertEq(endTime, _saleDetails.endTime);
assertEq(merkleRoot, _saleDetails.merkleRoot);
}

function testTokenSaleDetailsBatch(
uint256[] memory tokenIds,
uint256[] memory costs,
uint256[] memory supplyCaps,
uint64[] memory startTimes,
uint64[] memory endTimes,
bytes32[] memory merkleRoots
) public {
uint256 minLength = tokenIds.length;
minLength = minLength > costs.length ? costs.length : minLength;
minLength = minLength > supplyCaps.length ? supplyCaps.length : minLength;
minLength = minLength > startTimes.length ? startTimes.length : minLength;
minLength = minLength > endTimes.length ? endTimes.length : minLength;
minLength = minLength > merkleRoots.length ? merkleRoots.length : minLength;
minLength = minLength > 5 ? 5 : minLength; // Max 5
vm.assume(minLength > 0);
// solhint-disable-next-line no-inline-assembly
assembly {
mstore(tokenIds, minLength)
mstore(costs, minLength)
mstore(supplyCaps, minLength)
mstore(startTimes, minLength)
mstore(endTimes, minLength)
mstore(merkleRoots, minLength)
}

// Sort tokenIds ascending and ensure no duplicates
for (uint256 i = 0; i < minLength; i++) {
for (uint256 j = i + 1; j < minLength; j++) {
if (tokenIds[i] > tokenIds[j]) {
(tokenIds[i], tokenIds[j]) = (tokenIds[j], tokenIds[i]);
}
}
}
for (uint256 i = 0; i < minLength - 1; i++) {
vm.assume(tokenIds[i] != tokenIds[i + 1]);
}

for (uint256 i = 0; i < minLength; i++) {
endTimes[i] = uint64(bound(endTimes[i], block.timestamp + 1, type(uint64).max));
endTimes[i] = uint64(bound(endTimes[i], startTimes[i], type(uint64).max));
}

// Setter
for (uint256 i = 0; i < minLength; i++) {
vm.expectEmit(true, true, true, true, address(sale));
emit TokenSaleDetailsUpdated(
tokenIds[i], costs[i], supplyCaps[i], startTimes[i], endTimes[i], merkleRoots[i]
);
}
sale.setTokenSaleDetailsBatch(tokenIds, costs, supplyCaps, startTimes, endTimes, merkleRoots);

// Getter
IERC1155SaleFunctions.SaleDetails[] memory _saleDetails = sale.tokenSaleDetailsBatch(tokenIds);
for (uint256 i = 0; i < minLength; i++) {
assertEq(costs[i], _saleDetails[i].cost);
assertEq(supplyCaps[i], _saleDetails[i].supplyCap);
assertEq(startTimes[i], _saleDetails[i].startTime);
assertEq(endTimes[i], _saleDetails[i].endTime);
assertEq(merkleRoots[i], _saleDetails[i].merkleRoot);
}
}

//
// Withdraw
//
Expand Down
11 changes: 9 additions & 2 deletions test/tokens/ERC1155/utility/sale/ERC1155SaleMint.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";

// solhint-disable not-rely-on-time

contract ERC1155SaleTest is TestHelper, IERC1155SaleSignals, IERC1155SupplySignals, IMerkleProofSingleUseSignals {
contract ERC1155SaleMintTest is TestHelper, IERC1155SaleSignals, IERC1155SupplySignals, IMerkleProofSingleUseSignals {

// Redeclare events
event TransferSingle(
Expand Down Expand Up @@ -105,6 +105,9 @@ contract ERC1155SaleTest is TestHelper, IERC1155SaleSignals, IERC1155SupplySigna
uint64 startTime,
uint64 endTime
) public withFactory(useFactory) {
startTime = uint64(bound(startTime, 0, type(uint64).max - 1));
endTime = uint64(bound(endTime, 0, type(uint64).max - 1));

(tokenId, amount) = assumeSafe(mintTo, tokenId, amount);
if (startTime > endTime) {
uint64 temp = startTime;
Expand Down Expand Up @@ -142,6 +145,9 @@ contract ERC1155SaleTest is TestHelper, IERC1155SaleSignals, IERC1155SupplySigna
uint64 startTime,
uint64 endTime
) public withFactory(useFactory) {
startTime = uint64(bound(startTime, 0, type(uint64).max - 1));
endTime = uint64(bound(endTime, 0, type(uint64).max - 1));

(tokenId, amount) = assumeSafe(mintTo, tokenId, amount);
if (startTime > endTime) {
uint64 temp = startTime;
Expand Down Expand Up @@ -444,8 +450,9 @@ contract ERC1155SaleTest is TestHelper, IERC1155SaleSignals, IERC1155SupplySigna
// Must be ordered
(tokenIds[1], tokenIds[0]) = (tokenIds[0], tokenIds[1]);
}

// solhint-disable-next-line no-inline-assembly
assembly {
// solhint-disable-line no-inline-assembly
mstore(tokenIds, 2) // Exactly 2 unique tokenIds
}
uint256[] memory amounts = new uint256[](2);
Expand Down
2 changes: 1 addition & 1 deletion test/tokens/ERC721/utility/sale/ERC721SaleBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { IERC721AQueryable } from "erc721a/contracts/interfaces/IERC721AQueryabl

// solhint-disable not-rely-on-time

contract ERC721SaleTest is TestHelper, IERC721SaleSignals {
contract ERC721SaleBaseTest is TestHelper, IERC721SaleSignals {

// Redeclare events
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
Expand Down
5 changes: 4 additions & 1 deletion test/tokens/ERC721/utility/sale/ERC721SaleMint.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";

// solhint-disable not-rely-on-time

contract ERC721SaleTest is TestHelper, IERC721SaleSignals, IMerkleProofSingleUseSignals {
contract ERC721SaleMintTest is TestHelper, IERC721SaleSignals, IMerkleProofSingleUseSignals {

// Redeclare events
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
Expand Down Expand Up @@ -69,6 +69,9 @@ contract ERC721SaleTest is TestHelper, IERC721SaleSignals, IMerkleProofSingleUse
uint64 startTime,
uint64 endTime
) public assumeSafe(mintTo, amount) withFactory(useFactory) {
startTime = uint64(bound(startTime, 0, type(uint64).max - 1));
endTime = uint64(bound(endTime, 0, type(uint64).max - 1));

if (startTime > endTime) {
uint64 temp = startTime;
startTime = endTime;
Expand Down
Loading