ShopReg.sol
1 // SPDX-FileCopyrightText: 2024 Mass Labs 2 // 3 // SPDX-License-Identifier: GPL-3.0-or-later 4 5 pragma solidity ^0.8.19; 6 7 import {ERC721} from "openzeppelin/contracts/token/ERC721/ERC721.sol"; 8 import {ERC721Enumerable} from "openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; 9 import {ERC721URIStorage} from "openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; 10 11 /// used as salt for creating an OrderPayment Contract 12 contract ShopReg is ERC721Enumerable, ERC721URIStorage { 13 error InvalidNonce(uint64 cur, uint64 _nonce); 14 15 /// @notice rootHashes is a mapping of shops to their state root hash 16 mapping(uint256 shopid => bytes32) public rootHashes; 17 /// @notice sequenceNonce is a mapping of shops to the nonce of the last event used in the root hash 18 mapping(uint256 shopid => uint64) public nonce; 19 /// @notice relays is a mapping of shop nfts to their relays 20 mapping(uint256 shopid => uint256[]) public relays; 21 mapping(uint256 shopid => bytes32) public schema; 22 /// @notice endPoints are for relays to store their endpoints as URLs 23 /// any shop can also run their own relay(s) and this endPoints field to br 24 mapping(uint256 shopid => string[]) public endPoints; 25 26 constructor() ERC721("ShopRegistry", "SR") { 27 } 28 29 // The following functions are overrides required by Solidity. 30 function _update( 31 address to, 32 uint256 tokenId, 33 address auth 34 ) internal override(ERC721, ERC721Enumerable) returns (address) { 35 return super._update(to, tokenId, auth); 36 } 37 38 function _increaseBalance( 39 address account, 40 uint128 value 41 ) internal override(ERC721, ERC721Enumerable) { 42 super._increaseBalance(account, value); 43 } 44 45 /// forge-lint: disable-next-line(mixed-case-function) 46 function tokenURI( 47 uint256 tokenId 48 ) public view override(ERC721, ERC721URIStorage) returns (string memory) { 49 return super.tokenURI(tokenId); 50 } 51 52 function supportsInterface( 53 bytes4 interfaceId 54 ) 55 public 56 view 57 override(ERC721Enumerable, ERC721URIStorage) 58 returns (bool) 59 { 60 return super.supportsInterface(interfaceId); 61 } 62 63 /// @notice Sets the metadata URI for a given shop with the provided URI 64 /// @param shopId shop token id, newTokenURI uri to metadata 65 /// forge-lint: disable-next-line(mixed-case-function) 66 function setTokenURI(uint256 shopId, string calldata newTokenUri) public { 67 require(ownerOf(shopId) == msg.sender, "NOT_AUTHORIZED"); 68 _setTokenURI(shopId, newTokenUri); 69 } 70 71 /// @notice mint registers a new shop and creates a NFT for it 72 /// @param shopId The shop nft. Needs to be unique or it will revert 73 /// @param _schema The schema of the shop 74 /// @param owner The owner of the shop 75 function mint(uint256 shopId, bytes32 _schema, address owner) public { 76 // safe mint checks if id is taken 77 _safeMint(owner, shopId); 78 schema[shopId] = _schema; 79 } 80 81 /// @notice updateRootHash updates the state root of the shop 82 /// @param shopId The shop nft 83 /// @param hash The new state root hash 84 function updateRootHash( 85 uint256 shopId, 86 bytes32 hash, 87 uint64 _nonce 88 ) public { 89 require(ownerOf(shopId) == msg.sender || _checkIsConfiguredRelay(shopId), "NOT_AUTHORIZED"); 90 rootHashes[shopId] = hash; 91 uint64 curNonce = nonce[shopId]; 92 if (curNonce >= _nonce) { 93 revert InvalidNonce(curNonce, _nonce); 94 } 95 nonce[shopId] = _nonce; 96 } 97 98 /** 99 * RELAY CONFIGURATION 100 */ 101 102 /// @notice getRelayCount returns the number of relays for a shop 103 /// @param shopId The shop nft 104 /// @return The number of relays 105 function getRelayCount(uint256 shopId) public view returns (uint256) { 106 return relays[shopId].length; 107 } 108 109 /// @notice getAllRelays returns all relays for a shop 110 /// @param shopId The shop nft 111 /// @return An array of relay nfts 112 function getAllRelays( 113 uint256 shopId 114 ) public view returns (uint256[] memory) { 115 return relays[shopId]; 116 } 117 118 /// @notice addRelay adds a relay to the shop 119 /// @param shopId The shop nft 120 /// @param relayId The relay nft 121 function addRelay(uint256 shopId, uint256 relayId) public { 122 require(ownerOf(shopId) == msg.sender, "NOT_AUTHORIZED"); 123 relays[shopId].push(relayId); 124 } 125 126 /// @notice replaceRelay replaces a relay in the shop 127 /// @param shopId The shop nft 128 /// @param idx The index of the relay to replace 129 /// @param relayId The new relay nft 130 function replaceRelay(uint256 shopId, uint8 idx, uint256 relayId) public { 131 require(ownerOf(shopId) == msg.sender, "NOT_AUTHORIZED"); 132 relays[shopId][idx] = relayId; 133 } 134 135 /// @notice removeRelay removes a relay from the shop 136 /// @param shopId The shop nft 137 /// @param idx The index of the relay to remove 138 function removeRelay(uint256 shopId, uint8 idx) public { 139 require(ownerOf(shopId) == msg.sender, "NOT_AUTHORIZED"); 140 uint256 last = relays[shopId].length - 1; 141 if (last != idx) { 142 relays[shopId][idx] = relays[shopId][last]; 143 } 144 relays[shopId].pop(); 145 } 146 147 function setRelayEndpoints(uint256 shopId, string[] calldata endpoints) public { 148 require(ownerOf(shopId) == msg.sender, "NOT_AUTHORIZED"); 149 endPoints[shopId] = endpoints; 150 } 151 152 /// @notice getRelayEndPoints returns the endpoints of all relays in the shop 153 /// @param shopId The shop nft 154 function getRelayEndPoints(uint256 shopId) public view returns (string[] memory) { 155 uint256[] storage allRelays = relays[shopId]; 156 uint256 totalEndpoints = endPoints[shopId].length; 157 for (uint256 i = 0; i < allRelays.length; i++) { 158 totalEndpoints += endPoints[allRelays[i]].length; 159 } 160 string[] memory relayEndPoints = new string[](totalEndpoints); 161 string[] storage currentEndpoints = endPoints[shopId]; 162 uint256 index = 0; 163 164 for (uint256 j = 0; j < currentEndpoints.length; j++) { 165 relayEndPoints[index] = currentEndpoints[j]; 166 index++; 167 } 168 for (uint256 i = 0; i < allRelays.length; i++) { 169 currentEndpoints = endPoints[allRelays[i]]; 170 for (uint256 j = 0; j < currentEndpoints.length; j++) { 171 relayEndPoints[index] = currentEndpoints[j]; 172 index++; 173 } 174 } 175 return relayEndPoints; 176 } 177 178 /// @dev checks if the sender is part of the configured relays 179 /// @param shopId The shop nft 180 function _checkIsConfiguredRelay( 181 uint256 shopId 182 ) internal view returns (bool) { 183 uint256[] storage allRelays = relays[shopId]; 184 for (uint256 index = 0; index < allRelays.length; index++) { 185 uint256 relayId = allRelays[index]; 186 address relayAddr = this.ownerOf(relayId); 187 if (relayAddr == msg.sender) { 188 return true; 189 } 190 } 191 return false; 192 } 193 }