-
Notifications
You must be signed in to change notification settings - Fork 555
Solidity SDK ERC1155 #206
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
Solidity SDK ERC1155 #206
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
a72d7df
Add ERC1155Base
54be670
Update batchMintTo
0ee69c8
add ERC1155 LazyMint
7d0f74d
add ERC1155DelayedReveal
41689fc
Add ERC1155SignatureMint
98835da
Add DropSinglePhase1155 and ERC1155Drop
714bcdc
add totalSupply + fix batchMintTo
59db77c
Add tests for ERC1155Base
f9d98b6
add tests for ERC1155LazyMint
50e25eb
add tests for ERC1155DelayedReveal
227d2f0
run prettier
be86327
update test file names
54f52c1
wip ERC1155SignatureMint test
8b15487
Add tests for ERC1155SignatureMint
6f7f8f2
bug fix: set claimCondition to updated in-memory var
a624522
Add tests for ERC1155Drop
cc052a5
run prettier
e93ae29
wip code comments ERC1155 bases
f6ddf0b
forge update
0ab9ed1
merge main into contracts-sdk-erc1155
cfc5830
update code comments for ERC1155 bases
b0e7b25
Update eip/ERC1155 contract
a2895cf
docs update; run prettier
0052686
merge main into contracts-sdk-erc1155
c6a33b4
Update delayed reveal in ERC1155 bases
e2f9464
docs update
11089f1
pkg release
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,277 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
pragma solidity ^0.8.0; | ||
|
||
import { ERC1155 } from "../eip/ERC1155.sol"; | ||
|
||
import "../extension/ContractMetadata.sol"; | ||
import "../extension/Multicall.sol"; | ||
import "../extension/Ownable.sol"; | ||
import "../extension/Royalty.sol"; | ||
import "../extension/BatchMintMetadata.sol"; | ||
|
||
import "../lib/TWStrings.sol"; | ||
|
||
/** | ||
* The `ERC1155Base` smart contract implements the ERC1155 NFT standard. | ||
* It includes the following additions to standard ERC1155 logic: | ||
* | ||
* - Ability to mint NFTs via the provided `mintTo` and `batchMintTo` functions. | ||
* | ||
* - Contract metadata for royalty support on platforms such as OpenSea that use | ||
* off-chain information to distribute roaylties. | ||
* | ||
* - Ownership of the contract, with the ability to restrict certain functions to | ||
* only be called by the contract's owner. | ||
* | ||
* - Multicall capability to perform multiple actions atomically | ||
* | ||
* - EIP 2981 compliance for royalty support on NFT marketplaces. | ||
*/ | ||
|
||
contract ERC1155Base is ERC1155, ContractMetadata, Ownable, Royalty, Multicall, BatchMintMetadata { | ||
using TWStrings for uint256; | ||
|
||
/*////////////////////////////////////////////////////////////// | ||
State variables | ||
//////////////////////////////////////////////////////////////*/ | ||
|
||
/// @dev The tokenId of the next NFT to mint. | ||
uint256 internal nextTokenIdToMint_; | ||
|
||
/*////////////////////////////////////////////////////////////// | ||
Mappings | ||
//////////////////////////////////////////////////////////////*/ | ||
|
||
/** | ||
* @notice Returns the total supply of NFTs of a given tokenId | ||
* @dev Mapping from tokenId => total circulating supply of NFTs of that tokenId. | ||
*/ | ||
mapping(uint256 => uint256) public totalSupply; | ||
|
||
/*////////////////////////////////////////////////////////////// | ||
Constructor | ||
//////////////////////////////////////////////////////////////*/ | ||
|
||
constructor( | ||
string memory _name, | ||
string memory _symbol, | ||
address _royaltyRecipient, | ||
uint128 _royaltyBps | ||
) ERC1155(_name, _symbol) { | ||
_setupOwner(msg.sender); | ||
_setupDefaultRoyaltyInfo(_royaltyRecipient, _royaltyBps); | ||
} | ||
|
||
/*////////////////////////////////////////////////////////////// | ||
Overriden metadata logic | ||
//////////////////////////////////////////////////////////////*/ | ||
|
||
/// @notice Returns the metadata URI for the given tokenId. | ||
function uri(uint256 _tokenId) public view virtual override returns (string memory) { | ||
string memory uriForToken = _uri[_tokenId]; | ||
if (bytes(uriForToken).length > 0) { | ||
return uriForToken; | ||
} | ||
|
||
string memory batchUri = getBaseURI(_tokenId); | ||
return string(abi.encodePacked(batchUri, _tokenId.toString())); | ||
} | ||
|
||
/*////////////////////////////////////////////////////////////// | ||
Mint / burn logic | ||
//////////////////////////////////////////////////////////////*/ | ||
|
||
/** | ||
* @notice Lets an authorized address mint NFTs to a recipient. | ||
* @dev - The logic in the `_canMint` function determines whether the caller is authorized to mint NFTs. | ||
* - If `_tokenId == type(uint256).max` a new NFT at tokenId `nextTokenIdToMint` is minted. If the given | ||
* `tokenId < nextTokenIdToMint`, then additional supply of an existing NFT is being minted. | ||
* | ||
* @param _to The recipient of the NFTs to mint. | ||
* @param _tokenId The tokenId of the NFT to mint. | ||
* @param _tokenURI The full metadata URI for the NFTs minted (if a new NFT is being minted). | ||
* @param _amount The amount of the same NFT to mint. | ||
*/ | ||
function mintTo( | ||
address _to, | ||
uint256 _tokenId, | ||
string memory _tokenURI, | ||
uint256 _amount | ||
) public virtual { | ||
require(_canMint(), "Not authorized to mint."); | ||
|
||
uint256 tokenIdToMint; | ||
uint256 nextIdToMint = nextTokenIdToMint(); | ||
|
||
if (_tokenId == type(uint256).max) { | ||
tokenIdToMint = nextIdToMint; | ||
nextTokenIdToMint_ += 1; | ||
_setTokenURI(nextIdToMint, _tokenURI); | ||
} else { | ||
require(_tokenId < nextIdToMint, "invalid id"); | ||
tokenIdToMint = _tokenId; | ||
} | ||
|
||
_mint(_to, tokenIdToMint, _amount, ""); | ||
} | ||
|
||
/** | ||
* @notice Lets an authorized address mint multiple NEW NFTs at once to a recipient. | ||
* @dev The logic in the `_canMint` function determines whether the caller is authorized to mint NFTs. | ||
* If `_tokenIds[i] == type(uint256).max` a new NFT at tokenId `nextTokenIdToMint` is minted. If the given | ||
* `tokenIds[i] < nextTokenIdToMint`, then additional supply of an existing NFT is minted. | ||
* The metadata for each new NFT is stored at `baseURI/{tokenID of NFT}` | ||
* | ||
* @param _to The recipient of the NFT to mint. | ||
* @param _tokenIds The tokenIds of the NFTs to mint. | ||
* @param _amounts The amounts of each NFT to mint. | ||
* @param _baseURI The baseURI for the `n` number of NFTs minted. The metadata for each NFT is `baseURI/tokenId` | ||
*/ | ||
function batchMintTo( | ||
address _to, | ||
uint256[] memory _tokenIds, | ||
uint256[] memory _amounts, | ||
string memory _baseURI | ||
) public virtual { | ||
require(_canMint(), "Not authorized to mint."); | ||
require(_amounts.length > 0, "Minting zero tokens."); | ||
require(_tokenIds.length == _amounts.length, "Length mismatch."); | ||
|
||
uint256 nextIdToMint = nextTokenIdToMint(); | ||
uint256 startNextIdToMint = nextIdToMint; | ||
|
||
uint256 numOfNewNFTs; | ||
Check warningCode scanning / Slither Uninitialized local variables
ERC1155Base.batchMintTo(address,uint256[],uint256[],string).numOfNewNFTs (contracts/base/ERC1155Base.sol#143) is a local variable never initialized
|
||
|
||
for (uint256 i = 0; i < _tokenIds.length; i += 1) { | ||
if (_tokenIds[i] == type(uint256).max) { | ||
_tokenIds[i] = nextIdToMint; | ||
|
||
nextIdToMint += 1; | ||
numOfNewNFTs += 1; | ||
} else { | ||
require(_tokenIds[i] < nextIdToMint, "invalid id"); | ||
} | ||
} | ||
|
||
if (numOfNewNFTs > 0) { | ||
_batchMintMetadata(startNextIdToMint, numOfNewNFTs, _baseURI); | ||
} | ||
|
||
nextTokenIdToMint_ = nextIdToMint; | ||
_mintBatch(_to, _tokenIds, _amounts, ""); | ||
} | ||
|
||
/** | ||
* @notice Lets an owner or approved operator burn NFTs of the given tokenId. | ||
* | ||
* @param _owner The owner of the NFT to burn. | ||
* @param _tokenId The tokenId of the NFT to burn. | ||
* @param _amount The amount of the NFT to burn. | ||
*/ | ||
function burn( | ||
address _owner, | ||
Check noticeCode scanning / Slither Local variable shadowing
ERC1155Base.burn(address,uint256,uint256)._owner (contracts/base/ERC1155Base.sol#172) shadows:
- Ownable._owner (contracts/extension/Ownable.sol#15) (state variable)
|
||
uint256 _tokenId, | ||
uint256 _amount | ||
) external virtual { | ||
address caller = msg.sender; | ||
|
||
require(caller == _owner || isApprovedForAll[_owner][caller], "Unapproved caller"); | ||
require(balanceOf[_owner][_tokenId] >= _amount, "Not enough tokens owned"); | ||
|
||
_burn(_owner, _tokenId, _amount); | ||
} | ||
|
||
/** | ||
* @notice Lets an owner or approved operator burn NFTs of the given tokenIds. | ||
* | ||
* @param _owner The owner of the NFTs to burn. | ||
* @param _tokenIds The tokenIds of the NFTs to burn. | ||
* @param _amounts The amounts of the NFTs to burn. | ||
*/ | ||
function burnBatch( | ||
address _owner, | ||
Check noticeCode scanning / Slither Local variable shadowing
ERC1155Base.burnBatch(address,uint256[],uint256[])._owner (contracts/base/ERC1155Base.sol#192) shadows:
- Ownable._owner (contracts/extension/Ownable.sol#15) (state variable)
|
||
uint256[] memory _tokenIds, | ||
uint256[] memory _amounts | ||
) external virtual { | ||
address caller = msg.sender; | ||
|
||
require(caller == _owner || isApprovedForAll[_owner][caller], "Unapproved caller"); | ||
require(_tokenIds.length == _amounts.length, "Length mismatch"); | ||
|
||
for (uint256 i = 0; i < _tokenIds.length; i += 1) { | ||
require(balanceOf[_owner][_tokenIds[i]] >= _amounts[i], "Not enough tokens owned"); | ||
} | ||
|
||
_burnBatch(_owner, _tokenIds, _amounts); | ||
} | ||
|
||
/*////////////////////////////////////////////////////////////// | ||
ERC165 Logic | ||
//////////////////////////////////////////////////////////////*/ | ||
|
||
/// @notice Returns whether this contract supports the given interface. | ||
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC1155, IERC165) returns (bool) { | ||
return | ||
interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165 | ||
interfaceId == 0xd9b67a26 || // ERC165 Interface ID for ERC1155 | ||
interfaceId == 0x0e89341c || // ERC165 Interface ID for ERC1155MetadataURI | ||
interfaceId == type(IERC2981).interfaceId; // ERC165 ID for ERC2981 | ||
} | ||
|
||
/*////////////////////////////////////////////////////////////// | ||
View functions | ||
//////////////////////////////////////////////////////////////*/ | ||
|
||
/// @notice The tokenId assigned to the next new NFT to be minted. | ||
function nextTokenIdToMint() public view virtual returns (uint256) { | ||
return nextTokenIdToMint_; | ||
} | ||
|
||
/*////////////////////////////////////////////////////////////// | ||
Internal (overrideable) functions | ||
//////////////////////////////////////////////////////////////*/ | ||
|
||
/// @dev Returns whether contract metadata can be set in the given execution context. | ||
function _canSetContractURI() internal view virtual override returns (bool) { | ||
return msg.sender == owner(); | ||
} | ||
|
||
/// @dev Returns whether a token can be minted in the given execution context. | ||
function _canMint() internal view virtual returns (bool) { | ||
return msg.sender == owner(); | ||
} | ||
|
||
/// @dev Returns whether owner can be set in the given execution context. | ||
function _canSetOwner() internal view virtual override returns (bool) { | ||
return msg.sender == owner(); | ||
} | ||
|
||
/// @dev Returns whether royalty info can be set in the given execution context. | ||
function _canSetRoyaltyInfo() internal view virtual override returns (bool) { | ||
return msg.sender == owner(); | ||
} | ||
|
||
/// @dev Runs before every token transfer / mint / burn. | ||
function _beforeTokenTransfer( | ||
address operator, | ||
address from, | ||
address to, | ||
uint256[] memory ids, | ||
uint256[] memory amounts, | ||
bytes memory data | ||
) internal virtual override { | ||
super._beforeTokenTransfer(operator, from, to, ids, amounts, data); | ||
|
||
if (from == address(0)) { | ||
for (uint256 i = 0; i < ids.length; ++i) { | ||
totalSupply[ids[i]] += amounts[i]; | ||
} | ||
} | ||
|
||
if (to == address(0)) { | ||
for (uint256 i = 0; i < ids.length; ++i) { | ||
totalSupply[ids[i]] -= amounts[i]; | ||
} | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
pragma solidity ^0.8.0; | ||
|
||
import "./ERC1155LazyMint.sol"; | ||
import "../extension/DelayedReveal.sol"; | ||
|
||
/** | ||
* BASE: ERC1155Base | ||
* EXTENSION: LazyMint, DelayedReveal | ||
* | ||
* The `ERC1155DelayedReveal` contract uses the `ERC1155Base` contract, along with the `LazyMint` and `DelayedReveal` extension. | ||
* | ||
* 'Lazy minting' means defining the metadata of NFTs without minting it to an address. Regular 'minting' | ||
* of NFTs means actually assigning an owner to an NFT. | ||
* | ||
* As a contract admin, this lets you prepare the metadata for NFTs that will be minted by an external party, | ||
* without paying the gas cost for actually minting the NFTs. | ||
* | ||
* 'Delayed reveal' is a mechanism by which you can distribute NFTs to your audience and reveal the metadata of the distributed | ||
* NFTs, after the fact. | ||
* | ||
* You can read more about how the `DelayedReveal` extension works, here: https://blog.thirdweb.com/delayed-reveal-nfts | ||
*/ | ||
|
||
contract ERC1155DelayedReveal is ERC1155LazyMint, DelayedReveal { | ||
using TWStrings for uint256; | ||
|
||
/*////////////////////////////////////////////////////////////// | ||
Constructor | ||
//////////////////////////////////////////////////////////////*/ | ||
|
||
constructor( | ||
string memory _name, | ||
string memory _symbol, | ||
address _royaltyRecipient, | ||
uint128 _royaltyBps | ||
) ERC1155LazyMint(_name, _symbol, _royaltyRecipient, _royaltyBps) {} | ||
|
||
/*////////////////////////////////////////////////////////////// | ||
Overriden Metadata logic | ||
//////////////////////////////////////////////////////////////*/ | ||
|
||
/** | ||
* @notice Returns the metadata URI for an NFT. | ||
* @dev See `BatchMintMetadata` for handling of metadata in this contract. | ||
* | ||
* @param _tokenId The tokenId of an NFT. | ||
*/ | ||
function uri(uint256 _tokenId) public view override returns (string memory) { | ||
(uint256 batchId, ) = getBatchId(_tokenId); | ||
string memory batchUri = getBaseURI(_tokenId); | ||
|
||
if (isEncryptedBatch(batchId)) { | ||
return string(abi.encodePacked(batchUri, "0")); | ||
} else { | ||
return string(abi.encodePacked(batchUri, _tokenId.toString())); | ||
} | ||
} | ||
|
||
/*////////////////////////////////////////////////////////////// | ||
Lazy minting logic | ||
//////////////////////////////////////////////////////////////*/ | ||
|
||
/** | ||
* @notice Lets an authorized address lazy mint a given amount of NFTs. | ||
* | ||
* @param _amount The number of NFTs to lazy mint. | ||
* @param _baseURIForTokens The placeholder base URI for the 'n' number of NFTs being lazy minted, where the | ||
* metadata for each of those NFTs is `${baseURIForTokens}/${tokenId}`. | ||
* @param _data The encrypted base URI + provenance hash for the batch of NFTs being lazy minted. | ||
* @return batchId A unique integer identifier for the batch of NFTs lazy minted together. | ||
*/ | ||
function lazyMint( | ||
uint256 _amount, | ||
string calldata _baseURIForTokens, | ||
bytes calldata _data | ||
) public virtual override returns (uint256 batchId) { | ||
if (_data.length > 0) { | ||
(bytes memory encryptedURI, bytes32 provenanceHash) = abi.decode(_data, (bytes, bytes32)); | ||
if (encryptedURI.length != 0 && provenanceHash != "") { | ||
_setEncryptedData(nextTokenIdToLazyMint + _amount, _data); | ||
} | ||
} | ||
|
||
return super.lazyMint(_amount, _baseURIForTokens, _data); | ||
} | ||
|
||
/*////////////////////////////////////////////////////////////// | ||
Delayed reveal logic | ||
//////////////////////////////////////////////////////////////*/ | ||
|
||
/** | ||
* @notice Lets an authorized address reveal a batch of delayed reveal NFTs. | ||
* | ||
* @param _index The ID for the batch of delayed-reveal NFTs to reveal. | ||
* @param _key The key with which the base URI for the relevant batch of NFTs was encrypted. | ||
*/ | ||
function reveal(uint256 _index, bytes calldata _key) external virtual override returns (string memory revealedURI) { | ||
require(_canReveal(), "Not authorized"); | ||
|
||
uint256 batchId = getBatchIdAtIndex(_index); | ||
revealedURI = getRevealURI(batchId, _key); | ||
|
||
_setEncryptedData(batchId, ""); | ||
_setBaseURI(batchId, revealedURI); | ||
|
||
emit TokenURIRevealed(_index, revealedURI); | ||
} | ||
|
||
/// @dev Checks whether NFTs can be revealed in the given execution context. | ||
function _canReveal() internal view virtual returns (bool) { | ||
return msg.sender == owner(); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Check warning
Code scanning / Slither
Unused state variable