/ src / ShopReg.sol
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  }