diff --git a/contracts/Entity.sol b/contracts/Entity.sol index 5e6c23d..caa006d 100644 --- a/contracts/Entity.sol +++ b/contracts/Entity.sol @@ -15,11 +15,15 @@ import "./IUtility.sol"; import "./RoyaltiesReceiver.sol"; struct FragmentInitData { + // The Proto-Fragment ID uint256 fragmentId; uint256 maxSupply; + // The address of the `Fragment` Contract address fragmentsLibrary; + // The address of RezProxy Contract that delegates all its calls to a Vault Contract address payable vault; bool unique; + /// If `updatable` is false, the Fragment cannot be updated bool updateable; } @@ -27,6 +31,9 @@ struct EntityData { uint64 blockNum; } + +/// @title An Entity/Fragment Contract. This Contract stores one and only one Entity/Fragment. +/// @dev This contract is an ERC-721 Initializable Contract contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { using SafeERC20 for IERC20; using Counters for Counters.Counter; @@ -37,8 +44,11 @@ contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { Counters.Counter private _tokenIds; // mapping for fragments storage + // Maps a Fragment ID to an EntityData Struct mapping(uint256 => EntityData) private _idToBlock; + // Maps a Fragment ID to a Data Hash mapping(uint256 => bytes32) private _entityRefs; + // Maps a Data Hash To a Set of Fragment IDs mapping(uint256 => EnumerableSet.UintSet) private _envToId; IFragment private _fragmentsLibrary; @@ -47,12 +57,15 @@ contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { uint256 private _publicMintingPrice; uint256 private _dutchStartBlock; uint256 private _dutchStep; + // The ID of the Proto-Fragment that is linked with this Contract uint256 private _fragmentId; uint256 private _maxSupply; uint256 private _maxPublicAmount; uint256 private _publicCap; uint8 private _publicMinting; // 0 no, 1 normal, 2 dutch auction + // ¿All Fragment Tokens must have a unique `environment`? bool private _uniqueEnv; + // If this is false, the Fragment/Entity assosciated with this Entity Contract cannot be updated bool private _canUpdate; uint8 private constant NO_PUB_MINTING = 0; @@ -100,6 +113,7 @@ contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { } } + /// @notice Returns the address of the owner of this Fragment/Entity function fragmentOwner() public view returns (address) { return _fragmentsLibrary.ownerOf(_fragmentId); } @@ -109,6 +123,12 @@ contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { _; } + + /// @notice The de-facto Constructor of the Entity Smart Contract + /// @param tokenName - The name of the ERC-721 Token of this ERC-721 Contract + /// @param tokenSymbol - The symbol of the ERC-721 Token of this ERC-721 Contract + /// @param params - A Fragment. The fragment represented using the struct `FragmentInitData` + /// @dev The `initializer` modifier ensures this function is only called once function bootstrap( string calldata tokenName, string calldata tokenSymbol, @@ -135,6 +155,8 @@ contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { setupRoyalties(payable(address(_vault)), FRAGMENT_ROYALTIES_BPS); } + /// @notice Returns the tokenURI of the ERC-721 Token with ID `tokenId`. (Note: Every ERC-721 Contract must have this function) + /// The tokenURI on an NFT is a unique identifier of what the token "looks" like. A URI could be an API call over HTTPS, an IPFS hash, or anything else unique. (https://www.freecodecamp.org/news/how-to-make-an-nft-and-render-on-opensea-marketplace/#:~:text=come%20into%20play.-,TokenURI,hash%2C%20or%20anything%20else%20unique.) function tokenURI(uint256 tokenId) public view @@ -155,6 +177,7 @@ contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { ); } + /// @notice Returns a JSON that represents the storefront-level metadata (for example a Collection in OpenSea) for your contract function contractURI() public view returns (string memory) { IUtility ut = IUtility(_fragmentsLibrary.getUtilityLibrary()); return @@ -167,6 +190,10 @@ contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { ); } + /// @notice Set the Contract Information + /// @param contractName - The contract name + /// @param desc - The contract description + /// @param url - The contract URL function setContractInfo( string calldata contractName, string calldata desc, @@ -177,18 +204,22 @@ contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { _url = url; } + /// @notice Returns the Fragment ID of the Entity Contract function getFragment() external view returns (uint256) { return _fragmentId; } + /// @notice Returns he address of the `Fragment` Contract function getLibrary() external view returns (address) { return address(_fragmentsLibrary); } + /// @notice Get the address of the Assosciated Vault Smart Contract function getVault() external view returns (address) { return address(_vault); } + /// @notice Given a token with ID `tokenID`, return the data hash of the token and the Block Number where the Token Data was last modified function getData(uint256 tokenId) external view @@ -197,6 +228,7 @@ contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { return (_entityRefs[tokenId], _idToBlock[tokenId].blockNum); } + /// @notice Check if the Data Hash `dataHash` corresponds with the Token with id `id` function containsId(uint160 dataHash, uint256 id) external view @@ -205,10 +237,16 @@ contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { return _envToId[dataHash].contains(id); } + /// @notice Set the state variable `_delegate`. `_delegate` is in charge of authenticating sigatures on this contract function setDelegate(address delegate) public onlyOwner { _delegate = delegate; } + /// @notice to update the Fragment with id `id` (only the wwner of the Fragment can call this function) + /// @dev Note: The Fragment must have been created with `updateable` set to true - otherwise the update is not allowed + /// @param signature - A signature from the the state variable `_delegate` + /// @param id - ¿The ID of the Fragment? + /// @param environment - ¿The New Data of the Fragment? function update( bytes calldata signature, uint256 id, @@ -238,6 +276,7 @@ contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { abi.encodePacked(_fragmentId, environment) ); + _envToId[uint256(dataHash)].add(id); _entityRefs[id] = dataHash; @@ -246,6 +285,7 @@ contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { emit Updated(id); } + /// @notice Uploads `amount` number of Fragment Tokens into this Contract, all of which have the data hash of `dataHash`. The owner of all these Fragment Tokens is `msg.sender` function _upload(bytes32 dataHash, uint96 amount) internal { for (uint256 i = 0; i < amount; i++) { _tokenIds.increment(); @@ -255,6 +295,7 @@ contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { "Max minting limit has been reached" ); + // Ensure either `_uniqueEnv` is false or `_envToId[uint256(dataHash)]` does not exist require( !_uniqueEnv || _envToId[uint256(dataHash)].length() == 0, "Unique token already minted." @@ -269,6 +310,9 @@ contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { } } + /// Adds `amount` Fragments to this Contract. NOTE: ONLY THE OWNER OF THIS CONTRACT CAN CALL THIS FUNCTION + /// @param environment - ¿ + /// @param amount - The amount of Fragments to add to this contract function upload(bytes calldata environment, uint96 amount) external onlyOwner @@ -293,6 +337,14 @@ contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { We use a signature to allow an entity off chain to verify that the content is valid and vouch for it. If we want to skip that crafted address and random signatures can be used */ + /// @notice Allows the `msg.sender` to mint `amount` number of Fragment Tokens (which all have the same `environment`) + /// @param `signature` - The signature provided by the `_delegate` state variable. The signature authenticates whether the the caller can mint/purchase these Fragment Tokens + /// @param `environment` - ¿ + /// @param `amount` - The amount of Fragments the caller wants to purchase. + /// @dev The caller can only mint if the `_publicCap` is greater than `_tokenIds.current() + (amount - 1)` + /// The caller can only mint a maximum of `_maxPublicAmount` tokens + /// The price of each Token is `_publicMintingPrice` + /// function mint( bytes calldata signature, bytes calldata environment, @@ -339,6 +391,9 @@ contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { We use a signature to allow an entity off chain to verify that the content is valid and vouch for it. If we want to skip that crafted address and random signatures can be used */ + /// @notice Allows the caller to bid for a Fragment (Note: The Auction Type is a Dutch Action. Therefore, the price of the Fragment will drop after every time-step/block) + /// @param `signature` - The signature provided by the `_delegate` state variable. The signature authenticates whether the the caller can mint/purchase these Fragment Tokens + /// @param `environment` - ¿ function bid(bytes calldata signature, bytes calldata environment) external payable @@ -391,6 +446,10 @@ contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { _tokenIds.current() < _publicCap; } + /// @notice ¿Allow the Fragment(s) in this Contract to be Publicly Minted at price `price`? NOTE: ONLY THE OWNER OF THIS CONTRACT CAN CALL THIS FUNCTION + /// @param price - The Minimum Price a Fragment can be bought for + /// @param maxAmount - The maximum number of Fragments that can be bought in a single function call + /// @param cap - The number of Fragments that are for sale function setPublicSale( uint256 price, uint96 maxAmount, @@ -402,7 +461,13 @@ contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { _publicCap = cap; assert(_publicCap <= _maxSupply); } - + // A Dutch auction is one of several similar types of auctions for buying or selling goods. + // Most commonly, it means an auction in which the auctioneer begins with a high asking price in the case of selling, + // and lowers it until some participant accepts the price, or it reaches a predetermined reserve price. + /// @notice ¿Allow the Fragment(s) in this Contract to be auctioned to the highest bigger (The exact auction type used is a Dutch Auction) NOTE: ONLY THE OWNER OF THIS CONTRACT CAN CALL THIS FUNCTION + /// @param maxPrice - The starting price + /// @param priceStep - The amount the price decreases at every time step + /// @param slots - The number of Fragments that are for sale function openDutchAuction( uint256 maxPrice, uint256 priceStep, @@ -416,10 +481,13 @@ contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { assert(_publicCap <= _maxSupply); } + /// @notice Stop public minting (i.e prevent further Fragments from being minted by anyone) NOTE: ONLY THE OWNER OF THIS CONTRACT CAN CALL THIS FUNCTION function stopMarket() external onlyOwner { _publicMinting = NO_PUB_MINTING; } + /// @notice Transfer ERC-20 Token with token contract address `tokenAddress` and amount `tokenAmount` to `fragmentOwner()`. + /// NOTE: ONLY THE CONTRACT OWNER CAN THIS CALL THIS FUNCTION function recoverERC20(address tokenAddress, uint256 tokenAmount) external onlyOwner @@ -428,6 +496,8 @@ contract Entity is ERC721Enumerable, Initializable, RoyaltiesReceiver { IERC20(tokenAddress).safeTransfer(fragmentOwner(), tokenAmount); } + /// @notice Claim Ether that is held by this contract (i.e by this Vault Contract). + /// NOTE: ONLY THE CONTRACT OWNER CAN THIS CALL THIS FUNCTION function recoverETH(uint256 amount) external onlyOwner { // notice: fragmentOwner, not owner due to owner used for opensea workaround... payable(fragmentOwner()).transfer(amount); diff --git a/contracts/Fragment.sol b/contracts/Fragment.sol index 5c04ae6..5178fb6 100644 --- a/contracts/Fragment.sol +++ b/contracts/Fragment.sol @@ -27,6 +27,8 @@ struct StakeData { } // this contract uses proxy +/// @title This contract holds all the Proto-Fragments +/// @dev The Royalty Payment Logic is implemented in `RoyaltiesReceiever` contract Fragment is ERC721Enumerable, Ownable, @@ -80,10 +82,10 @@ contract Fragment is // Use a human readable counter for IDs bytes32 private constant FRAGMENT_COUNTER = keccak256("fragcolor.fragment.counter"); - // ID -> Fragment Hash + // Token ID -> Fragment Hash bytes32 private constant FRAGMENT_FRAGMENTS_ID2HASH = keccak256("fragcolor.fragment.fragments"); - // ID -> Fragment Hash + // Token ID -> Fragment Hash (¿Shouldn't this be Fragment Hash -> Token ID?) bytes32 private constant FRAGMENT_FRAGMENTS_HASH2ID = keccak256("fragcolor.fragment.fragments"); // keep track of rezzed entitites @@ -115,6 +117,16 @@ contract Fragment is super.supportsInterface(interfaceId); } + /// @notice The de-facto Constructor of the Fragment Smart Contract. + /// - Sets the addresses of the following contracts in storage slots: + /// 1. Utility Contract + /// 2. Entity Contract + /// 3. Vault Contract + /// - Declares the owner of this contract (i.e of the `Fragment` contract) as Royalty Receiver and also sets its Royalty Rate (in bps) + /// @param entityContract - The address of the RezProxy Contract that delegates all its calls to an Entity Contract + /// @param fragmentsLibrary - The address of the `Fragment` Contract + /// @dev The `initializer` modifier ensures this function is only called once + // The de-facto constructor stores `entityContract` in storage slot `SLOT_entityContract` and the `fragmentsLibrary` in storage slot `SLOT_fragmentsLibrary` function bootstrap() public payable initializer { // Ownable Ownable._bootstrap(); @@ -135,6 +147,7 @@ contract Fragment is setupRoyalties(payable(owner()), FRAGMENT_ROYALTIES_BPS); } + /// @notice Loads the Address of the Utility Library from Storage function getUtilityLibrary() public view returns (address addr) { bytes32 slot = SLOT_utilityLibrary; assembly { @@ -142,6 +155,7 @@ contract Fragment is } } + /// @notice Loads the Address of the "controller" of this contract (i.e of the Fragments Library) function getController() public view returns (address addr) { bytes32 slot = SLOT_controller; assembly { @@ -149,6 +163,8 @@ contract Fragment is } } + /// @notice Returns the tokenURI of the ERC-721 Token with ID `tokenId`. (Note: Every ERC-721 Contract must have this function) + /// The tokenURI on an NFT is a unique identifier of what the token "looks" like. A URI could be an API call over HTTPS, an IPFS hash, or anything else unique. (https://www.freecodecamp.org/news/how-to-make-an-nft-and-render-on-opensea-marketplace/#:~:text=come%20into%20play.-,TokenURI,hash%2C%20or%20anything%20else%20unique.) function tokenURI(uint256 tokenId) public view @@ -166,6 +182,8 @@ contract Fragment is return ut.buildFragmentRootMetadata(owner(), FRAGMENT_ROYALTIES_BPS); } + /// @notice Get the Token ID of Fragment Hash `fragmentHash` + /// @param fragmentHash - The Fragment Hash function idOf(bytes32 fragmentHash) external view returns (uint256) { uint256[1] storage s; bytes32 sslot = bytes32( @@ -182,6 +200,8 @@ contract Fragment is return s[0]; } + /// @notice Get the Fragment Hash of Token ID `fragmentId` + /// @param fragmentId - The Token ID function hashOf(uint256 fragmentId) public view returns (bytes32) { bytes32[1] storage s; bytes32 sslot = bytes32( @@ -198,6 +218,7 @@ contract Fragment is return s[0]; } + /// @notice Returns the addresses of all the Entity contracts assosciated with a Proto-Fragment (whose ID is `fragmentID`) function getEntities(uint256 fragmentId) external view @@ -218,6 +239,7 @@ contract Fragment is } } + /// @notice Returns a Boolean indicating whether `addr` is one of the Entity Contracts assosciated with a Proto-Fragment (whose ID is `fragmentID`) function isEntityOf(address addr, uint256 fragmentId) external view @@ -234,6 +256,7 @@ contract Fragment is return s[0].contains(addr); } + /// @notice Returns the current chain’s EIP-155 unique identifier function _getChainId() private view returns (uint256) { uint256 id; assembly { @@ -242,6 +265,7 @@ contract Fragment is return id; } + /// @notice Allows the Owner of this Contract to add an Authorizer (i.e Fragnova's Blockchain's Off-Chain Validator) function addAuth(address addr) external onlyOwner { EnumerableSet.AddressSet[1] storage auths; bytes32 slot = bytes32( @@ -253,6 +277,7 @@ contract Fragment is auths[0].add(addr); } + /// @notice Allows the Owner of this Contract to remove an Authorizer (i.e Fragnova's Blockchain's Off-Chain Validator) function delAuth(address addr) external onlyOwner { EnumerableSet.AddressSet[1] storage auths; bytes32 slot = bytes32( @@ -264,10 +289,17 @@ contract Fragment is auths[0].remove(addr); } + /// @notice Attaches a Proto-Fragment from Fragnova's Blockchain to this Smart Contract and assigns its ownership to `msg.sender` + /// @param fragmentHash - The Proto-Fragment ID to attach + /// @param signature - The signature provided by Fragnova's Blockahin's Off-Chain Validator to validate this attach request + /// @dev Verifies if the authorizer signed the message. + /// Then, it mints an ERC-721 Token (where the ID is the one plus the uint256 stored in `uint256(keccak256(abi.encodePacked(FRAGMENT_COUNTER)))`) + /// and gives its ownership to `msg.sender` function attach(bytes32 fragmentHash, bytes calldata signature) external { require(!_exists(uint256(fragmentHash)), "Fragment already attached"); uint64[1] storage nonce; + // ¿I wonder why we there is only one authorizer? EnumerableSet.AddressSet[1] storage auths; Counters.Counter[1] storage tokenIds; // read from unstructured storage @@ -280,6 +312,7 @@ contract Fragment is ) ); assembly { + // Make `nonce` array point to storage slot `slot` nonce.slot := slot } } @@ -288,6 +321,7 @@ contract Fragment is uint256(keccak256(abi.encodePacked(FRAGMENT_ATTACH_AUTHS))) ); assembly { + // Make `auths` array point to storage slot `slot` auths.slot := slot } } @@ -296,6 +330,7 @@ contract Fragment is uint256(keccak256(abi.encodePacked(FRAGMENT_COUNTER))) ); assembly { + // Make `tokenIds` array point to storage slot `slot` tokenIds.slot := slot } } @@ -305,6 +340,7 @@ contract Fragment is // Authenticate this operation { + // Returns a hash (of a hash) that will be later verified with `signature` bytes32 hash = ECDSA.toEthSignedMessageHash( keccak256( abi.encodePacked( @@ -316,6 +352,7 @@ contract Fragment is ) ); + // Verify whether the hash was signed by `signature` and return public address of signer address auth = ECDSA.recover(hash, signature); require(auths[0].contains(auth), "Invalid signature"); @@ -324,7 +361,7 @@ contract Fragment is tokenIds[0].increment(); uint256 tokenId = tokenIds[0].current(); - // Store to ID 2 Hash table + // Store to Token ID 2 Fragment Hash table { bytes32[1] storage fragmentHashStorage; bytes32 slot = bytes32( @@ -337,10 +374,11 @@ contract Fragment is assembly { fragmentHashStorage.slot := slot } + /// @dev Stores fragmentHash in `abi.encodePacked(FRAGMENT_FRAGMENTS_ID2HASH, tokenId)` fragmentHashStorage[0] = fragmentHash; } - // Store to Hash 2 ID table + // Store to Fragment Hash 2 Token ID table { uint256[1] storage fragmentIDStorage; bytes32 slot = bytes32( @@ -357,12 +395,21 @@ contract Fragment is fragmentIDStorage.slot := slot } require(fragmentIDStorage[0] == 0, "Fragment already attached"); + /// @dev Stores tokenId in `abi.encodePacked(FRAGMENT_FRAGMENTS_HASH2ID, fragmentHash)` fragmentIDStorage[0] = tokenId; } + // Assign Ownernship on `tokenId` to `msg.sender` _mint(msg.sender, tokenId); } + /// @notice Creates a Fragment/Entity using a Proto-Fragment with Token ID `fragmentID`. Note: Only the owner of the Proto-Fragment can call this function + /// @param fragmentId - The Token ID of the Proto-Fragment + /// @param tokenName - The name to give the Fragment/Entity + /// @param tokenSymbol - The symbol to give the Fragment/Entity + /// @param unique - ¿ + /// @param updateable - ¿ + /// @param maxSupply - ¿ function spawn( uint256 fragmentId, string memory tokenName, @@ -381,10 +428,12 @@ contract Fragment is assembly { mstore(add(ptr, 0x20), fragmentHash) } + // Create and Deploy an Entity Smart Contract entity = ClonesWithCallData.cloneWithCallDataProvision( getAddress(SLOT_entityLogic), ptr ); + // Create and Deploy a Vault Smart Contract vault = ClonesWithCallData.cloneWithCallDataProvision( getAddress(SLOT_vaultLogic), ptr @@ -393,6 +442,7 @@ contract Fragment is // entity { + // Create a Struct that represents the Fragment/Entity FragmentInitData memory params = FragmentInitData( fragmentId, maxSupply, @@ -402,6 +452,7 @@ contract Fragment is updateable ); + // Call the de-facto constructor of the Entity Smart Contract and pass in the information about how we want the Fragment/Entity to be IEntity(entity).bootstrap(tokenName, tokenSymbol, params); // keep track of this new contract @@ -412,13 +463,17 @@ contract Fragment is ) ); assembly { + // Make `s` array point to storage slot `slot` s.slot := slot } + // Add `entity` to the AddressSet in `s[0]` s[0].add(entity); } // vault { + // Call the de-facto constructor of the Vault Smart Contract and pass in the information + // about the address of the Fragment/Entity Contract and the address of this contract (i.e the address of the Proto-Fragment Contract) IVault(payable(vault)).bootstrap(entity, address(this)); } @@ -426,6 +481,8 @@ contract Fragment is emit Spawn(fragmentHash, entity, vault); } + /// @notice Transfer Ownership of this contract (i.e of the `Fragment` contract) to `newOwner` + /// Furthermore, the royalty recipient of this contract (i.e of the `Fragment` contract) is also `newOwner` function transferOwnership(address newOwner) public override onlyOwner { setupRoyalties(payable(newOwner), FRAGMENT_ROYALTIES_BPS); super.transferOwnership(newOwner); @@ -455,6 +512,8 @@ contract Fragment is _burn(fragmentId); } + /// @notice Transfer ERC-20 Token with token contract address `tokenAddress` and amount `tokenAmount` to `owner()`. + /// NOTE: ONLY THE CONTRACT OWNER CAN THIS CALL THIS FUNCTION function recoverERC20(address tokenAddress, uint256 tokenAmount) external onlyOwner @@ -462,6 +521,8 @@ contract Fragment is IERC20(tokenAddress).safeTransfer(owner(), tokenAmount); } + /// @notice Transfer Ethers with amount `amount` to `owner()` + /// NOTE: ONLY THE CONTRACT OWNER CAN THIS CALL THIS FUNCTION function recoverETH(uint256 amount) external onlyOwner { payable(owner()).transfer(amount); } diff --git a/contracts/PreERC721.sol b/contracts/PreERC721.sol index fbef33b..9eeedf3 100644 --- a/contracts/PreERC721.sol +++ b/contracts/PreERC721.sol @@ -60,6 +60,8 @@ contract PreERC721 is ERC721, Initializable, Ownable, RoyaltiesReceiver { /// Overrides /// + /// @notice Returns the name of this ERC-721 Contract + /// @dev ¿I am not sure what _getImmutableVariablesOffset does? function name() public pure override returns (string memory) { uint256 offset = _getImmutableVariablesOffset(); bytes32 nameBytes; @@ -69,6 +71,10 @@ contract PreERC721 is ERC721, Initializable, Ownable, RoyaltiesReceiver { return string(abi.encodePacked(nameBytes)); } + /// Overrides + /// + /// @notice Returns the symbol of this ERC-721 Contract + /// @dev ¿I am not sure what _getImmutableVariablesOffset does? function symbol() public pure override returns (string memory) { uint256 offset = _getImmutableVariablesOffset(); bytes32 symbolBytes; @@ -113,6 +119,7 @@ contract PreERC721 is ERC721, Initializable, Ownable, RoyaltiesReceiver { else return bytes1(uint8(b) + 0x57); } + /// @notice Returns a JSON that represents the storefront-level metadata (for example a Collection in OpenSea) for your contract function contractURI() public pure returns (string memory) { bytes memory url = abi.encodePacked( GATEWAY_URL, @@ -141,6 +148,8 @@ contract PreERC721 is ERC721, Initializable, Ownable, RoyaltiesReceiver { return string(data); } + /// @notice Returns the tokenURI of the ERC-721 Token with ID `tokenId`. (Note: Every ERC-721 Contract must have this function) + /// The tokenURI on an NFT is a unique identifier of what the token "looks" like. A URI could be an API call over HTTPS, an IPFS hash, or anything else unique. (https://www.freecodecamp.org/news/how-to-make-an-nft-and-render-on-opensea-marketplace/#:~:text=come%20into%20play.-,TokenURI,hash%2C%20or%20anything%20else%20unique.) function tokenURI(uint256 tokenId) public pure @@ -156,6 +165,12 @@ contract PreERC721 is ERC721, Initializable, Ownable, RoyaltiesReceiver { return string(url); } + /// @notice The de-facto Constructor of the PreER721 Smart Contract. + /// 1. Stores the address of the Proxy of the `PreERC721Genesis` Contract + /// 2. Calls the `genesisEvents()` function so that OpenSea and others will track/find our NFT contract + /// 3. Sets the Royalties recipient of this contract (i.e of the `Fragment` contract) to `owner()` + /// @param genesisState The address of the Proxy of the `PreERC721Genesis` Contract + /// @dev The `initializer` modifier ensures this function is only called once function genesis(address genesisState) external onlyOwner initializer { _genesisState = IGenesisState(genesisState); _genesisState.generateEvents(); @@ -179,6 +194,8 @@ contract PreERC721 is ERC721, Initializable, Ownable, RoyaltiesReceiver { super._beforeTokenTransfer(from, to, tokenId); } + /// @notice Get the number of ERC-721 Tokens (of this contract) and the + /// Tokens of the `PreERC721Genesis` Contract that are held by the address `tokenOwner` function balanceOf(address tokenOwner) public view @@ -195,6 +212,8 @@ contract PreERC721 is ERC721, Initializable, Ownable, RoyaltiesReceiver { return balance; } + /// @notice Returns the address of the owner of ERC-721 Token with ID `tokenID` in this Contract (i.e in PreERC721) + /// If an owner doesn't exist in this contract, it checks if it exists in the `PreERC721Genesis` contract (whose address is interface is `_genesisState`) function ownerOf(uint256 tokenId) public view diff --git a/contracts/PreERC721Factory.sol b/contracts/PreERC721Factory.sol index ca4460f..008b4d9 100644 --- a/contracts/PreERC721Factory.sol +++ b/contracts/PreERC721Factory.sol @@ -5,6 +5,13 @@ import "./ClonesWithCalldata.sol"; contract PreERC721Factory { event Created(address indexed newContract); + /// @notice Create a New `PreERC721` Contract. The New Contract is a Clone of an existing `PreERC721` Contract (whose address is `implementation`) + /// @param name - Name of the new contract + /// @param symbol - Symbol of the new contract + /// @param fragmentHash - The hash of a Proto-Fragment + /// @param owner - The address of the owner of the new contract + /// @param implementation - The Address of an existing `PreERC721` Contract to Clone + /// @param ¿How does the `fragmentHash` pass to the new `PreERC721` Contract function create( bytes32 name, bytes32 symbol, @@ -15,6 +22,7 @@ contract PreERC721Factory { bytes32 owner32 = bytes32(bytes20(owner)); bytes memory ptr = new bytes(128); assembly { + // Store `name` is Memory Slot `ptr + 24` mstore(add(ptr, 0x20), name) mstore(add(ptr, 0x40), symbol) mstore(add(ptr, 0x60), fragmentHash) diff --git a/contracts/PreERC721Genesis.sol b/contracts/PreERC721Genesis.sol index 2eb5879..13637bf 100644 --- a/contracts/PreERC721Genesis.sol +++ b/contracts/PreERC721Genesis.sol @@ -432,6 +432,8 @@ contract PreERC721Genesis is Ownable, Initializable { return cids; } + /// @notice Returns the address of `tokenID`th element in the array `getOwners()` + /// @dev If the `tokenId` is contained the array state variable `_excludedTokens` or is greater than `getOwners()`, it returns `address(0x0)` function getGenesisOwner(uint256 tokenId) public view returns (address) { if (_excludedTokens.contains(tokenId)) { return address(0x0); @@ -493,6 +495,8 @@ contract PreERC721Genesis is Ownable, Initializable { uint256 indexed tokenId ); + + /// @notice This function is called just so that we emit 100 `Transfer` events on the Blockchain - as this will allow OpenSea and others to track/find our NFT contract function generateEvents() external onlyController { address[TOTAL_TOKENS] memory owners = getOwners(); for (uint256 i = 0; i < TOTAL_TOKENS; i++) { @@ -506,6 +510,9 @@ contract PreERC721Genesis is Ownable, Initializable { return cids[tokenId - 1]; } + /// @notice The de-facto Constructor of the PreER721 Smart Contract. Stores the address of Proxy of the PreERC721 Contract + /// @param controller - The address of the Proxy of the PreERC721 Contract + /// @dev The `initializer` modifier ensures this function is only called once function init(address controller) external onlyOwner initializer { _controller = controller; } diff --git a/contracts/RezProxy.sol b/contracts/RezProxy.sol index 185d098..c958b47 100644 --- a/contracts/RezProxy.sol +++ b/contracts/RezProxy.sol @@ -6,13 +6,18 @@ pragma solidity ^0.8.7; import "openzeppelin-solidity/contracts/proxy/Proxy.sol"; import "openzeppelin-solidity/contracts/proxy/utils/Initializable.sol"; +/// @title A Proxy Contract named `RezProxy` contract RezProxy is Proxy { bytes32 private constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + /// Storage Slot that stores a Boolean which indicates whether the RezProxy Contract has been "initialized" bytes32 private constant _INIT_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + /// @dev Sets the boolean value in storage slot `_INIT_SLOT` to True + /// Sets the address in storage slot `_IMPLEMENTATION_SLOT` to `newImplementation` + /// @param newImplementation The Implementation Contract Address function bootstrapProxy(address newImplementation) public { bytes32 slot = _INIT_SLOT; bool initialized = true; @@ -42,6 +47,8 @@ contract RezProxy is Proxy { } } + /// @notice Returns the address of the actual Implementation Contract + /// @dev The fallback function delegates to the address that is returned (https://docs.openzeppelin.com/contracts/4.x/api/proxy#Proxy-_implementation--) function _implementation() internal view override returns (address impl) { bytes32 slot = _IMPLEMENTATION_SLOT; // solhint-disable-next-line no-inline-assembly diff --git a/contracts/RoyaltiesReceiver.sol b/contracts/RoyaltiesReceiver.sol index 932921b..bcc90f0 100644 --- a/contracts/RoyaltiesReceiver.sol +++ b/contracts/RoyaltiesReceiver.sol @@ -5,13 +5,23 @@ pragma solidity ^0.8.7; import "openzeppelin-solidity/contracts/token/ERC20/utils/SafeERC20.sol"; +/// @title This contract essentially is an ERC-2981 Contract (https://www.youtube.com/watch?v=h6Fb_dPZCd0) contract RoyaltiesReceiver { using SafeERC20 for IERC20; + // The Proto-Fragment owner will get 7% royalty of the sales price, for any and all sales uint256 public constant FRAGMENT_ROYALTIES_BPS = 700; bytes4 private constant _INTERFACE_ID_ROYALTIES_CREATORCORE = 0xbb3bafd6; + // Interface ID of the function `royaltyInfo` (https://eips.ethereum.org/EIPS/eip-2981) + // bytes4(keccak256("royaltyInfo(uint256,uint256)")) == 0x2a55205a bytes4 private constant _INTERFACE_ID_ROYALTIES_EIP2981 = 0x2a55205a; + /* Inteface ID of `getFeeBps` and `getFeeRecipients` (https://docs.rarible.org/ethereum/smart-contracts/royalties/) + * bytes4(keccak256('getFeeBps(uint256)')) == 0x0ebd4c7f + * bytes4(keccak256('getFeeRecipients(uint256)')) == 0xb9c4d9fb + * + * => 0x0ebd4c7f ^ 0xb9c4d9fb == 0xb7799584 + */ bytes4 private constant _INTERFACE_ID_ROYALTIES_RARIBLE = 0xb7799584; bytes32 private constant SLOT_royaltiesRecipient = @@ -25,6 +35,7 @@ contract RoyaltiesReceiver { uint256(keccak256("fragments.RoyaltiesReceiver.royaltiesBps")) - 1 ); + /// @notice Returns the address of the Royalties Recipient function getRoyaltiesRecipient() private view @@ -36,6 +47,7 @@ contract RoyaltiesReceiver { } } + /// @notice Returns the basis points of the Sales Price that will go to the Royalties Recipient function getRoyaltiesBps() private view returns (uint256 bps) { bytes32 slot = SLOT_royaltiesBps; assembly { @@ -43,6 +55,10 @@ contract RoyaltiesReceiver { } } + + /// @notice Sets up the Royalties Recipient (`royaltiesRecipient`) and the basis points of the Sales Price that will go to the Royalties Recipient (`royaltiesBps`) + /// @param royaltiesRecipient - The Address of the royalties Recipient + /// @param royaltiesBps - The basis points of the Sales Price that will go to the Royalties Recipient function setupRoyalties( address payable royaltiesRecipient, uint256 royaltiesBps @@ -58,6 +74,7 @@ contract RoyaltiesReceiver { } } + /// @dev This is a mandatory function that must be implemented by an ERC-2981 Contract (https://www.youtube.com/watch?v=h6Fb_dPZCd0) function _supportsInterface(bytes4 interfaceId) internal pure @@ -69,6 +86,8 @@ contract RoyaltiesReceiver { interfaceId == _INTERFACE_ID_ROYALTIES_RARIBLE; } + /// @notice Returns the Royalties Recipients and their respective Royalties BPS of this Smart Contract + /// @param uint256 - The first parameter is not used function getRoyalties(uint256) external view @@ -81,6 +100,7 @@ contract RoyaltiesReceiver { return (recipients, bps); } + /// @notice Returns a list of addresses of the Royalties Recipients (the length of the list is always one for this Smart Contract) function getFeeRecipients(uint256) external view @@ -91,12 +111,22 @@ contract RoyaltiesReceiver { return recipients; } + /// @notice Returns a list of BPS values (that represent the Royalty Rate) of the Royalties Recipients (the length of the list is always one for this Smart Contract) function getFeeBps(uint256) external view returns (uint256[] memory bps) { bps = new uint256[](1); bps[0] = getRoyaltiesBps(); return bps; } + + /// @notice Called with the sale price to determine how much royalty + // is owed and to whom. + /// @param uint256 - The first parameter is not used, that's why it's unnamed. (However, usually the first parameter is the token ID of the NFT asset queried for royalty information) + /// @param value - the sale price of the NFT asset specified by _tokenId + /// @return receiver - address of who should be sent the royalty payment + /// @return royaltyAmount - the royalty payment amount for _salePrice + /// @dev Currently, the `royaltyAmount` is `getRoyaltiesBps()` multiplied by `value` + /// @dev This is a mandatory function that must be implemented by an ERC-2981 Contract (https://www.youtube.com/watch?v=h6Fb_dPZCd0) function royaltyInfo(uint256, uint256 value) external view diff --git a/contracts/UnstructuredStorage.sol b/contracts/UnstructuredStorage.sol index 2580517..806a491 100644 --- a/contracts/UnstructuredStorage.sol +++ b/contracts/UnstructuredStorage.sol @@ -20,6 +20,7 @@ contract UnstructuredStorage is Ownable { _setUint(slot, value); } + /// @dev Gets the address stored in storage slot `slot` function getAddress(bytes32 slot) public view returns (address value) { assembly { value := sload(slot) diff --git a/contracts/Utility.sol b/contracts/Utility.sol index cec1b3c..196fb82 100644 --- a/contracts/Utility.sol +++ b/contracts/Utility.sol @@ -46,6 +46,7 @@ contract Utility { return id; } + /// @notice Build and return the metadata URI for Proto-Fragment whose hash is `fragmentHash` function buildFragmentMetadata(bytes32 fragmentHash) public pure @@ -82,6 +83,13 @@ contract Utility { return string(data); } + /// @notice Build and return the metadata URI for the Entity/Fragment whose Token ID is `id` + /// @param id - The Token ID of the Entity/Fragment + /// @param mutableHash - ¿ + /// @param entityID - The Entity Contract Address + /// @param dataBlock - The block number where the Entity was last modified + /// @dev Returns a string in the following format "{METADATA_URI}?ch={_getChainId().toHexString()}&id={id.toHexString()} + /// &e=0x{toAsciiString(entityId)}&m={uint256(mutableHash).toHexString()}&d={dataBlock.toHexString()}" function buildEntityMetadata( uint256 id, bytes32 mutableHash, @@ -105,6 +113,12 @@ contract Utility { return string(query); } + /// @notice Returns a JSON containing the paramater names and values + /// @param name Entity/Fragment Contract's Name + /// @param desc Entity/Fragment Contract's Description + /// @param url Entity/Fragment Contract's URL + /// @param vaultAddress The Address of the Vault Contract that corresponds to the Entity/Fragment Contract + /// @param feeBasisPoints The Royalty Rate (in basis points) function buildEntityRootMetadata( string memory name, string memory desc, diff --git a/contracts/Vault.sol b/contracts/Vault.sol index 2b6bb09..63e1518 100644 --- a/contracts/Vault.sol +++ b/contracts/Vault.sol @@ -8,6 +8,7 @@ import "openzeppelin-solidity/contracts/token/ERC20/utils/SafeERC20.sol"; import "./IFragment.sol"; import "./IEntity.sol"; +/// @title Vault is an Initializable Contract contract Vault is Initializable { using SafeERC20 for IERC20; @@ -16,6 +17,8 @@ contract Vault is Initializable { bytes32 private constant SLOT_entityContract = bytes32(uint256(keccak256("fragments.vault.entityContract")) - 1); + + /// @notice Returns the address of the owner of the Proto-Fragment function fragmentOwner() public view virtual returns (address) { address entityContract; bytes32 slot = SLOT_entityContract; @@ -26,6 +29,7 @@ contract Vault is Initializable { return e.fragmentOwner(); } + /// @notice Returns the address of the owner of the `Fragment` smart contract function foundation() public view virtual returns (address) { address fragmentsLibrary; bytes32 slot = SLOT_fragmentsLibrary; @@ -38,8 +42,14 @@ contract Vault is Initializable { constructor() {} + /// @notice Deposits all payments made in linked `Entity` Smart Contract (the address of which can be found in `SLOT_entityContract`) to this associated `Vault` Smart Contract function deposit() public payable {} + /// @notice The de-facto Constructor of the Vault Smart Contract. Stores the address of the Proxy of the Entity Contract and the address of the Fragments Library (`Fragments.sol`) in Storage Slots + /// @param entityContract - The address of the RezProxy Contract that delegates all its calls to an Entity Contract + /// @param fragmentsLibrary - The address of the `Fragment` Contract + /// @dev The `initializer` modifier ensures this function is only called once + // The de-facto constructor stores `entityContract` in storage slot `SLOT_entityContract` and the `fragmentsLibrary` in storage slot `SLOT_fragmentsLibrary` function bootstrap(address entityContract, address fragmentsLibrary) public initializer @@ -55,6 +65,9 @@ contract Vault is Initializable { } } + /// @notice Claim ERC-20 Tokens (whose token contract address is `tokenAddress`) that are held by this contract (i.e by this Vault Contract) + /// 80% of ERC-20 Tokens held goes to the Proto-Fragment Owner (`fragmentOwner()`) + /// and 20% goes to the Fragments Foundation function claimERC20(address tokenAddress) public { IERC20 erc20 = IERC20(tokenAddress); uint256 balance = erc20.balanceOf(address(this)); @@ -67,6 +80,9 @@ contract Vault is Initializable { erc20.safeTransfer(foundation(), balance); } + /// @notice Claim Ether that is held by this contract (i.e by this Vault Contract) + /// 80% of the Ethers held goes to the Proto-Fragment Owner (`fragmentOwner()`) + /// and 20% goes to the Fragments Foundation function claimETH() public { uint256 balance = address(this).balance; assert(balance > 0);