-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: AINFT contract template structure impl.
- Loading branch information
Jungwoo Pyo
committed
Mar 8, 2023
1 parent
22bb04c
commit 24861dd
Showing
15 changed files
with
21,686 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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,11 @@ | ||
node_modules | ||
.env | ||
coverage | ||
coverage.json | ||
typechain | ||
typechain-types | ||
|
||
# Hardhat files | ||
cache | ||
artifacts | ||
|
This file contains 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,34 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.9; | ||
|
||
// Uncomment this line to use console.log | ||
// import "hardhat/console.sol"; | ||
|
||
contract AINClient { | ||
uint public unlockTime; | ||
address payable public owner; | ||
|
||
event Withdrawal(uint amount, uint when); | ||
|
||
constructor(uint _unlockTime) payable { | ||
require( | ||
block.timestamp < _unlockTime, | ||
"Unlock time should be in the future" | ||
); | ||
|
||
unlockTime = _unlockTime; | ||
owner = payable(msg.sender); | ||
} | ||
|
||
function withdraw() public { | ||
// Uncomment this line, and the import of "hardhat/console.sol", to print a log in your terminal | ||
// console.log("Unlock time is %o and block timestamp is %o", unlockTime, block.timestamp); | ||
|
||
require(block.timestamp >= unlockTime, "You can't withdraw yet"); | ||
require(msg.sender == owner, "You aren't the owner"); | ||
|
||
emit Withdrawal(address(this).balance, block.timestamp); | ||
|
||
owner.transfer(address(this).balance); | ||
} | ||
} |
This file contains 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,31 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.9; | ||
|
||
import "./AINFT721Upgradeable.sol"; | ||
|
||
contract AINFT721LogicV2 is AINFT721Upgradeable { | ||
/** | ||
* @dev execute the additional function updated to proxy contract. | ||
*/ | ||
function example__resetTokenURI(uint256 tokenId) external returns (bool) { | ||
require( | ||
(_msgSender() == ownerOf(tokenId)) || | ||
hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), | ||
"AINFT721LogicV2::example__resetTokenURI() - only contract owner or token holder can call this funciton." | ||
); | ||
bool ret = true; | ||
uint256 currentVersion = tokenURICurrentVersion[tokenId]; | ||
for (uint256 i = currentVersion; i > 0; i--) { | ||
bool success = _rollbackTokenURI(tokenId); | ||
ret = ret || success; | ||
} | ||
return ret; | ||
} | ||
|
||
/** | ||
* @dev get the logic contract version | ||
*/ | ||
function logicVersion() external virtual override returns (uint256) { | ||
return 2; | ||
} | ||
} |
This file contains 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,276 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.9; | ||
|
||
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; | ||
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol"; | ||
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol"; | ||
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; | ||
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; | ||
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol"; | ||
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; | ||
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; | ||
import "@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol"; | ||
|
||
import "@openzeppelin/contracts/utils/Strings.sol"; | ||
|
||
import "./interfaces/IERC4906Upgradeable.sol"; | ||
import "./interfaces/IAINFT.sol"; | ||
|
||
/** | ||
*@dev Proxy contract for AINFT721 | ||
*@notice About design pattern, refer to https://github.com/OpenZeppelin/openzeppelin-labs/tree/master/upgradeability_using_inherited_storage | ||
*/ | ||
contract AINFT721Upgradeable is | ||
Initializable, | ||
ERC721Upgradeable, | ||
ERC721EnumerableUpgradeable, | ||
ERC721URIStorageUpgradeable, | ||
PausableUpgradeable, | ||
AccessControlUpgradeable, | ||
ERC721BurnableUpgradeable, | ||
UUPSUpgradeable, | ||
IERC4906Upgradeable, | ||
IAINFT | ||
{ | ||
using CountersUpgradeable for CountersUpgradeable.Counter; | ||
using Strings for uint256; | ||
|
||
struct MetadataContainer { | ||
address updater; | ||
string metadataURI; | ||
} | ||
|
||
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); | ||
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); | ||
bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); | ||
CountersUpgradeable.Counter private _tokenIdCounter; | ||
string public baseURI; | ||
mapping(bytes32 => MetadataContainer) public metadataStorage; // keccak256(bytes32(tokenId, version)) | ||
mapping(uint256 => uint256) public tokenURICurrentVersion; // tokenId: tokenURIVersion | ||
|
||
/// @custom:oz-upgrades-unsafe-allow constructor | ||
constructor() { | ||
_disableInitializers(); | ||
} | ||
|
||
function initialize( | ||
string memory name_, | ||
string memory symbol_ | ||
) public initializer { | ||
__ERC721_init(name_, symbol_); | ||
__ERC721Enumerable_init(); | ||
__ERC721URIStorage_init(); | ||
__Pausable_init(); | ||
__AccessControl_init(); | ||
__ERC721Burnable_init(); | ||
__UUPSUpgradeable_init(); | ||
|
||
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender); | ||
_grantRole(PAUSER_ROLE, msg.sender); | ||
_grantRole(MINTER_ROLE, msg.sender); | ||
_grantRole(UPGRADER_ROLE, msg.sender); | ||
} | ||
|
||
function _baseURI() internal view override returns (string memory) { | ||
return baseURI; | ||
} | ||
|
||
function pause() public onlyRole(PAUSER_ROLE) { | ||
_pause(); | ||
} | ||
|
||
function unpause() public onlyRole(PAUSER_ROLE) { | ||
_unpause(); | ||
} | ||
|
||
function safeMint( | ||
address to, | ||
string memory uri | ||
) public onlyRole(MINTER_ROLE) { | ||
uint256 tokenId = _tokenIdCounter.current(); | ||
_tokenIdCounter.increment(); | ||
_safeMint(to, tokenId); | ||
_setTokenURI(tokenId, uri); | ||
} | ||
|
||
function _beforeTokenTransfer( | ||
address from, | ||
address to, | ||
uint256 tokenId, | ||
uint256 batchSize | ||
) | ||
internal | ||
override(ERC721Upgradeable, ERC721EnumerableUpgradeable) | ||
whenNotPaused | ||
{ | ||
super._beforeTokenTransfer(from, to, tokenId, batchSize); | ||
} | ||
|
||
function _authorizeUpgrade( | ||
address newImplementation | ||
) internal override onlyRole(UPGRADER_ROLE) {} | ||
|
||
function logicVersion() external virtual returns (uint256) { | ||
return 1; | ||
} | ||
|
||
// The following functions are overrides required by Solidity. | ||
|
||
function _burn( | ||
uint256 tokenId | ||
) internal override(ERC721Upgradeable, ERC721URIStorageUpgradeable) { | ||
super._burn(tokenId); | ||
} | ||
|
||
/** | ||
* @dev Reverts if the `tokenId` has not been minted yet. | ||
*/ | ||
function _requireMinted(uint256 tokenId) internal view virtual override { | ||
super._requireMinted(tokenId); | ||
} | ||
|
||
function tokenURI( | ||
uint256 tokenId | ||
) | ||
public | ||
view | ||
override(ERC721Upgradeable, ERC721URIStorageUpgradeable) | ||
returns (string memory) | ||
{ | ||
//TODO | ||
_requireMinted(tokenId); | ||
return getRecentTokenURI(tokenId); | ||
} | ||
|
||
function supportsInterface( | ||
bytes4 interfaceId | ||
) | ||
public | ||
view | ||
override( | ||
AccessControlUpgradeable, | ||
ERC721EnumerableUpgradeable, | ||
ERC721Upgradeable, | ||
IERC165Upgradeable | ||
) | ||
returns (bool) | ||
{ | ||
return super.supportsInterface(interfaceId); | ||
} | ||
|
||
function setBaseURI( | ||
string memory newBaseURI | ||
) public onlyRole(DEFAULT_ADMIN_ROLE) returns (bool) { | ||
require( | ||
bytes(newBaseURI).length > 0, | ||
"AINFT721::setBaseURI() - Empty newBaseURI" | ||
); | ||
require( | ||
keccak256(bytes(newBaseURI)) != keccak256(bytes(baseURI)), | ||
"AINFT721::setBaseURI() - Same newBaseURI as baseURI" | ||
); | ||
|
||
baseURI = newBaseURI; | ||
return true; | ||
} | ||
|
||
/** | ||
* Override the IAINFT.sol | ||
*/ | ||
|
||
function getMetadataStorageKey( | ||
uint256 tokenId, | ||
uint256 version | ||
) public pure returns (bytes32) { | ||
|
||
return keccak256( | ||
bytes( | ||
string( | ||
abi.encodePacked(tokenId.toString(), "AINFT delimeter", version.toString()) | ||
) | ||
) | ||
); | ||
} | ||
|
||
/** | ||
* @dev version up & upload the metadata | ||
*/ | ||
function updateTokenURI( | ||
uint256 tokenId, | ||
string memory newTokenURI | ||
) external returns (bool) { | ||
require( | ||
(_msgSender() == ownerOf(tokenId)) || | ||
hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), | ||
"AINFT721::updateTokenURI() - not owner of tokenId or contract owner" | ||
); | ||
|
||
uint256 updatedVersion = ++tokenURICurrentVersion[tokenId]; | ||
bytes32 metadataKey = getMetadataStorageKey(tokenId, updatedVersion); | ||
metadataStorage[metadataKey] = MetadataContainer({ | ||
updater: _msgSender(), | ||
metadataURI: newTokenURI | ||
}); | ||
super._setTokenURI(tokenId, newTokenURI); | ||
emit MetadataUpdate(tokenId); | ||
return true; | ||
} | ||
|
||
/** | ||
* @dev if you've ever updated the metadata more than once, rollback the metadata to the previous one and return true. | ||
* if its metadata has not been updated yet or failed to update, return false | ||
*/ | ||
function _rollbackTokenURI(uint256 tokenId) internal returns (bool) { | ||
require( | ||
(_msgSender() == ownerOf(tokenId)) || | ||
hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), | ||
"AINFT721::rollbackTokenURI() - only contract owner or token holder can call this funciton." | ||
); | ||
uint256 currentVersion = tokenURICurrentVersion[tokenId]; | ||
if (currentVersion == 0) return false; | ||
else { | ||
//delete the currentVersion of metadataStorage | ||
bytes32 currentMetadataKey = getMetadataStorageKey(tokenId, currentVersion); | ||
delete metadataStorage[currentMetadataKey]; | ||
|
||
//rollback the version | ||
tokenURICurrentVersion[tokenId]--; | ||
return true; | ||
} | ||
} | ||
|
||
function rollbackTokenURI(uint256 tokenId) external returns (bool) { | ||
return _rollbackTokenURI(tokenId); | ||
} | ||
|
||
function getOriginTokenURI( | ||
uint256 tokenId | ||
) public view override returns (string memory) { | ||
return string(abi.encodePacked(_baseURI(), "/", tokenId.toString())); | ||
} | ||
|
||
function getRecentTokenURI( | ||
uint256 tokenId | ||
) public view override returns (string memory) { | ||
uint256 currentVersion = tokenURICurrentVersion[tokenId]; | ||
if (currentVersion == 0) { | ||
return getOriginTokenURI(tokenId); | ||
} else { | ||
bytes32 metadataKey = getMetadataStorageKey(tokenId, currentVersion); | ||
return metadataStorage[metadataKey].metadataURI; | ||
} | ||
} | ||
|
||
function getCertainTokenURI( | ||
uint256 tokenId, | ||
uint256 uriVersion | ||
) public view override returns (string memory) { | ||
|
||
if (uriVersion == 0) { | ||
return getOriginTokenURI(tokenId); | ||
} else { | ||
bytes32 metadataKey = getMetadataStorageKey(tokenId, uriVersion); | ||
return metadataStorage[metadataKey].metadataURI; | ||
} | ||
} | ||
} |
Empty file.
This file contains 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,16 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.9; | ||
|
||
//TODO: implement the function inside | ||
interface IAINFT { | ||
|
||
function updateTokenURI(uint256 tokenId, string memory newTokenURI) external returns (bool); | ||
|
||
///@dev delete the recent tokenURI and rollback tokenURI to previous one. If the tokenId is origin, it reverts | ||
function rollbackTokenURI(uint256 tokenId) external returns (bool); | ||
|
||
function getOriginTokenURI(uint256 tokenId) external view returns (string memory); | ||
function getRecentTokenURI(uint256 tokenId) external view returns (string memory); | ||
function getCertainTokenURI(uint256 tokenId, uint256 uriVersion) external view returns (string memory); | ||
|
||
} |
This file contains 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,20 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.9; | ||
|
||
import "@openzeppelin/contracts-upgradeable/interfaces/IERC165Upgradeable.sol"; | ||
import "@openzeppelin/contracts-upgradeable/interfaces/IERC721Upgradeable.sol"; | ||
|
||
/// @title ERC-4906: EIP-721 Metadata Update Extension | ||
/// @notice https://eips.ethereum.org/EIPS/eip-4906#backwards-compatibility | ||
interface IERC4906Upgradeable is IERC165Upgradeable, IERC721Upgradeable { | ||
|
||
/// @dev This event emits when the metadata of a token is changed. | ||
/// So that the third-party platforms such as NFT market could | ||
/// timely update the images and related attributes of the NFT. | ||
event MetadataUpdate(uint256 _tokenId); | ||
|
||
/// @dev This event emits when the metadata of a range of tokens is changed. | ||
/// So that the third-party platforms such as NFT market could | ||
/// timely update the images and related attributes of the NFTs. | ||
event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId); | ||
} |
Oops, something went wrong.