diff --git a/contracts/BaseDocumentStore.sol b/contracts/BaseDocumentStore.sol index 4ae303b..1dc0b7d 100644 --- a/contracts/BaseDocumentStore.sol +++ b/contracts/BaseDocumentStore.sol @@ -35,7 +35,7 @@ contract BaseDocumentStore is Initializable { * @notice Emitted when a document is revoked * @param document The hash of the revoked document */ - event DocumentRevoked(bytes32 indexed document); + event DocumentRevoked(bytes32 indexed documentRoot, bytes32 indexed document); /** * @notice Initialises the contract with a name @@ -49,9 +49,9 @@ contract BaseDocumentStore is Initializable { * @notice Issues a document * @param document The hash of the document to issue */ - function _issue(bytes32 document) internal onlyNotIssued(document) { + function _issue(bytes32 document) internal { documentIssued[document] = block.number; - emit DocumentIssued(document); + // emit DocumentIssued(document); } /** @@ -97,11 +97,8 @@ contract BaseDocumentStore is Initializable { * @param document The hash of the document to revoke * @return A boolean indicating whether the document was successfully revoked */ - function _revoke(bytes32 document) internal onlyNotRevoked(document) returns (bool) { + function _revoke(bytes32 document) internal { documentRevoked[document] = block.number; - emit DocumentRevoked(document); - - return true; } function _bulkRevoke(bytes32[] memory documents) internal { @@ -115,7 +112,7 @@ contract BaseDocumentStore is Initializable { * @param document The hash of the document to check * @return A boolean indicating whether the document has been revoked */ - function isRevoked(bytes32 document) public view returns (bool) { + function _isRevoked(bytes32 document) internal view returns (bool) { return documentRevoked[document] != 0; } @@ -143,6 +140,7 @@ contract BaseDocumentStore is Initializable { * @param document The hash of the document to check */ modifier onlyNotIssued(bytes32 document) { + // TODO: TO BE REMOVED require(!isIssued(document), "Error: Only hashes that have not been issued can be issued"); _; } @@ -152,7 +150,8 @@ contract BaseDocumentStore is Initializable { * @param claim The hash of the document to check */ modifier onlyNotRevoked(bytes32 claim) { - require(!isRevoked(claim), "Error: Hash has been revoked previously"); + // TODO: TO BE REMOVED + require(!_isRevoked(claim), "Error: Hash has been revoked previously"); _; } } diff --git a/contracts/DocumentStore.sol b/contracts/DocumentStore.sol index b4b4a34..23af987 100644 --- a/contracts/DocumentStore.sol +++ b/contracts/DocumentStore.sol @@ -14,6 +14,11 @@ import "./base/DocumentStoreAccessControl.sol"; * @notice A contract for storing and revoking documents with access control */ contract DocumentStore is DocumentStoreAccessControl, BaseDocumentStore { + error InactiveDocument(bytes32 documentRoot, bytes32 document); + error DocumentAlreadyRevoked(bytes32 document); + error DocumentAlreadyIssued(bytes32 document); + error InvalidDocument(); + using MerkleProof for bytes32[]; /** @@ -39,8 +44,14 @@ contract DocumentStore is DocumentStoreAccessControl, BaseDocumentStore { * @notice Issues a document * @param document The hash of the document to issue */ - function issue(bytes32 documentRoot) public onlyRole(ISSUER_ROLE) onlyNotIssued(document) { + function issue(bytes32 documentRoot) public onlyRole(ISSUER_ROLE) { + if (isRootIssued(documentRoot)) { + revert DocumentAlreadyIssued(documentRoot); + } + _issue(documentRoot); + + emit DocumentIssued(documentRoot); } /** @@ -56,35 +67,90 @@ contract DocumentStore is DocumentStoreAccessControl, BaseDocumentStore { * @param document The hash of the document to revoke * @return A boolean indicating whether the revocation was successful */ - function revoke(bytes32 document) public onlyRole(REVOKER_ROLE) onlyNotRevoked(document) returns (bool) { - return _revoke(document); + function revokeRoot(bytes32 documentRoot) public onlyRole(REVOKER_ROLE) { + revoke(documentRoot, documentRoot, new bytes32[](0)); + } + + function revoke( + bytes32 documentRoot, + bytes32 document, + bytes32[] memory proof + ) public onlyRole(REVOKER_ROLE) { + bool isActive = isActive(documentRoot, document, proof); + if (!isActive) { + revert InActiveDocument(documentRoot, document); + } + _revoke(document); + emit DocumentRevoked(documentRoot, document); } /** * @notice Revokes documents in bulk * @param documents The hashes of the documents to revoke */ - function bulkRevoke(bytes32[] memory documents) public onlyRole(REVOKER_ROLE) { - return _bulkRevoke(documents); + function bulkRevoke( + bytes32[] documentRoots, + bytes32[] documents, + bytes32[][] memory proofs + ) public onlyRole(REVOKER_ROLE) { + for (uint256 i = 0; i < documentRoots.length; i++) { + revoke(documentRoots[i], documents[i], proofs[i]); + } + } + + function bulkRevokeRoot(bytes32[] documentRoots, bytes32[][] memory proofs) public onlyRole(REVOKER_ROLE) { + for (uint256 i = 0; i < documentRoots.length; i++) { + revoke(documentRoots[i], documentRoots[i], proofs[i]); + } } function isIssued( bytes32 documentRoot, bytes32 document, bytes32[] memory proof - ) public view returns (bool) { - require(document != 0x0 && documentRoot != 0x0, "Invalid document"); + ) public view onlyValidDocument(documentRoot, document) returns (bool) { if (documentRoot == document && proof.length == 0) { - return _isIssued(document) && !isRevoked(document); + return _isIssued(document); } - return - _isIssued(documentRoot) && - !isRevoked(documentRoot) && - !isRevoked(document) && - proof.verify(documentRoot, document); + return _isIssued(documentRoot) && proof.verify(documentRoot, document); } - function isIssued(bytes32 documentRoot) public view returns (bool) { + function isRootIssued(bytes32 documentRoot) public view returns (bool) { return isIssued(documentRoot, documentRoot, new bytes32[](0)); } + + function isRevoked( + bytes32 documentRoot, + bytes32 document, + bytes32[] memory proof + ) public view onlyValidDocument(documentRoot, document) returns (bool) { + if (documentRoot == document && proof.length == 0) { + return _isRevoked(document); + } + return (_isRevoked(documentRoot) || _isRevoked(document)) && proof.verify(documentRoot, document); + } + + /** + * @notice Checks if a document has been revoked + * @param document The hash of the document to check + * @return A boolean indicating whether the document has been revoked + */ + function isRootRevoked(bytes32 documentRoot) public view returns (bool) { + return isRevoked(documentRoot, documentRoot, new bytes32[](0)); + } + + function isActive( + bytes32 documentRoot, + bytes32 document, + bytes32[] memory proof + ) public view returns (bool) { + return isIssued(documentRoot, document, proof) && !isRevoked(documentRoot, document, proof); + } + + modifier onlyValidDocument(bytes32 documentRoot, bytes32 document) { + if (document == 0x0 || documentRoot == 0x0) { + revert InvalidDocument(); + } + _; + } }