Skip to content
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

AINFT contract template #1

Open
wants to merge 15 commits into
base: develop
Choose a base branch
from
Open
11 changes: 11 additions & 0 deletions .gitignore
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

31 changes: 31 additions & 0 deletions contracts/AINFT721LogicV2.sol
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 {
jakepyo marked this conversation as resolved.
Show resolved Hide resolved
/**
* @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 = false;
uint256 currentVersion = getTokenURICurrentVersion(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 pure virtual override returns (uint256) {
return 2;
}
}
305 changes: 305 additions & 0 deletions contracts/AINFT721Upgradeable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

//import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
jakepyo marked this conversation as resolved.
Show resolved Hide resolved
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,
ERC721EnumerableUpgradeable,
PausableUpgradeable,
AccessControlUpgradeable,
ERC721BurnableUpgradeable,
UUPSUpgradeable,
IERC4906Upgradeable,
IAINFT
{
using CountersUpgradeable for CountersUpgradeable.Counter;
using Strings for uint256;
minho-comcom-ai marked this conversation as resolved.
Show resolved Hide resolved

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 private baseURI;
mapping(bytes32 => MetadataContainer) private _metadataStorage; // keccak256(bytes32(tokenId, version))
mapping(uint256 => uint256) private _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();
__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 supportsInterface(
bytes4 interfaceId
)
public
view
override(
AccessControlUpgradeable,
ERC721EnumerableUpgradeable,
ERC721Upgradeable,
IERC165Upgradeable
)
returns (bool)
{
return super.supportsInterface(interfaceId);
minho-comcom-ai marked this conversation as resolved.
Show resolved Hide resolved
}

function pause() public onlyRole(PAUSER_ROLE) {
_pause();
}

function unpause() public onlyRole(PAUSER_ROLE) {
_unpause();
}

function safeMint(
address to
)
public
// string memory uri
onlyRole(MINTER_ROLE)
{
uint256 tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
_safeMint(to, tokenId);
}

function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId,
uint256 batchSize
)
internal
override(ERC721Upgradeable, ERC721EnumerableUpgradeable)
whenNotPaused
{
super._beforeTokenTransfer(from, to, tokenId, batchSize);
}

////
// UPGRADEABLE RELATED FUNCTIONS
////

function _authorizeUpgrade(
address newImplementation
) internal override onlyRole(UPGRADER_ROLE) {}

function logicVersion() external pure virtual returns (uint256) {
return 1;
}

////
////

// The following functions are overrides required by Solidity.
function _burn(uint256 tokenId) internal override(ERC721Upgradeable) {
jakepyo marked this conversation as resolved.
Show resolved Hide resolved
super._burn(tokenId);
}

////
// URI VIEW FUNCTIONS
////

function _baseURI() internal view override returns (string memory) {
return baseURI;
}

/**
* @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) returns (string memory) {
_requireMinted(tokenId);
return getRecentTokenURI(tokenId);
}

function _getMetadataStorageKey(
uint256 tokenId,
uint256 version
) internal pure returns (bytes32) {
return
keccak256(
bytes(
jakepyo marked this conversation as resolved.
Show resolved Hide resolved
string(
abi.encodePacked(
tokenId.toString(),
"AINFT delimeter",
version.toString()
)
)
)
);
}

function getMetadataStorage(
jakepyo marked this conversation as resolved.
Show resolved Hide resolved
uint256 tokenId
) public view returns (MetadataContainer memory) {
//TODO
uint256 currentVersion = _tokenURICurrentVersion[tokenId];
bytes32 key = _getMetadataStorageKey(tokenId, currentVersion);

return _metadataStorage[key];
}

function getTokenURICurrentVersion(
uint256 tokenId
) public view returns (uint256) {
return _tokenURICurrentVersion[tokenId];
}

function getOriginTokenURI(
uint256 tokenId
) public view override returns (string memory) {
return string(abi.encodePacked(_baseURI(), "/", tokenId.toString()));
}

function getRecentTokenURI(
jakepyo marked this conversation as resolved.
Show resolved Hide resolved
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;
}
}

////
////

////
// UPDATE URI(METADATA) FUNCTIONS
////

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;
}

/**
* @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);
}
}
18 changes: 18 additions & 0 deletions contracts/interfaces/IAINFT.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

//TODO: implement the function inside
minho-comcom-ai marked this conversation as resolved.
Show resolved Hide resolved
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);

}
20 changes: 20 additions & 0 deletions contracts/interfaces/IERC4906Upgradeable.sol
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);
}
Loading