diff --git a/contracts/FleekAccessControl.sol b/contracts/FleekAccessControl.sol index bcbad22..733a3b9 100644 --- a/contracts/FleekAccessControl.sol +++ b/contracts/FleekAccessControl.sol @@ -6,29 +6,45 @@ import "../interfaces/IFleekSite.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; abstract contract FleekAccessControl is AccessControl { - bytes32 public constant OWNER_ROLE = keccak256("OWNER_ROLE"); - bytes32 public constant CONTROLLER_ROLE = keccak256("CONTROLLER_ROLE"); + bytes32 public constant COLLECTION_OWNER_ROLE = + keccak256("COLLECTION_OWNER_ROLE"); + bytes32 public constant COLLECTION_CONTROLLER_ROLE = + keccak256("COLLECTION_CONTROLLER_ROLE"); constructor() { - _setRoleAdmin(OWNER_ROLE, DEFAULT_ADMIN_ROLE); - _grantRole(OWNER_ROLE, msg.sender); + _setRoleAdmin(COLLECTION_OWNER_ROLE, DEFAULT_ADMIN_ROLE); + _grantRole(COLLECTION_OWNER_ROLE, msg.sender); } - modifier requireOwner() { + modifier requireCollectionOwner() { require( - hasRole(OWNER_ROLE, msg.sender), - "FleekAccessControl: must have owner role" + hasRole(COLLECTION_OWNER_ROLE, msg.sender), + "FleekAccessControl: must have collection owner role" ); _; } - modifier requireController() { - bool hasPermission = hasRole(CONTROLLER_ROLE, msg.sender) || - hasRole(DEFAULT_ADMIN_ROLE, msg.sender); + modifier requireCollectionController() { require( - hasPermission, - "FleekAccessControl: caller is not a controller" + hasRole(COOLECTION_OWNER_ROLE, msg.sender) || + hasRole(COLLECTION_CONTROLLER_ROLE, msg.sender), + "FleekAccessControl: must have collection controller role" ); _; } + + modifier requireTokenController(uint256 tokenId) { + require( + hasRole(_tokenRole(tokenId, "CONTROLLER"), msg.sender), + "FleekAccessControl: must have token role" + ); + _; + } + + function _tokenRole( + uint256 tokenId, + string role + ) internal pure returns (bytes32) { + return keccak256(abi.encodePacked("TOKEN_", role, tokenId)); + } } diff --git a/contracts/FleekERC721.sol b/contracts/FleekERC721.sol index b5bf7a9..ad518d8 100644 --- a/contracts/FleekERC721.sol +++ b/contracts/FleekERC721.sol @@ -2,87 +2,124 @@ pragma solidity ^0.8.7; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; import "./FleekAccessControl.sol"; import "../interfaces/IFleekERC721.sol"; import "./FleekSite.sol"; -contract FleekERC721 is IFleekERC721, ERC721URIStorage, FleekAccessControl { +contract FleekERC721 is ERC721, FleekAccessControl { + using Strings for uint256; using Counters for Counters.Counter; - Counters.Counter private _tokenIds; - mapping(uint256 => address) private _contracts; + struct Build { + string _commit; + string _repository; + } - constructor( - string memory name, - string memory symbol - ) ERC721(name, symbol) {} + struct Site { + string URI; //ipfs hash example + string ENS; + uint256 currentBuild; + Build[] builds; + } + + Counters.Counter private _tokenIds; + mapping(uint256 => Site) private _sites; + + modifier requireMinted(uint256 tokenId) { + require(_requireMinted(tokenId), "FleekERC721: token not minted"); + } function mint( - uint8 fleekContract, - string memory base64EncodedMetadata, - address account - ) public override requireController returns (uint256) { - uint256 newItemId = _tokenIds.current(); - _safeMint(account, newItemId); - _setTokenURI(newItemId, base64EncodedMetadata); - - // it should be something like a switch - if (fleekContract == FleekContract.Site) { - _mintSite(newItemId, account); - } - + address to, + string memory URI, + string memory ENS, + string memory commit, + string memory repository + ) public payable requireCollectionOwner returns (uint256) { + uint256 tokenId = _tokenIds.current(); + _grantRole(_tokenRole(tokenId), to); + _mint(to, tokenId); _tokenIds.increment(); - return newItemId; + _sites[tokenId] = Site(URI, ENS, 0, [Build(commit, repository)]); + return tokenId; } - function updateTokenURI( - address tokenHolderAddress, + function upgradeTokenBuild( uint256 tokenId, - string memory base64EncodedMetadata - ) public override requireController { - address tokenOwner = ownerOf(tokenId); - require( - tokenOwner == tokenHolderAddress, - "Address does not own provided tokenId" - ); - _setTokenURI(tokenId, base64EncodedMetadata); + string memory commit, + string memory repository + ) public payable require requireMinted(tokenId) requireTokenOwner(tokenId) { + _setTokenBuild(commit, repository); } - function getCurrentTokenId() public view override returns (uint256) { - return _tokenIds.current(); - } - - function supportsInterface( - bytes4 interfaceId + function tokenURI( + uint256 tokenId ) public view virtual - override(IERC165, ERC721, AccessControl) - returns (bool) + override + requireMinted(tokenId) + returns (string memory) { - return super.supportsInterface(interfaceId); - } + address memory owner = ownerOf(tokenId); + Site memory site = _sites[tokenId]; - function tokenContract( - uint256 tokenId - ) public view override returns (address) { - return _contracts[tokenId]; - } - - function _mintSite( - uint256 _newTokenId, - address account - ) internal returns (address) { - // it should receive the parameters from user request - _contracts[_newTokenId] = FleekSite( - "name", - "description", - "thumbnail", - "external_url" + // prettier-ignore + bytes memory dataURI = abi.encodePacked( + '{', + '"owner":"', owner, '",', + '"ENS":"', site.ENS, '",', + '"URI":"', site.URI, '",', + '"build:{', + '"id":"', site.currentBuild, '",', + '"commit":"', site.builds[site.currentBuild]._commit, '",', + '"repository":"', site.builds[site.currentBuild]._repository, '"' + '}', + '}' ); - return _contracts[_newTokenId]; + + return string(abi.encodePacked(_baseURI(), dataURI)); + } + + function _baseURI() internal view virtual override returns (string memory) { + return "data:application/json;base64,"; + } + + function _setTokenURI( + uint256 tokenId, + string memory _tokenURI + ) internal virtual requireMinted(tokenId) requireTokenController(tokenId) { + _sites[tokenId].URI = _tokenURI; + } + + function _setTokenENS( + uint256 tokenId, + string memory _tokenENS + ) internal virtual requireMinted(tokenId) requireTokenController(tokenId) { + _sites[tokenId].ENS = _tokenENS; + } + + function _setTokenBuild( + uint256 tokenId, + string memory _commit, + string memory _repository + ) internal virtual requireMinted(tokenId) requireTokenController(tokenId) { + _sites[tokenId].builds.push(Build(_commit, _repository)); + _sites[tokenId].currentBuild = _sites[tokenId].builds.length - 1; + } + + function _burn(uint256 tokenId) internal virtual override { + require( + ownerOf(tokenId) == msg.sender, + "FleekERC721: must be token owner" + ); + super._burn(tokenId); + + if (bytes(_sites[tokenId]).length != 0) { + delete _sites[tokenId]; + } } }