Skip to content

Commit 47d244d

Browse files
Merge pull request #35 from 0xsequence/1155-sales
Updates to ERC1155Sales
2 parents 216bab0 + 8045b5e commit 47d244d

8 files changed

Lines changed: 1051 additions & 963 deletions

File tree

src/tokens/ERC1155/README.md

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,29 @@ This folder contains contracts that are pre-configured for specific use cases.
1818

1919
The `ERC1155Items` contract is a preset that configures the `ERC1155BaseToken` contract to allow minting of tokens. It adds a `MINTER_ROLE` and a `mint(address to, uint256 amount)` function that can only be called by accounts with the `MINTER_ROLE`.
2020

21+
## Utilities
22+
23+
This folder contains contracts that work in conjunction with other ERC1155 contracts.
24+
2125
### Sale
2226

23-
The `ERC1155Sale` contract is a preset that configures the `ERC1155BaseToken` contract to allow for the sale of tokens. It adds a `mint(address to, , uint256[] memory tokenIds, uint256[] memory amounts, bytes memory data, bytes32[] calldata proof)` function allows for the minting of tokens under various conditions.
27+
The `ERC1155Sale` contract is a utility contract that provides sale functionality for ERC-1155 tokens. It works in conjunction with an `ERC1155Items` contract to handle the minting and sale of tokens under various conditions.
28+
29+
The contract supports multiple sale configurations through a sale details system. Each sale configuration includes:
30+
31+
- Token ID range (minTokenId to maxTokenId)
32+
- Cost per token
33+
- Payment token (ETH or ERC20)
34+
- Supply limit per token ID
35+
- Sale time window (startTime to endTime)
36+
- Optional merkle root for allowlist minting
2437

25-
Conditions may be set by the contract owner. Set the payment token with `setPaymentToken(address paymentTokenAddr)`, then use either the `setTokenSaleDetails(uint256 tokenId, uint256 cost, uint256 remainingSupply, uint64 startTime, uint64 endTime, bytes32 merkleRoot)` function for single token settings or the `setGlobalSaleDetails(uint256 cost, uint256 remainingSupply, uint64 startTime, uint64 endTime, bytes32 merkleRoot)` function for global settings. These functions can only be called by accounts with the `MINT_ADMIN_ROLE`.
38+
Conditions may be set by the contract owner using the `addSaleDetails(SaleDetails calldata details)` function for new configurations or `updateSaleDetails(uint256 saleIndex, SaleDetails calldata details)` for existing ones. These functions can only be called by accounts with the `MINT_ADMIN_ROLE`.
2639

2740
When using a merkle proof, each caller may only use each root once. To prevent collisions ensure the same root is not used for multiple sale details.
28-
Leaves are defined as `keccak256(abi.encodePacked(caller, tokenId))`. The `caller` is the message sender, who will also receive the tokens. The `tokenId` is the id of the token that will be minted, (for global sales `type(uint256).max` is used).
41+
Leaves are defined as `keccak256(abi.encodePacked(caller, tokenId))`. The `caller` is the message sender, who will also receive the tokens. The `tokenId` is the id of the token that will be minted.
2942

30-
For information about the function parameters, please refer to the function specification in `presets/sale/IERC1155Sale.sol`.
43+
For information about the function parameters, please refer to the function specification in `utility/sale/IERC1155Sale.sol`.
3144

3245
## Usage
3346

src/tokens/ERC1155/utility/sale/ERC1155Sale.sol

Lines changed: 109 additions & 232 deletions
Large diffs are not rendered by default.

src/tokens/ERC1155/utility/sale/ERC1155SaleFactory.sol

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,14 @@ contract ERC1155SaleFactory is IERC1155SaleFactory, SequenceProxyFactory {
2323

2424
/// @inheritdoc IERC1155SaleFactoryFunctions
2525
function deploy(
26+
uint256 nonce,
2627
address proxyOwner,
2728
address tokenOwner,
2829
address items,
2930
address implicitModeValidator,
3031
bytes32 implicitModeProjectId
3132
) external returns (address proxyAddr) {
32-
bytes32 salt = keccak256(abi.encode(tokenOwner, items, implicitModeValidator, implicitModeProjectId));
33+
bytes32 salt = keccak256(abi.encode(nonce, tokenOwner, items, implicitModeValidator, implicitModeProjectId));
3334
proxyAddr = _createProxy(salt, proxyOwner, "");
3435
ERC1155Sale(proxyAddr).initialize(tokenOwner, items, implicitModeValidator, implicitModeProjectId);
3536
emit ERC1155SaleDeployed(proxyAddr);
@@ -38,13 +39,14 @@ contract ERC1155SaleFactory is IERC1155SaleFactory, SequenceProxyFactory {
3839

3940
/// @inheritdoc IERC1155SaleFactoryFunctions
4041
function determineAddress(
42+
uint256 nonce,
4143
address proxyOwner,
4244
address tokenOwner,
4345
address items,
4446
address implicitModeValidator,
4547
bytes32 implicitModeProjectId
4648
) external view returns (address proxyAddr) {
47-
bytes32 salt = keccak256(abi.encode(tokenOwner, items, implicitModeValidator, implicitModeProjectId));
49+
bytes32 salt = keccak256(abi.encode(nonce, tokenOwner, items, implicitModeValidator, implicitModeProjectId));
4850
return _computeProxyAddress(salt, proxyOwner, "");
4951
}
5052

Lines changed: 94 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,117 @@
11
// SPDX-License-Identifier: Apache-2.0
22
pragma solidity ^0.8.19;
33

4-
interface IERC1155SaleFunctions {
4+
interface IERC1155Sale {
55

6+
/**
7+
* Sale details.
8+
* @param minTokenId Minimum token ID for this sale. (Inclusive)
9+
* @param maxTokenId Maximum token ID for this sale. (Inclusive)
10+
* @param cost Cost per token.
11+
* @param paymentToken ERC20 token address to accept payment in. address(0) indicates payment in ETH.
12+
* @param supply Maximum number of tokens that can be minted (per token ID).
13+
* @param startTime Start time of the sale. (Inclusive)
14+
* @param endTime End time of the sale. (Inclusive)
15+
* @param merkleRoot Merkle root for allowlist minting. 0 indicates no proof required.
16+
*/
617
struct SaleDetails {
18+
uint256 minTokenId;
19+
uint256 maxTokenId;
720
uint256 cost;
8-
uint256 remainingSupply;
21+
address paymentToken;
22+
uint256 supply;
923
uint64 startTime;
10-
uint64 endTime; // 0 end time indicates sale inactive
11-
bytes32 merkleRoot; // Root of allowed addresses
24+
uint64 endTime;
25+
bytes32 merkleRoot;
1226
}
1327

1428
/**
15-
* Get global sales details.
16-
* @return Sale details.
17-
* @notice Global sales details apply to all tokens.
18-
* @notice Global sales details are overriden when token sale is active.
29+
* Get the total number of sale details.
30+
* @return Total number of sale details.
1931
*/
20-
function globalSaleDetails() external view returns (SaleDetails memory);
32+
function saleDetailsCount() external view returns (uint256);
2133

2234
/**
23-
* Get token sale details.
24-
* @param tokenId Token ID to get sale details for.
25-
* @return Sale details.
26-
* @notice Token sale details override global sale details.
35+
* Get sale details.
36+
* @param saleIndex Index of the sale details to get.
37+
* @return details Sale details.
2738
*/
28-
function tokenSaleDetails(
29-
uint256 tokenId
30-
) external view returns (SaleDetails memory);
39+
function saleDetails(
40+
uint256 saleIndex
41+
) external view returns (SaleDetails memory details);
3142

3243
/**
33-
* Get sale details for multiple tokens.
34-
* @param tokenIds Array of token IDs to retrieve sale details for.
35-
* @return Array of sale details corresponding to each token ID.
36-
* @notice Each token's sale details override the global sale details if set.
44+
* Get sale details for multiple sale indexes.
45+
* @param saleIndexes Array of sale indexes to retrieve sale details for.
46+
* @return details Array of sale details corresponding to each sale index.
3747
*/
38-
function tokenSaleDetailsBatch(
39-
uint256[] calldata tokenIds
40-
) external view returns (SaleDetails[] memory);
48+
function saleDetailsBatch(
49+
uint256[] calldata saleIndexes
50+
) external view returns (SaleDetails[] memory details);
4151

4252
/**
43-
* Get payment token.
44-
* @return Payment token address.
45-
* @notice address(0) indicates payment in ETH.
53+
* Add new sale details.
54+
* @param details Sale details to add.
55+
* @return saleIndex Index of the newly added sale details.
4656
*/
47-
function paymentToken() external view returns (address);
57+
function addSaleDetails(
58+
SaleDetails calldata details
59+
) external returns (uint256 saleIndex);
60+
61+
/**
62+
* Update existing sale details.
63+
* @param saleIndex Index of the sale details to update.
64+
* @param details Sale details to update.
65+
*/
66+
function updateSaleDetails(uint256 saleIndex, SaleDetails calldata details) external;
4867

4968
/**
5069
* Mint tokens.
5170
* @param to Address to mint tokens to.
5271
* @param tokenIds Token IDs to mint.
5372
* @param amounts Amounts of tokens to mint.
5473
* @param data Data to pass if receiver is contract.
55-
* @param paymentToken ERC20 token address to accept payment in. address(0) indicates ETH.
74+
* @param saleIndexes Sale indexes for each token. Must match tokenIds length.
75+
* @param expectedPaymentToken ERC20 token address to accept payment in. address(0) indicates ETH.
5676
* @param maxTotal Maximum amount of payment tokens.
57-
* @param proof Merkle proof for allowlist minting.
77+
* @param proofs Merkle proofs for allowlist minting. Must match tokenIds length.
5878
* @notice Sale must be active for all tokens.
59-
* @dev tokenIds must be sorted ascending without duplicates.
79+
* @dev All sales must use the same payment token.
6080
* @dev An empty proof is supplied when no proof is required.
6181
*/
6282
function mint(
6383
address to,
64-
uint256[] memory tokenIds,
65-
uint256[] memory amounts,
66-
bytes memory data,
67-
address paymentToken,
84+
uint256[] calldata tokenIds,
85+
uint256[] calldata amounts,
86+
bytes calldata data,
87+
uint256[] calldata saleIndexes,
88+
address expectedPaymentToken,
6889
uint256 maxTotal,
69-
bytes32[] calldata proof
90+
bytes32[][] calldata proofs
7091
) external payable;
7192

72-
}
93+
/**
94+
* Emitted when sale details are added.
95+
* @param saleIndex Index of the sale details that were added.
96+
* @param details Sale details that were added.
97+
*/
98+
event SaleDetailsAdded(uint256 saleIndex, SaleDetails details);
7399

74-
interface IERC1155SaleSignals {
100+
/**
101+
* Emitted when sale details are updated.
102+
* @param saleIndex Index of the sale details that were updated.
103+
* @param details Sale details that were updated.
104+
*/
105+
event SaleDetailsUpdated(uint256 saleIndex, SaleDetails details);
75106

76-
event GlobalSaleDetailsUpdated(
77-
uint256 cost, uint256 remainingSupply, uint64 startTime, uint64 endTime, bytes32 merkleRoot
78-
);
79-
event TokenSaleDetailsUpdated(
80-
uint256 tokenId, uint256 cost, uint256 remainingSupply, uint64 startTime, uint64 endTime, bytes32 merkleRoot
81-
);
82-
event ItemsMinted(address to, uint256[] tokenIds, uint256[] amounts);
107+
/**
108+
* Emitted when tokens are minted.
109+
* @param to Address that minted the tokens.
110+
* @param tokenIds Token IDs that were minted.
111+
* @param amounts Amounts of tokens that were minted.
112+
* @param saleIndexes Sale indexes that were minted from.
113+
*/
114+
event ItemsMinted(address to, uint256[] tokenIds, uint256[] amounts, uint256[] saleIndexes);
83115

84116
/**
85117
* Contract already initialized.
@@ -92,15 +124,14 @@ interface IERC1155SaleSignals {
92124
error InvalidSaleDetails();
93125

94126
/**
95-
* Sale is not active globally.
127+
* Sale details index does not exist.
96128
*/
97-
error GlobalSaleInactive();
129+
error SaleDetailsNotFound(uint256 index);
98130

99131
/**
100132
* Sale is not active.
101-
* @param tokenId Invalid Token ID.
102133
*/
103-
error SaleInactive(uint256 tokenId);
134+
error SaleInactive();
104135

105136
/**
106137
* Insufficient tokens for payment.
@@ -110,18 +141,26 @@ interface IERC1155SaleSignals {
110141
*/
111142
error InsufficientPayment(address currency, uint256 expected, uint256 actual);
112143

113-
/**
114-
* Invalid token IDs.
115-
*/
116-
error InvalidTokenIds();
117-
118144
/**
119145
* Insufficient supply of tokens.
120146
* @param remainingSupply Remaining supply.
121147
* @param amount Amount to mint.
122148
*/
123149
error InsufficientSupply(uint256 remainingSupply, uint256 amount);
124150

125-
}
151+
/**
152+
* Invalid array lengths.
153+
*/
154+
error InvalidArrayLengths();
155+
156+
/**
157+
* Invalid amount.
158+
*/
159+
error InvalidAmount();
160+
161+
/**
162+
* Payment token mismatch between sales.
163+
*/
164+
error PaymentTokenMismatch();
126165

127-
interface IERC1155Sale is IERC1155SaleFunctions, IERC1155SaleSignals { }
166+
}

src/tokens/ERC1155/utility/sale/IERC1155SaleFactory.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ interface IERC1155SaleFactoryFunctions {
55

66
/**
77
* Creates an ERC-1155 Sale proxy contract
8+
* @param nonce Extra salt to add to the salt used to compute the address
89
* @param proxyOwner The owner of the ERC-1155 Sale proxy
910
* @param tokenOwner The owner of the ERC-1155 Sale implementation
1011
* @param items The ERC-1155 Items contract address
@@ -14,6 +15,7 @@ interface IERC1155SaleFactoryFunctions {
1415
* @notice The deployed contract must be granted the MINTER_ROLE on the ERC-1155 Items contract.
1516
*/
1617
function deploy(
18+
uint256 nonce,
1719
address proxyOwner,
1820
address tokenOwner,
1921
address items,
@@ -23,6 +25,7 @@ interface IERC1155SaleFactoryFunctions {
2325

2426
/**
2527
* Computes the address of a proxy instance.
28+
* @param nonce Extra salt to add to the salt used to compute the address
2629
* @param proxyOwner The owner of the ERC-1155 Sale proxy
2730
* @param tokenOwner The owner of the ERC-1155 Sale implementation
2831
* @param items The ERC-1155 Items contract address
@@ -31,6 +34,7 @@ interface IERC1155SaleFactoryFunctions {
3134
* @return proxyAddr The address of the ERC-1155 Sale Proxy
3235
*/
3336
function determineAddress(
37+
uint256 nonce,
3438
address proxyOwner,
3539
address tokenOwner,
3640
address items,

src/tokens/common/MerkleProofSingleUse.sol

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import { IMerkleProofSingleUse } from "./IMerkleProofSingleUse.sol";
66
import { MerkleProof } from "openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol";
77

88
/**
9-
* Require single use merkle proofs per address.
9+
* Require single use merkle proofs per root.
1010
*/
1111
abstract contract MerkleProofSingleUse is IMerkleProofSingleUse {
1212

13-
// Stores proofs used by an address
14-
mapping(address => mapping(bytes32 => bool)) private _proofUsed;
13+
// Stores proofs used per root
14+
mapping(bytes32 => mapping(bytes32 => bool)) private _proofUsed;
1515

1616
/**
1717
* Requires the given merkle proof to be valid.
@@ -24,10 +24,11 @@ abstract contract MerkleProofSingleUse is IMerkleProofSingleUse {
2424
*/
2525
function requireMerkleProof(bytes32 root, bytes32[] calldata proof, address addr, bytes32 salt) internal {
2626
if (root != bytes32(0)) {
27-
if (!checkMerkleProof(root, proof, addr, salt)) {
27+
bytes32 leaf = _getLeaf(addr, salt);
28+
if (!_checkMerkleProof(root, proof, leaf)) {
2829
revert MerkleProofInvalid(root, proof, addr, salt);
2930
}
30-
_proofUsed[addr][root] = true;
31+
_proofUsed[root][leaf] = true;
3132
}
3233
}
3334

@@ -45,7 +46,15 @@ abstract contract MerkleProofSingleUse is IMerkleProofSingleUse {
4546
address addr,
4647
bytes32 salt
4748
) public view returns (bool) {
48-
return !_proofUsed[addr][root] && MerkleProof.verify(proof, root, keccak256(abi.encodePacked(addr, salt)));
49+
return _checkMerkleProof(root, proof, _getLeaf(addr, salt));
50+
}
51+
52+
function _getLeaf(address addr, bytes32 salt) internal pure returns (bytes32) {
53+
return keccak256(abi.encodePacked(addr, salt));
54+
}
55+
56+
function _checkMerkleProof(bytes32 root, bytes32[] calldata proof, bytes32 leaf) internal view returns (bool) {
57+
return !_proofUsed[root][leaf] && MerkleProof.verify(proof, root, leaf);
4958
}
5059

5160
}

0 commit comments

Comments
 (0)