Skip to content

Commit

Permalink
feat: erc5192 for soulbound document
Browse files Browse the repository at this point in the history
  • Loading branch information
superical committed Mar 18, 2024
1 parent a1a6429 commit 06ce819
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 44 deletions.
39 changes: 34 additions & 5 deletions src/OwnableDocumentStore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ import {IDocumentStore} from "./interfaces/IDocumentStore.sol";
import "./base/DocumentStoreAccessControl.sol";
import "./interfaces/IOwnableDocumentStoere.sol";
import "./interfaces/IOwnableDocumentStoreErrors.sol";
import "./interfaces/IERC5192.sol";

contract OwnableDocumentStore is
DocumentStoreAccessControl,
ERC721Upgradeable,
IERC5192,
IOwnableDocumentStoreErrors,
IOwnableDocumentStore
{
mapping(uint256 => bool) private _revoked;
mapping(uint256 => bool) private _locked;

constructor(string memory name_, string memory symbol_, address initAdmin) {
initialize(name_, symbol_, initAdmin);
Expand All @@ -38,10 +41,6 @@ contract OwnableDocumentStore is
return super.name();
}

function _isRevoked(uint256 tokenId) internal view returns (bool) {
return _revoked[tokenId];
}

function isActive(bytes32 document) public view nonZeroDocument(document) returns (bool) {
uint256 tokenId = uint256(document);
address owner = _ownerOf(tokenId);
Expand All @@ -54,10 +53,16 @@ contract OwnableDocumentStore is
revert ERC721NonexistentToken(tokenId);
}

function issue(address to, bytes32 document) public nonZeroDocument(document) onlyRole(ISSUER_ROLE) {
function issue(address to, bytes32 document, bool lock) public nonZeroDocument(document) onlyRole(ISSUER_ROLE) {
uint256 tokenId = uint256(document);
if (!_isRevoked(tokenId)) {
_mint(to, tokenId);
if (lock) {
_locked[tokenId] = true;
emit Locked(tokenId);
} else {
emit Unlocked(tokenId);
}
} else {
revert DocumentIsRevoked(document);
}
Expand Down Expand Up @@ -96,9 +101,33 @@ contract OwnableDocumentStore is
return
interfaceId == type(IDocumentStore).interfaceId ||
interfaceId == type(IOwnableDocumentStore).interfaceId ||
interfaceId == type(IERC5192).interfaceId ||
super.supportsInterface(interfaceId);
}

function locked(uint256 tokenId) public view returns (bool) {
if (tokenId == 0) {
revert ZeroDocument();
}
return _isLocked(tokenId);
}

function _isRevoked(uint256 tokenId) internal view returns (bool) {
return _revoked[tokenId];
}

function _isLocked(uint256 tokenId) internal view returns (bool) {
return _locked[tokenId];
}

function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) {
address from = super._update(to, tokenId, auth);
if (_isLocked(tokenId) && (from != address(0) && to != address(0))) {
revert DocumentLocked(bytes32(tokenId));
}
return from;
}

modifier nonZeroDocument(bytes32 document) {
uint256 tokenId = uint256(document);
if (tokenId == 0) {
Expand Down
20 changes: 20 additions & 0 deletions src/interfaces/IERC5192.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.23 <0.9.0;

interface IERC5192 {
/// @notice Emitted when the locking status is changed to locked.
/// @dev If a token is minted and the status is locked, this event should be emitted.
/// @param tokenId The identifier for a token.
event Locked(uint256 tokenId);

/// @notice Emitted when the locking status is changed to unlocked.
/// @dev If a token is minted and the status is unlocked, this event should be emitted.
/// @param tokenId The identifier for a token.
event Unlocked(uint256 tokenId);

/// @notice Returns the locking status of an Soulbound Token
/// @dev SBTs assigned to zero address are considered invalid, and queries
/// about them do throw.
/// @param tokenId The identifier for an SBT.
function locked(uint256 tokenId) external view returns (bool);
}
4 changes: 2 additions & 2 deletions src/interfaces/IOwnableDocumentStoere.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.23 <0.9.0;

import {IDocumentStore} from "./IDocumentStore.sol";
import "./IDocumentStore.sol";

interface IOwnableDocumentStore is IDocumentStore {
function issue(address to, bytes32 documentRoot) external;
function issue(address to, bytes32 documentRoot, bool locked) external;
}
2 changes: 2 additions & 0 deletions src/interfaces/IOwnableDocumentStoreErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ interface IOwnableDocumentStoreErrors {
error ZeroDocument();

error DocumentIsRevoked(bytes32 document);

error DocumentLocked(bytes32 document);
}
6 changes: 2 additions & 4 deletions test/CommonTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -245,11 +245,9 @@ abstract contract OwnableDocumentStoreCommonTest is CommonTest {
super.setUp();

vm.startPrank(owner);

documentStore = new OwnableDocumentStore(storeName, storeSymbol, owner);
documentStore.grantRole(documentStore.ISSUER_ROLE(), issuer);
documentStore.grantRole(documentStore.REVOKER_ROLE(), revoker);

vm.stopPrank();
}
}
Expand All @@ -270,8 +268,8 @@ abstract contract OwnableDocumentStore_Initializer is OwnableDocumentStoreCommon
recipients[1] = vm.addr(5);

vm.startPrank(issuer);
documentStore.issue(recipients[0], documents[0]);
documentStore.issue(recipients[1], documents[1]);
documentStore.issue(recipients[0], documents[0], false);
documentStore.issue(recipients[1], documents[1], true);
vm.stopPrank();
}
}
Loading

0 comments on commit 06ce819

Please sign in to comment.