Skip to content

Commit

Permalink
feat: pausable transferable document
Browse files Browse the repository at this point in the history
  • Loading branch information
superical committed Mar 27, 2024
1 parent 1e3cae5 commit 5ed53a9
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 4 deletions.
29 changes: 25 additions & 4 deletions src/base/BaseTransferableDocumentStore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pragma solidity >=0.8.23 <0.9.0;

import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";

import "../base/DocumentStoreAccessControl.sol";
Expand All @@ -13,6 +14,7 @@ import "../interfaces/IERC5192.sol";
abstract contract BaseTransferableDocumentStore is
DocumentStoreAccessControl,
ERC721Upgradeable,
PausableUpgradeable,
IERC5192,
ITransferableDocumentStoreErrors,
ITransferableDocumentStore
Expand All @@ -26,8 +28,7 @@ abstract contract BaseTransferableDocumentStore is
mapping(uint256 => bool) locked;
}

// keccak256(abi.encode(uint256(keccak256("openattestation.storage.TransferableDocumentStore")) - 1)) &
// ~bytes32(uint256(0xff))
// keccak256(abi.encode(uint256(keccak256("openattestation.storage.TransferableDocumentStore")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant _DocumentStoreStorageSlot =
0xe00db8ee8fc09b2a809fe10830715c77ed23b7661f93309e127fb70c1f33ef00;

Expand Down Expand Up @@ -124,10 +125,26 @@ abstract contract BaseTransferableDocumentStore is
return _isLocked(tokenId);
}

function setBaseURI(string memory baseURI) public onlyRole(DEFAULT_ADMIN_ROLE) {
function setBaseURI(string memory baseURI) public whenNotPaused onlyRole(DEFAULT_ADMIN_ROLE) {
_getStorage().baseURI = baseURI;
}

/**
* @dev Pauses all token transfers.
* @notice Requires the caller to be admin.
*/
function pause() external onlyRole(DEFAULT_ADMIN_ROLE) {
_pause();
}

/**
* @dev Unpauses all token transfers.
* @notice Requires the caller to be admin.
*/
function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
_unpause();
}

function _isRevoked(uint256 tokenId) internal view returns (bool) {
return _getStorage().revoked[tokenId];
}
Expand All @@ -136,7 +153,11 @@ abstract contract BaseTransferableDocumentStore is
return _getStorage().locked[tokenId];
}

function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) {
function _update(
address to,
uint256 tokenId,
address auth
) internal virtual override whenNotPaused returns (address) {
address from = super._update(to, tokenId, auth);
if (_isLocked(tokenId) && (from != address(0) && to != address(0))) {
revert DocumentLocked(bytes32(tokenId));
Expand Down
100 changes: 100 additions & 0 deletions test/TransferableDocumentStore.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -498,3 +498,103 @@ contract TransferableDocumentStore_supportsInterface_Test is TranferableDocument
assertTrue(documentStore.supportsInterface(type(IAccessControl).interfaceId));
}
}

contract TransferableDocumentStore_Pause_Test is TranferableDocumentStoreCommonTest {
function testPause() public {
vm.prank(owner);
documentStore.pause();

assertTrue(documentStore.paused(), "Document store should be paused");
}

function testUnpause() public {
vm.prank(owner);
documentStore.pause();
vm.prank(owner);
documentStore.unpause();

assertFalse(documentStore.paused(), "Document store should not be paused");
}

function testPauseAsNonAdminRevert() public {
address nonAdmin = vm.addr(69);

vm.expectRevert(
abi.encodeWithSelector(
IAccessControl.AccessControlUnauthorizedAccount.selector,
nonAdmin,
documentStore.DEFAULT_ADMIN_ROLE()
)
);

vm.prank(nonAdmin);
documentStore.pause();
}

function testUnpauseAsNonAdminRevert() public {
address nonAdmin = vm.addr(69);

vm.expectRevert(
abi.encodeWithSelector(
IAccessControl.AccessControlUnauthorizedAccount.selector,
nonAdmin,
documentStore.DEFAULT_ADMIN_ROLE()
)
);

vm.prank(nonAdmin);
documentStore.unpause();
}
}

contract TransferableDocumentStore_Pausable_Test is TransferableDocumentStore_Initializer {
function setUp() public override {
super.setUp();

vm.prank(owner);
documentStore.pause();
}

function testIssueWhenPausedRevert() public {
bytes32 fakeDoc = "0x1234";

vm.expectRevert(abi.encodeWithSelector(PausableUpgradeable.EnforcedPause.selector));

vm.prank(issuer);
documentStore.issue(recipients[0], fakeDoc, false);
}

function testRevokeWhenPausedRevert() public {
bytes32 revokeDoc = documents()[0];

vm.expectRevert(abi.encodeWithSelector(PausableUpgradeable.EnforcedPause.selector));

vm.prank(revoker);
documentStore.revoke(revokeDoc);
}

function testNonlockedDocTransferFromWhenPausedRevert() public {
bytes32 transferNonLockedDoc = documents()[0];

vm.expectRevert(abi.encodeWithSelector(PausableUpgradeable.EnforcedPause.selector));

vm.prank(recipients[0]);
documentStore.transferFrom(recipients[0], recipients[1], uint256(transferNonLockedDoc));
}

function testLockedDocTransferFromWhenPausedRevert() public {
bytes32 transferLockedDoc = documents()[1];

vm.expectRevert(abi.encodeWithSelector(PausableUpgradeable.EnforcedPause.selector));

vm.prank(recipients[0]);
documentStore.transferFrom(recipients[1], recipients[0], uint256(transferLockedDoc));
}

function testSetBaseURIWhenPausedRevert() public {
vm.expectRevert(abi.encodeWithSelector(PausableUpgradeable.EnforcedPause.selector));

vm.prank(owner);
documentStore.setBaseURI("https://example.com/");
}
}

0 comments on commit 5ed53a9

Please sign in to comment.