diff --git a/CHANGELOG.md b/CHANGELOG.md index 0617669..199d877 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ Please follow https://changelog.md/ conventions. +## v0.3.0 + +- Add ERC-2771 support + ## v0.2.0 - Add the constant VERSION diff --git a/README.md b/README.md index 488193f..380053e 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,18 @@ function setDocument(address smartContract,bytes32 name_,string memory uri_, byt | 🛑 | Function can modify state | | 💵 | Function is payable | + + +## Gasless support (ERC-2771) + +The DocumentEngine supports client-side gasless transactions using the [Gas Station Network](https://docs.opengsn.org/#the-problem) (GSN) pattern, the main open standard for transfering fee payment to another account than that of the transaction issuer. The contract uses the OpenZeppelin contract `ERC2771ContextUpgradeable`, which allows a contract to get the original client with `_msgSender()` instead of the fee payer given by `msg.sender` while allowing upgrades on the main contract (see *Deployment via a proxy* above). + +At deployment, the parameter `forwarder` inside the constructor has to be set with the defined address of the forwarder. Please note that the forwarder can not be changed after deployment. + +Please see the OpenGSN [documentation](https://docs.opengsn.org/contracts/#receiving-a-relayed-call) for more details on what is done to support GSN in the contract. + + + ## Dependencies The toolchain includes the following components, where the versions are the latest ones that we tested: diff --git a/doc/coverage/coverage/index-sort-b.html b/doc/coverage/coverage/index-sort-b.html index b03de3c..3fe0eaf 100644 --- a/doc/coverage/coverage/index-sort-b.html +++ b/doc/coverage/coverage/index-sort-b.html @@ -31,18 +31,18 @@ lcov.info Lines: - 48 - 48 - 100.0 % + 51 + 52 + 98.1 % Date: - 2024-09-03 09:18:28 + 2024-09-09 15:02:57 Functions: - 10 - 11 - 90.9 % + 12 + 14 + 85.7 % @@ -84,12 +84,12 @@ src -
100.0%
+
98.1%98.1%
- 100.0 % - 48 / 48 - 90.9 % - 10 / 11 + 98.1 % + 51 / 52 + 85.7 % + 12 / 14 92.9 % 13 / 14 diff --git a/doc/coverage/coverage/index-sort-f.html b/doc/coverage/coverage/index-sort-f.html index 577f764..7e88c84 100644 --- a/doc/coverage/coverage/index-sort-f.html +++ b/doc/coverage/coverage/index-sort-f.html @@ -31,18 +31,18 @@ lcov.info Lines: - 48 - 48 - 100.0 % + 51 + 52 + 98.1 % Date: - 2024-09-03 09:18:28 + 2024-09-09 15:02:57 Functions: - 10 - 11 - 90.9 % + 12 + 14 + 85.7 % @@ -84,12 +84,12 @@ src -
100.0%
+
98.1%98.1%
- 100.0 % - 48 / 48 - 90.9 % - 10 / 11 + 98.1 % + 51 / 52 + 85.7 % + 12 / 14 92.9 % 13 / 14 diff --git a/doc/coverage/coverage/index-sort-l.html b/doc/coverage/coverage/index-sort-l.html index 7c65a94..00b9318 100644 --- a/doc/coverage/coverage/index-sort-l.html +++ b/doc/coverage/coverage/index-sort-l.html @@ -31,18 +31,18 @@ lcov.info Lines: - 48 - 48 - 100.0 % + 51 + 52 + 98.1 % Date: - 2024-09-03 09:18:28 + 2024-09-09 15:02:57 Functions: - 10 - 11 - 90.9 % + 12 + 14 + 85.7 % @@ -84,12 +84,12 @@ src -
100.0%
+
98.1%98.1%
- 100.0 % - 48 / 48 - 90.9 % - 10 / 11 + 98.1 % + 51 / 52 + 85.7 % + 12 / 14 92.9 % 13 / 14 diff --git a/doc/coverage/coverage/index.html b/doc/coverage/coverage/index.html index a6dc604..138d820 100644 --- a/doc/coverage/coverage/index.html +++ b/doc/coverage/coverage/index.html @@ -31,18 +31,18 @@ lcov.info Lines: - 48 - 48 - 100.0 % + 51 + 52 + 98.1 % Date: - 2024-09-03 09:18:28 + 2024-09-09 15:02:57 Functions: - 10 - 11 - 90.9 % + 12 + 14 + 85.7 % @@ -84,12 +84,12 @@ src -
100.0%
+
98.1%98.1%
- 100.0 % - 48 / 48 - 90.9 % - 10 / 11 + 98.1 % + 51 / 52 + 85.7 % + 12 / 14 92.9 % 13 / 14 diff --git a/doc/coverage/coverage/src/DocumentEngine.sol.func-sort-c.html b/doc/coverage/coverage/src/DocumentEngine.sol.func-sort-c.html index 5f749f9..935fc26 100644 --- a/doc/coverage/coverage/src/DocumentEngine.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/DocumentEngine.sol.func-sort-c.html @@ -31,18 +31,18 @@ lcov.info Lines: - 48 - 48 - 100.0 % + 51 + 52 + 98.1 % Date: - 2024-09-03 09:18:28 + 2024-09-09 15:02:57 Functions: - 10 - 11 - 90.9 % + 12 + 14 + 85.7 % @@ -69,48 +69,60 @@ Hit count Sort by hit count - DocumentEngine.hasRole + DocumentEngine._msgData + 0 + + + DocumentEngine.hasRole 0 - DocumentEngine.removeDocument + DocumentEngine.removeDocument 2 - DocumentEngine._removeDocument + DocumentEngine._removeDocument 5 - DocumentEngine._removeDocumentName + DocumentEngine._removeDocumentName 5 - DocumentEngine.batchRemoveDocuments + DocumentEngine.batchRemoveDocuments 7 - DocumentEngine.getAllDocuments + DocumentEngine.getAllDocuments 8 - DocumentEngine.batchSetDocuments + DocumentEngine.batchSetDocuments 13 - DocumentEngine._getDocument + DocumentEngine._getDocument 20 - DocumentEngine.getDocument + DocumentEngine.getDocument 20 - DocumentEngine.setDocument - 27 + DocumentEngine.setDocument + 28 + + + DocumentEngine._setDocument + 39 + + + DocumentEngine._contextSuffixLength + 50 - DocumentEngine._setDocument - 38 + DocumentEngine._msgSender + 50
diff --git a/doc/coverage/coverage/src/DocumentEngine.sol.func.html b/doc/coverage/coverage/src/DocumentEngine.sol.func.html index 27951b1..4139274 100644 --- a/doc/coverage/coverage/src/DocumentEngine.sol.func.html +++ b/doc/coverage/coverage/src/DocumentEngine.sol.func.html @@ -31,18 +31,18 @@ lcov.info Lines: - 48 - 48 - 100.0 % + 51 + 52 + 98.1 % Date: - 2024-09-03 09:18:28 + 2024-09-09 15:02:57 Functions: - 10 - 11 - 90.9 % + 12 + 14 + 85.7 % @@ -69,48 +69,60 @@ Hit count Sort by hit count - DocumentEngine._getDocument + DocumentEngine._contextSuffixLength + 50 + + + DocumentEngine._getDocument 20 - DocumentEngine._removeDocument + DocumentEngine._msgData + 0 + + + DocumentEngine._msgSender + 50 + + + DocumentEngine._removeDocument 5 - DocumentEngine._removeDocumentName + DocumentEngine._removeDocumentName 5 - DocumentEngine._setDocument - 38 + DocumentEngine._setDocument + 39 - DocumentEngine.batchRemoveDocuments + DocumentEngine.batchRemoveDocuments 7 - DocumentEngine.batchSetDocuments + DocumentEngine.batchSetDocuments 13 - DocumentEngine.getAllDocuments + DocumentEngine.getAllDocuments 8 - DocumentEngine.getDocument + DocumentEngine.getDocument 20 - DocumentEngine.hasRole + DocumentEngine.hasRole 0 - DocumentEngine.removeDocument + DocumentEngine.removeDocument 2 - DocumentEngine.setDocument - 27 + DocumentEngine.setDocument + 28
diff --git a/doc/coverage/coverage/src/DocumentEngine.sol.gcov.html b/doc/coverage/coverage/src/DocumentEngine.sol.gcov.html index 24db044..f77a5a3 100644 --- a/doc/coverage/coverage/src/DocumentEngine.sol.gcov.html +++ b/doc/coverage/coverage/src/DocumentEngine.sol.gcov.html @@ -31,18 +31,18 @@ lcov.info Lines: - 48 - 48 - 100.0 % + 51 + 52 + 98.1 % Date: - 2024-09-03 09:18:28 + 2024-09-09 15:02:57 Functions: - 10 - 11 - 90.9 % + 12 + 14 + 85.7 % @@ -73,240 +73,298 @@ 2 : : pragma solidity ^0.8.20; 3 : : 4 : : import "OZ/access/AccessControl.sol"; - 5 : : import "CMTAT/interfaces/engine/draft-IERC1643.sol"; - 6 : : import "./DocumentEngineInvariant.sol"; - 7 : : - 8 : : contract DocumentEngine is IERC1643, DocumentEngineInvariant, AccessControl { - 9 : : // Mapping from contract addresses to document names to their corresponding Document structs - 10 : : mapping(address => mapping(bytes32 => Document)) private _documents; - 11 : : mapping(address => bytes32[]) private _documentNames; - 12 : : - 13 : : // Constructor to initialize the admin role - 14 : : constructor(address admin) { - 15 : : _grantRole(DEFAULT_ADMIN_ROLE, admin); - 16 : : } - 17 : : - 18 : : /*////////////////////////////////////////////////////////////// - 19 : : PUBLIC/EXTERNAL FUNCTIONS - 20 : : //////////////////////////////////////////////////////////////*/ - 21 : : - 22 : : /** - 23 : : * @notice Restricted function to set or update a document - 24 : : */ - 25 : : function setDocument( - 26 : : address smartContract, - 27 : : bytes32 name_, - 28 : : string memory uri_, - 29 : : bytes32 documentHash_ - 30 : : ) public onlyRole(DOCUMENT_MANAGER_ROLE) { - 31 : 52 : _setDocument(smartContract, name_, uri_, documentHash_); - 32 : : } - 33 : : - 34 : : /** - 35 : : * @notice Restricted function to remove a document for a given smart contract and name - 36 : : */ - 37 : : function removeDocument( - 38 : : address smartContract, - 39 : : bytes32 name_ - 40 : : ) external onlyRole(DOCUMENT_MANAGER_ROLE) { - 41 : 2 : _removeDocument(smartContract, name_); - 42 : : } - 43 : : - 44 : : /** - 45 : : * @notice Batch version of setDocument to handle multiple documents at once - 46 : : */ - 47 : : function batchSetDocuments( - 48 : : address[] calldata smartContracts, - 49 : : bytes32[] calldata names, - 50 : : string[] calldata uris, - 51 : : bytes32[] calldata hashes - 52 : : ) external onlyRole(DOCUMENT_MANAGER_ROLE) { - 53 [ + + ]: : if ( - 54 : 35 : smartContracts.length == 0 || - 55 : 12 : smartContracts.length != names.length || - 56 : 10 : names.length != uris.length || - 57 : 8 : uris.length != hashes.length - 58 : : ) { - 59 : 6 : revert InvalidInputLength(); - 60 : : } - 61 : 28 : for (uint256 i = 0; i < smartContracts.length; i++) { - 62 : 16 : _setDocument(smartContracts[i], names[i], uris[i], hashes[i]); - 63 : : } - 64 : : } - 65 : : - 66 : : /** - 67 : : * @notice Batch version of setDocument to handle multiple documents at once - 68 : : */ - 69 : : function batchSetDocuments( - 70 : : address smartContract, - 71 : : bytes32[] calldata names, - 72 : : string[] calldata uris, - 73 : : bytes32[] calldata hashes - 74 : : ) external onlyRole(DOCUMENT_MANAGER_ROLE) { - 75 [ + + ]: : if ( - 76 : 19 : names.length == 0 || names.length != uris.length || - 77 : 4 : uris.length != hashes.length - 78 : : ) { - 79 : 4 : revert InvalidInputLength(); - 80 : : } - 81 : 14 : for (uint256 i = 0; i < names.length; ++i) { - 82 : 8 : _setDocument(smartContract, names[i], uris[i], hashes[i]); - 83 : : } - 84 : : } - 85 : : - 86 : : /** - 87 : : * @notice Batch version of removeDocument to handle multiple documents at once - 88 : : */ - 89 : : function batchRemoveDocuments( - 90 : : address[] calldata smartContracts, - 91 : : bytes32[] calldata names - 92 : : ) external onlyRole(DOCUMENT_MANAGER_ROLE) { - 93 [ + + ]: : if ( - 94 : 9 : smartContracts.length == 0 || - 95 : : (smartContracts.length != names.length) - 96 : : ) { - 97 : 4 : revert InvalidInputLength(); - 98 : : } - 99 : : - 100 : 7 : for (uint256 i = 0; i < smartContracts.length; ++i) { - 101 : 4 : _removeDocument(smartContracts[i], names[i]); + 5 : : import "OZ/metatx/ERC2771Context.sol"; + 6 : : import "CMTAT/interfaces/engine/draft-IERC1643.sol"; + 7 : : import "./DocumentEngineInvariant.sol"; + 8 : : + 9 : : /** + 10 : : * @title DocumentEngine + 11 : : * @notice contract to manage documents on-chain through ERC-1643 + 12 : : */ + 13 : : contract DocumentEngine is + 14 : : IERC1643, + 15 : : DocumentEngineInvariant, + 16 : : AccessControl, + 17 : : ERC2771Context + 18 : : { + 19 : : /** + 20 : : * @notice + 21 : : * Get the current version of the smart contract + 22 : : */ + 23 : : string public constant VERSION = "0.3.0"; + 24 : : // Mapping from contract addresses to document names to their corresponding Document structs + 25 : : mapping(address => mapping(bytes32 => Document)) private _documents; + 26 : : mapping(address => bytes32[]) private _documentNames; + 27 : : + 28 : : // Constructor to initialize the admin role + 29 : : constructor( + 30 : : address admin, + 31 : : address forwarderIrrevocable + 32 : : ) ERC2771Context(forwarderIrrevocable) { + 33 : : if (admin == address(0)) { + 34 : : revert AdminWithAddressZeroNotAllowed(); + 35 : : } + 36 : : _grantRole(DEFAULT_ADMIN_ROLE, admin); + 37 : : } + 38 : : + 39 : : /*////////////////////////////////////////////////////////////// + 40 : : PUBLIC/EXTERNAL FUNCTIONS + 41 : : //////////////////////////////////////////////////////////////*/ + 42 : : + 43 : : /** + 44 : : * @notice Restricted function to set or update a document + 45 : : */ + 46 : : function setDocument( + 47 : : address smartContract, + 48 : : bytes32 name_, + 49 : : string memory uri_, + 50 : : bytes32 documentHash_ + 51 : : ) public onlyRole(DOCUMENT_MANAGER_ROLE) { + 52 : 54 : _setDocument(smartContract, name_, uri_, documentHash_); + 53 : : } + 54 : : + 55 : : /** + 56 : : * @notice Restricted function to remove a document for a given smart contract and name + 57 : : */ + 58 : : function removeDocument( + 59 : : address smartContract, + 60 : : bytes32 name_ + 61 : : ) external onlyRole(DOCUMENT_MANAGER_ROLE) { + 62 : 2 : _removeDocument(smartContract, name_); + 63 : : } + 64 : : + 65 : : /** + 66 : : * @notice Batch version of setDocument to handle multiple documents at once + 67 : : */ + 68 : : function batchSetDocuments( + 69 : : address[] calldata smartContracts, + 70 : : bytes32[] calldata names, + 71 : : string[] calldata uris, + 72 : : bytes32[] calldata hashes + 73 : : ) external onlyRole(DOCUMENT_MANAGER_ROLE) { + 74 [ + + ]: : if ( + 75 : 35 : smartContracts.length == 0 || + 76 : 12 : smartContracts.length != names.length || + 77 : 10 : names.length != uris.length || + 78 : 8 : uris.length != hashes.length + 79 : : ) { + 80 : 6 : revert InvalidInputLength(); + 81 : : } + 82 : 28 : for (uint256 i = 0; i < smartContracts.length; i++) { + 83 : 16 : _setDocument(smartContracts[i], names[i], uris[i], hashes[i]); + 84 : : } + 85 : : } + 86 : : + 87 : : /** + 88 : : * @notice Batch version of setDocument to handle multiple documents at once + 89 : : */ + 90 : : function batchSetDocuments( + 91 : : address smartContract, + 92 : : bytes32[] calldata names, + 93 : : string[] calldata uris, + 94 : : bytes32[] calldata hashes + 95 : : ) external onlyRole(DOCUMENT_MANAGER_ROLE) { + 96 [ + + ]: : if ( + 97 : 16 : names.length == 0 || + 98 : 6 : names.length != uris.length || + 99 : 4 : uris.length != hashes.length + 100 : : ) { + 101 : 4 : revert InvalidInputLength(); 102 : : } - 103 : : } - 104 : : - 105 : : /** - 106 : : * @notice Batch version of removeDocument to handle multiple documents at once - 107 : : */ - 108 : : function batchRemoveDocuments( - 109 : : address smartContract, - 110 : : bytes32[] calldata names - 111 : : ) external onlyRole(DOCUMENT_MANAGER_ROLE) { - 112 [ + + ]: : if ( - 113 : 4 : names.length == 0 - 114 : : ) { - 115 : 2 : revert InvalidInputLength(); - 116 : : } - 117 : : - 118 : 7 : for (uint256 i = 0; i < names.length; ++i) { - 119 : 4 : _removeDocument(smartContract, names[i]); + 103 : 14 : for (uint256 i = 0; i < names.length; ++i) { + 104 : 8 : _setDocument(smartContract, names[i], uris[i], hashes[i]); + 105 : : } + 106 : : } + 107 : : + 108 : : /** + 109 : : * @notice Batch version of removeDocument to handle multiple documents at once + 110 : : */ + 111 : : function batchRemoveDocuments( + 112 : : address[] calldata smartContracts, + 113 : : bytes32[] calldata names + 114 : : ) external onlyRole(DOCUMENT_MANAGER_ROLE) { + 115 [ + + ]: : if ( + 116 : 9 : smartContracts.length == 0 || + 117 : : (smartContracts.length != names.length) + 118 : : ) { + 119 : 4 : revert InvalidInputLength(); 120 : : } - 121 : : } - 122 : : - 123 : : - 124 : : /** - 125 : : * @notice Public function to get a document from msg.sender - 126 : : */ - 127 : : function getDocument( - 128 : : bytes32 name_ - 129 : : ) external view override returns (string memory, bytes32, uint256) { - 130 : 3 : return _getDocument(msg.sender, name_); - 131 : : } - 132 : : - 133 : : /** - 134 : : * @notice Public function to get a document for a specific contract address - 135 : : */ - 136 : : function getDocument( - 137 : : address smartContract, - 138 : : bytes32 name_ - 139 : : ) external view returns (string memory, bytes32, uint256) { - 140 : 57 : return _getDocument(smartContract, name_); + 121 : : + 122 : 7 : for (uint256 i = 0; i < smartContracts.length; ++i) { + 123 : 4 : _removeDocument(smartContracts[i], names[i]); + 124 : : } + 125 : : } + 126 : : + 127 : : /** + 128 : : * @notice Batch version of removeDocument to handle multiple documents at once + 129 : : */ + 130 : : function batchRemoveDocuments( + 131 : : address smartContract, + 132 : : bytes32[] calldata names + 133 : : ) external onlyRole(DOCUMENT_MANAGER_ROLE) { + 134 [ + + ]: 4 : if (names.length == 0) { + 135 : 2 : revert InvalidInputLength(); + 136 : : } + 137 : : + 138 : 7 : for (uint256 i = 0; i < names.length; ++i) { + 139 : 4 : _removeDocument(smartContract, names[i]); + 140 : : } 141 : : } 142 : : 143 : : /** - 144 : : * @notice Get all document names for msg.sender + 144 : : * @notice Public function to get a document from msg.sender 145 : : */ - 146 : : function getAllDocuments() - 147 : : external - 148 : : view - 149 : : override - 150 : : returns (bytes32[] memory) - 151 : : { - 152 : 2 : return _documentNames[msg.sender]; - 153 : : } - 154 : : - 155 : : /** - 156 : : * @notice Get all document names for a specific smart contract - 157 : : */ - 158 : : function getAllDocuments( - 159 : : address smartContract - 160 : : ) external view returns (bytes32[] memory) { - 161 : 14 : return _documentNames[smartContract]; - 162 : : } - 163 : : - 164 : : - 165 : : /* ============ ACCESS CONTROL ============ */ - 166 : : /* - 167 : : * @dev Returns `true` if `account` has been granted `role`. - 168 : : */ - 169 : : function hasRole( - 170 : : bytes32 role, - 171 : : address account - 172 : : ) public view virtual override returns (bool) { - 173 : : // The Default Admin has all roles - 174 [ + + ]: 98 : if (AccessControl.hasRole(DEFAULT_ADMIN_ROLE, account)) { - 175 : 86 : return true; - 176 : : } - 177 : 18 : return AccessControl.hasRole(role, account); - 178 : : } - 179 : : - 180 : : /*////////////////////////////////////////////////////////////// - 181 : : INTERNAL FUNCTIONS - 182 : : //////////////////////////////////////////////////////////////*/ - 183 : : - 184 : : /** - 185 : : * @dev Internal function to fetch a document + 146 : : function getDocument( + 147 : : bytes32 name_ + 148 : : ) external view override returns (string memory, bytes32, uint256) { + 149 : 3 : return _getDocument(msg.sender, name_); + 150 : : } + 151 : : + 152 : : /** + 153 : : * @notice Public function to get a document for a specific contract address + 154 : : */ + 155 : : function getDocument( + 156 : : address smartContract, + 157 : : bytes32 name_ + 158 : : ) external view returns (string memory, bytes32, uint256) { + 159 : 57 : return _getDocument(smartContract, name_); + 160 : : } + 161 : : + 162 : : /** + 163 : : * @notice Get all document names for msg.sender + 164 : : */ + 165 : : function getAllDocuments() + 166 : : external + 167 : : view + 168 : : override + 169 : : returns (bytes32[] memory) + 170 : : { + 171 : 2 : return _documentNames[msg.sender]; + 172 : : } + 173 : : + 174 : : /** + 175 : : * @notice Get all document names for a specific smart contract + 176 : : */ + 177 : : function getAllDocuments( + 178 : : address smartContract + 179 : : ) external view returns (bytes32[] memory) { + 180 : 14 : return _documentNames[smartContract]; + 181 : : } + 182 : : + 183 : : /* ============ ACCESS CONTROL ============ */ + 184 : : /* + 185 : : * @dev Returns `true` if `account` has been granted `role`. 186 : : */ - 187 : : function _getDocument( - 188 : : address smartContract, - 189 : : bytes32 name_ - 190 : : ) internal view returns (string memory, bytes32, uint256) { - 191 : 40 : Document memory doc = _documents[smartContract][name_]; - 192 : 40 : return (doc.uri, doc.documentHash, doc.lastModified); - 193 : : } - 194 : : - 195 : : /** - 196 : : * @dev Internal helper to remove the document name from the list of document names - 197 : : */ - 198 : : function _removeDocumentName( - 199 : : address smartContract, - 200 : : bytes32 name_ - 201 : : ) internal { - 202 : 10 : uint256 length = _documentNames[smartContract].length; - 203 : 15 : for (uint256 i = 0; i < length; ++i) { - 204 [ # + ]: 10 : if (_documentNames[smartContract][i] == name_) { - 205 : 10 : _documentNames[smartContract][i] = _documentNames[ - 206 : : smartContract - 207 : : ][length - 1]; - 208 : 10 : _documentNames[smartContract].pop(); - 209 : 10 : break; - 210 : : } - 211 : : } - 212 : : } - 213 : : - 214 : : function _removeDocument(address smartContract, bytes32 name_) internal { - 215 : 10 : Document memory doc = _documents[smartContract][name_]; - 216 : 10 : emit DocumentRemoved(smartContract, name_, doc.uri, doc.documentHash); - 217 : : - 218 : 10 : delete _documents[smartContract][name_]; - 219 : 10 : _removeDocumentName(smartContract, name_); - 220 : : } - 221 : : - 222 : : function _setDocument( - 223 : : address smartContract, - 224 : : bytes32 name_, - 225 : : string memory uri_, - 226 : : bytes32 documentHash_ - 227 : : ) internal { - 228 : 76 : Document storage doc = _documents[smartContract][name_]; - 229 [ + + ]: 76 : if (doc.lastModified == 0) { - 230 : : // new document - 231 : 58 : _documentNames[smartContract].push(name_); - 232 : : } - 233 : 76 : doc.uri = uri_; - 234 : 76 : doc.documentHash = documentHash_; - 235 : 76 : doc.lastModified = block.timestamp; - 236 : 76 : emit DocumentUpdated(smartContract, name_, uri_, documentHash_); - 237 : : } - 238 : : } + 187 : : function hasRole( + 188 : : bytes32 role, + 189 : : address account + 190 : : ) public view virtual override returns (bool) { + 191 : : // The Default Admin has all roles + 192 [ + + ]: 100 : if (AccessControl.hasRole(DEFAULT_ADMIN_ROLE, account)) { + 193 : 88 : return true; + 194 : : } + 195 : 18 : return AccessControl.hasRole(role, account); + 196 : : } + 197 : : + 198 : : /*////////////////////////////////////////////////////////////// + 199 : : INTERNAL FUNCTIONS + 200 : : //////////////////////////////////////////////////////////////*/ + 201 : : + 202 : : /** + 203 : : * @dev Internal function to fetch a document + 204 : : */ + 205 : : function _getDocument( + 206 : : address smartContract, + 207 : : bytes32 name_ + 208 : : ) internal view returns (string memory, bytes32, uint256) { + 209 : 40 : Document memory doc = _documents[smartContract][name_]; + 210 : 40 : return (doc.uri, doc.documentHash, doc.lastModified); + 211 : : } + 212 : : + 213 : : /** + 214 : : * @dev Internal helper to remove the document name from the list of document names + 215 : : */ + 216 : : function _removeDocumentName( + 217 : : address smartContract, + 218 : : bytes32 name_ + 219 : : ) internal { + 220 : 10 : uint256 length = _documentNames[smartContract].length; + 221 : 15 : for (uint256 i = 0; i < length; ++i) { + 222 [ # + ]: 10 : if (_documentNames[smartContract][i] == name_) { + 223 : 10 : _documentNames[smartContract][i] = _documentNames[ + 224 : : smartContract + 225 : : ][length - 1]; + 226 : 10 : _documentNames[smartContract].pop(); + 227 : 10 : break; + 228 : : } + 229 : : } + 230 : : } + 231 : : + 232 : : function _removeDocument(address smartContract, bytes32 name_) internal { + 233 : 10 : Document memory doc = _documents[smartContract][name_]; + 234 : 10 : emit DocumentRemoved(smartContract, name_, doc.uri, doc.documentHash); + 235 : : + 236 : 10 : delete _documents[smartContract][name_]; + 237 : 10 : _removeDocumentName(smartContract, name_); + 238 : : } + 239 : : + 240 : : function _setDocument( + 241 : : address smartContract, + 242 : : bytes32 name_, + 243 : : string memory uri_, + 244 : : bytes32 documentHash_ + 245 : : ) internal { + 246 : 78 : Document storage doc = _documents[smartContract][name_]; + 247 [ + + ]: 78 : if (doc.lastModified == 0) { + 248 : : // new document + 249 : 60 : _documentNames[smartContract].push(name_); + 250 : : } + 251 : 78 : doc.uri = uri_; + 252 : 78 : doc.documentHash = documentHash_; + 253 : 78 : doc.lastModified = block.timestamp; + 254 : 78 : emit DocumentUpdated(smartContract, name_, uri_, documentHash_); + 255 : : } + 256 : : + 257 : : /*////////////////////////////////////////////////////////////// + 258 : : ERC2771 + 259 : : //////////////////////////////////////////////////////////////*/ + 260 : : + 261 : : /** + 262 : : * @dev This surcharge is not necessary if you do not use ERC2771 + 263 : : */ + 264 : : function _msgSender() + 265 : : internal + 266 : : view + 267 : : override(ERC2771Context, Context) + 268 : : returns (address sender) + 269 : : { + 270 : 150 : return ERC2771Context._msgSender(); + 271 : : } + 272 : : + 273 : : /** + 274 : : * @dev This surcharge is not necessary if you do not use ERC2771 + 275 : : */ + 276 : : function _msgData() + 277 : : internal + 278 : : view + 279 : : override(ERC2771Context, Context) + 280 : : returns (bytes calldata) + 281 : : { + 282 : 0 : return ERC2771Context._msgData(); + 283 : : } + 284 : : + 285 : : /** + 286 : : * @dev This surcharge is not necessary if you do not use the MetaTxModule + 287 : : */ + 288 : : function _contextSuffixLength() + 289 : : internal + 290 : : view + 291 : : override(ERC2771Context, Context) + 292 : : returns (uint256) + 293 : : { + 294 : 150 : return ERC2771Context._contextSuffixLength(); + 295 : : } + 296 : : } diff --git a/doc/coverage/coverage/src/index-sort-b.html b/doc/coverage/coverage/src/index-sort-b.html index c50c7f5..2fc9e73 100644 --- a/doc/coverage/coverage/src/index-sort-b.html +++ b/doc/coverage/coverage/src/index-sort-b.html @@ -31,18 +31,18 @@ lcov.info Lines: - 48 - 48 - 100.0 % + 51 + 52 + 98.1 % Date: - 2024-09-03 09:18:28 + 2024-09-09 15:02:57 Functions: - 10 - 11 - 90.9 % + 12 + 14 + 85.7 % @@ -84,12 +84,12 @@ DocumentEngine.sol -
100.0%
+
98.1%98.1%
- 100.0 % - 48 / 48 - 90.9 % - 10 / 11 + 98.1 % + 51 / 52 + 85.7 % + 12 / 14 92.9 % 13 / 14 diff --git a/doc/coverage/coverage/src/index-sort-f.html b/doc/coverage/coverage/src/index-sort-f.html index 78be304..6d71c92 100644 --- a/doc/coverage/coverage/src/index-sort-f.html +++ b/doc/coverage/coverage/src/index-sort-f.html @@ -31,18 +31,18 @@ lcov.info Lines: - 48 - 48 - 100.0 % + 51 + 52 + 98.1 % Date: - 2024-09-03 09:18:28 + 2024-09-09 15:02:57 Functions: - 10 - 11 - 90.9 % + 12 + 14 + 85.7 % @@ -84,12 +84,12 @@ DocumentEngine.sol -
100.0%
+
98.1%98.1%
- 100.0 % - 48 / 48 - 90.9 % - 10 / 11 + 98.1 % + 51 / 52 + 85.7 % + 12 / 14 92.9 % 13 / 14 diff --git a/doc/coverage/coverage/src/index-sort-l.html b/doc/coverage/coverage/src/index-sort-l.html index 81a3037..9d0c2f2 100644 --- a/doc/coverage/coverage/src/index-sort-l.html +++ b/doc/coverage/coverage/src/index-sort-l.html @@ -31,18 +31,18 @@ lcov.info Lines: - 48 - 48 - 100.0 % + 51 + 52 + 98.1 % Date: - 2024-09-03 09:18:28 + 2024-09-09 15:02:57 Functions: - 10 - 11 - 90.9 % + 12 + 14 + 85.7 % @@ -84,12 +84,12 @@ DocumentEngine.sol -
100.0%
+
98.1%98.1%
- 100.0 % - 48 / 48 - 90.9 % - 10 / 11 + 98.1 % + 51 / 52 + 85.7 % + 12 / 14 92.9 % 13 / 14 diff --git a/doc/coverage/coverage/src/index.html b/doc/coverage/coverage/src/index.html index 6573f91..64ab2cb 100644 --- a/doc/coverage/coverage/src/index.html +++ b/doc/coverage/coverage/src/index.html @@ -31,18 +31,18 @@ lcov.info Lines: - 48 - 48 - 100.0 % + 51 + 52 + 98.1 % Date: - 2024-09-03 09:18:28 + 2024-09-09 15:02:57 Functions: - 10 - 11 - 90.9 % + 12 + 14 + 85.7 % @@ -84,12 +84,12 @@ DocumentEngine.sol -
100.0%
+
98.1%98.1%
- 100.0 % - 48 / 48 - 90.9 % - 10 / 11 + 98.1 % + 51 / 52 + 85.7 % + 12 / 14 92.9 % 13 / 14 diff --git a/doc/coverage/lcov.info b/doc/coverage/lcov.info index ffd73b6..ba3dafb 100644 --- a/doc/coverage/lcov.info +++ b/doc/coverage/lcov.info @@ -1,169 +1,185 @@ TN: SF:src/DocumentEngine.sol -FN:25,DocumentEngine.setDocument -FNDA:27,DocumentEngine.setDocument -DA:31,26 -DA:31,26 -FN:37,DocumentEngine.removeDocument +FN:46,DocumentEngine.setDocument +FNDA:28,DocumentEngine.setDocument +DA:52,27 +DA:52,27 +FN:58,DocumentEngine.removeDocument FNDA:2,DocumentEngine.removeDocument -DA:41,1 -DA:41,1 -FN:47,DocumentEngine.batchSetDocuments +DA:62,1 +DA:62,1 +FN:68,DocumentEngine.batchSetDocuments FNDA:8,DocumentEngine.batchSetDocuments -DA:54,7 -DA:54,7 -DA:54,7 -DA:54,7 -DA:54,7 -DA:55,6 -DA:55,6 -DA:56,5 -DA:56,5 -DA:57,4 -DA:57,4 -BRDA:53,0,0,3 -BRDA:53,0,1,4 -DA:59,3 -DA:59,3 -DA:61,4 -DA:61,4 -DA:61,12 -DA:61,8 -DA:62,8 -DA:62,8 -FN:69,DocumentEngine.batchSetDocuments -FNDA:5,DocumentEngine.batchSetDocuments -DA:76,4 -DA:76,4 -DA:76,4 -DA:76,4 -DA:76,3 -DA:77,2 -DA:77,2 -BRDA:75,1,0,2 -BRDA:75,1,1,2 -DA:79,2 -DA:79,2 -DA:81,2 -DA:81,2 -DA:81,6 -DA:81,4 +DA:75,7 +DA:75,7 +DA:75,7 +DA:75,7 +DA:75,7 +DA:76,6 +DA:76,6 +DA:77,5 +DA:77,5 +DA:78,4 +DA:78,4 +BRDA:74,0,0,3 +BRDA:74,0,1,4 +DA:80,3 +DA:80,3 DA:82,4 DA:82,4 -FN:89,DocumentEngine.batchRemoveDocuments -FNDA:4,DocumentEngine.batchRemoveDocuments -DA:94,3 -DA:94,3 -DA:94,3 -BRDA:93,2,0,2 -BRDA:93,2,1,1 -DA:97,2 -DA:97,2 -DA:100,1 -DA:100,1 -DA:100,3 -DA:100,2 +DA:82,12 +DA:82,8 +DA:83,8 +DA:83,8 +FN:90,DocumentEngine.batchSetDocuments +FNDA:5,DocumentEngine.batchSetDocuments +DA:97,4 +DA:97,4 +DA:97,4 +DA:97,4 +DA:98,3 +DA:98,3 +DA:99,2 +DA:99,2 +BRDA:96,1,0,2 +BRDA:96,1,1,2 DA:101,2 DA:101,2 -FN:108,DocumentEngine.batchRemoveDocuments -FNDA:3,DocumentEngine.batchRemoveDocuments -DA:113,2 -DA:113,2 -BRDA:112,3,0,1 -BRDA:112,3,1,1 -DA:115,1 -DA:115,1 -DA:118,1 -DA:118,1 -DA:118,3 -DA:118,2 +DA:103,2 +DA:103,2 +DA:103,6 +DA:103,4 +DA:104,4 +DA:104,4 +FN:111,DocumentEngine.batchRemoveDocuments +FNDA:4,DocumentEngine.batchRemoveDocuments +DA:116,3 +DA:116,3 +DA:116,3 +BRDA:115,2,0,2 +BRDA:115,2,1,1 DA:119,2 DA:119,2 -FN:127,DocumentEngine.getDocument +DA:122,1 +DA:122,1 +DA:122,3 +DA:122,2 +DA:123,2 +DA:123,2 +FN:130,DocumentEngine.batchRemoveDocuments +FNDA:3,DocumentEngine.batchRemoveDocuments +DA:134,2 +DA:134,2 +BRDA:134,3,0,1 +BRDA:134,3,1,1 +DA:135,1 +DA:135,1 +DA:138,1 +DA:138,1 +DA:138,3 +DA:138,2 +DA:139,2 +DA:139,2 +FN:146,DocumentEngine.getDocument FNDA:1,DocumentEngine.getDocument -DA:130,1 -DA:130,1 -DA:130,1 -FN:136,DocumentEngine.getDocument +DA:149,1 +DA:149,1 +DA:149,1 +FN:155,DocumentEngine.getDocument FNDA:19,DocumentEngine.getDocument -DA:140,19 -DA:140,19 -DA:140,19 -FN:146,DocumentEngine.getAllDocuments +DA:159,19 +DA:159,19 +DA:159,19 +FN:165,DocumentEngine.getAllDocuments FNDA:1,DocumentEngine.getAllDocuments -DA:152,1 -DA:152,1 -FN:158,DocumentEngine.getAllDocuments +DA:171,1 +DA:171,1 +FN:177,DocumentEngine.getAllDocuments FNDA:7,DocumentEngine.getAllDocuments -DA:161,7 -DA:161,7 -FN:169,DocumentEngine.hasRole +DA:180,7 +DA:180,7 +FN:187,DocumentEngine.hasRole FNDA:0,DocumentEngine.hasRole -DA:174,49 -DA:174,49 -BRDA:174,4,0,43 -BRDA:174,4,1,6 -DA:175,43 -DA:175,43 -DA:177,6 -DA:177,6 -DA:177,6 -FN:187,DocumentEngine._getDocument +DA:192,50 +DA:192,50 +BRDA:192,4,0,44 +BRDA:192,4,1,6 +DA:193,44 +DA:193,44 +DA:195,6 +DA:195,6 +DA:195,6 +FN:205,DocumentEngine._getDocument FNDA:20,DocumentEngine._getDocument -DA:191,20 -DA:191,20 -DA:192,20 -DA:192,20 -FN:198,DocumentEngine._removeDocumentName +DA:209,20 +DA:209,20 +DA:210,20 +DA:210,20 +FN:216,DocumentEngine._removeDocumentName FNDA:5,DocumentEngine._removeDocumentName -DA:202,5 -DA:202,5 -DA:203,5 -DA:203,5 -DA:203,5 -DA:203,0 -DA:204,5 -DA:204,5 -BRDA:204,5,0,- -BRDA:204,5,1,5 -DA:205,5 -DA:205,5 -DA:208,5 -DA:208,5 -DA:209,5 -DA:209,5 -FN:214,DocumentEngine._removeDocument +DA:220,5 +DA:220,5 +DA:221,5 +DA:221,5 +DA:221,5 +DA:221,0 +DA:222,5 +DA:222,5 +BRDA:222,5,0,- +BRDA:222,5,1,5 +DA:223,5 +DA:223,5 +DA:226,5 +DA:226,5 +DA:227,5 +DA:227,5 +FN:232,DocumentEngine._removeDocument FNDA:5,DocumentEngine._removeDocument -DA:215,5 -DA:215,5 -DA:216,5 -DA:216,5 -DA:218,5 -DA:218,5 -DA:219,5 -DA:219,5 -FN:222,DocumentEngine._setDocument -FNDA:38,DocumentEngine._setDocument -DA:228,38 -DA:228,38 -DA:229,38 -DA:229,38 -BRDA:229,6,0,29 -BRDA:229,6,1,38 -DA:231,29 -DA:231,29 -DA:233,38 -DA:233,38 -DA:234,38 -DA:234,38 -DA:235,38 -DA:235,38 -DA:236,38 -DA:236,38 -FNF:15 -FNH:14 -LF:48 -LH:48 +DA:233,5 +DA:233,5 +DA:234,5 +DA:234,5 +DA:236,5 +DA:236,5 +DA:237,5 +DA:237,5 +FN:240,DocumentEngine._setDocument +FNDA:39,DocumentEngine._setDocument +DA:246,39 +DA:246,39 +DA:247,39 +DA:247,39 +BRDA:247,6,0,30 +BRDA:247,6,1,39 +DA:249,30 +DA:249,30 +DA:251,39 +DA:251,39 +DA:252,39 +DA:252,39 +DA:253,39 +DA:253,39 +DA:254,39 +DA:254,39 +FN:264,DocumentEngine._msgSender +FNDA:50,DocumentEngine._msgSender +DA:270,50 +DA:270,50 +DA:270,50 +FN:276,DocumentEngine._msgData +FNDA:0,DocumentEngine._msgData +DA:282,0 +DA:282,0 +DA:282,0 +FN:288,DocumentEngine._contextSuffixLength +FNDA:50,DocumentEngine._contextSuffixLength +DA:294,50 +DA:294,50 +DA:294,50 +FNF:18 +FNH:16 +LF:52 +LH:51 BRF:14 BRH:13 end_of_record diff --git a/src/DocumentEngine.sol b/src/DocumentEngine.sol index 1fd41ce..3f4fff9 100644 --- a/src/DocumentEngine.sol +++ b/src/DocumentEngine.sol @@ -10,7 +10,12 @@ import "./DocumentEngineInvariant.sol"; * @title DocumentEngine * @notice contract to manage documents on-chain through ERC-1643 */ -contract DocumentEngine is IERC1643, DocumentEngineInvariant, AccessControl, ERC2771Context { +contract DocumentEngine is + IERC1643, + DocumentEngineInvariant, + AccessControl, + ERC2771Context +{ /** * @notice * Get the current version of the smart contract @@ -21,7 +26,10 @@ contract DocumentEngine is IERC1643, DocumentEngineInvariant, AccessControl, ERC mapping(address => bytes32[]) private _documentNames; // Constructor to initialize the admin role - constructor(address admin, address forwarderIrrevocable) ERC2771Context(forwarderIrrevocable){ + constructor( + address admin, + address forwarderIrrevocable + ) ERC2771Context(forwarderIrrevocable) { if (admin == address(0)) { revert AdminWithAddressZeroNotAllowed(); } diff --git a/test/DocumentEngine.t.sol b/test/DocumentEngine.t.sol index 99f8b76..fda9890 100644 --- a/test/DocumentEngine.t.sol +++ b/test/DocumentEngine.t.sol @@ -70,9 +70,7 @@ contract DocumentEngineTest is Test, DocumentEngineInvariant, AccessControl { assertEq(documentEngine.isTrustedForwarder(forwarder), true); // admin vm.expectRevert( - abi.encodeWithSelector( - AdminWithAddressZeroNotAllowed.selector - ) + abi.encodeWithSelector(AdminWithAddressZeroNotAllowed.selector) ); documentEngine = new DocumentEngine(AddressZero, forwarder); }