Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: multicall #151

Merged
merged 3 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 2 additions & 26 deletions src/BaseDocumentStore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ pragma solidity >=0.8.23 <0.9.0;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol";

import "./interfaces/IDocumentStore.sol";
import "./interfaces/IDocumentStoreBatchable.sol";
import "./base/DocumentStoreAccessControl.sol";

Expand All @@ -15,8 +15,8 @@ import "./base/DocumentStoreAccessControl.sol";
*/
abstract contract BaseDocumentStore is
Initializable,
MulticallUpgradeable,
IDocumentStoreBatchable,
IDocumentStore,
DocumentStoreAccessControl
{
using MerkleProof for bytes32[];
Expand Down Expand Up @@ -53,16 +53,6 @@ abstract contract BaseDocumentStore is
_issue(documentRoot);
}

/**
* @notice Issues multiple documents
* @param documentRoots The hashes of the documents to issue
*/
function bulkIssue(bytes32[] memory documentRoots) external onlyRole(ISSUER_ROLE) {
for (uint256 i = 0; i < documentRoots.length; i++) {
_issue(documentRoots[i]);
}
}

function revoke(bytes32 documentRoot, bytes32 document, bytes32[] memory proof) external onlyRole(REVOKER_ROLE) {
_revoke(documentRoot, document, proof);
}
Expand All @@ -75,20 +65,6 @@ abstract contract BaseDocumentStore is
_revoke(documentRoot, documentRoot, new bytes32[](0));
}

/**
* @notice Revokes documents in bulk
* @param documentRoots The hashes of the documents to revoke
*/
function bulkRevoke(
bytes32[] memory documentRoots,
bytes32[] memory documents,
bytes32[][] memory proofs
) external onlyRole(REVOKER_ROLE) {
for (uint256 i = 0; i < documentRoots.length; i++) {
_revoke(documentRoots[i], documents[i], proofs[i]);
}
}

function isIssued(
bytes32 documentRoot,
bytes32 document,
Expand Down
4 changes: 0 additions & 4 deletions src/DocumentStore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@

pragma solidity >=0.8.23 <0.9.0;

import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

import "./BaseDocumentStore.sol";
import "./base/DocumentStoreAccessControl.sol";
import "./interfaces/IDocumentStoreBatchable.sol";

/**
* @title DocumentStore
Expand Down
3 changes: 2 additions & 1 deletion src/interfaces/IDocumentStoreBatchable.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.23 <0.9.0;
import "./IDocumentStore.sol";

interface IDocumentStoreBatchable {
interface IDocumentStoreBatchable is IDocumentStore {
function revoke(bytes32 documentRoot, bytes32 document, bytes32[] memory proof) external;

function isIssued(bytes32 documentRoot, bytes32 document, bytes32[] memory proof) external view returns (bool);
Expand Down
211 changes: 191 additions & 20 deletions test/CommonTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity >=0.8.23 <0.9.0;
import "forge-std/Test.sol";

import "../src/DocumentStore.sol";
import "./fixtures/DocumentStoreFixture.sol";

abstract contract CommonTest is Test {
string public storeName = "DocumentStore Test";
Expand All @@ -25,32 +26,202 @@ abstract contract CommonTest is Test {
}
}

abstract contract DocumentStoreWithFakeDocuments_Base is CommonTest {
abstract contract DocumentStore_Initializer is CommonTest {
bytes32[] public documents;

DocumentStoreFixture private _fixture;

function setUp() public virtual override {
super.setUp();

_fixture = new DocumentStoreFixture();

documents = _fixture.documents();

bytes[] memory issueData = new bytes[](3);
issueData[0] = abi.encodeCall(documentStore.issue, (documents[0]));
issueData[1] = abi.encodeCall(documentStore.issue, (documents[1]));
issueData[2] = abi.encodeCall(documentStore.issue, (documents[2]));

vm.prank(issuer);
documentStore.multicall(issueData);
}
}

abstract contract DocumentStoreBatchable_Initializer is CommonTest {
bytes32 public docRoot;
bytes32[] public documents = new bytes32[](3);
bytes32[][] public proofs = new bytes32[][](3);

DocumentStoreBatchableFixture private _fixture;

function setUp() public virtual override {
super.setUp();

_fixture = new DocumentStoreBatchableFixture();

docRoot = _fixture.docRoot();

documents = _fixture.documents();

proofs = _fixture.proofs();

vm.prank(issuer);
documentStore.issue(docRoot);
}
}

abstract contract DocumentStore_multicall_revoke_Base is CommonTest {
bytes[] public bulkRevokeData;

function docRoots() public view virtual returns (bytes32[] memory);

function documents() public view virtual returns (bytes32[] memory);

function proofs() public view virtual returns (bytes32[][] memory);

function testMulticallRevokeByOwner() public {
vm.expectEmit(true, true, false, true);
emit IDocumentStore.DocumentRevoked(docRoots()[0], documents()[0]);
vm.expectEmit(true, true, false, true);
emit IDocumentStore.DocumentRevoked(docRoots()[1], documents()[1]);
vm.expectEmit(true, true, false, true);
emit IDocumentStore.DocumentRevoked(docRoots()[2], documents()[2]);

vm.prank(owner);
documentStore.multicall(bulkRevokeData);

assertTrue(documentStore.isRevoked(docRoots()[0], documents()[0], proofs()[0]));
assertTrue(documentStore.isRevoked(docRoots()[1], documents()[1], proofs()[1]));
assertTrue(documentStore.isRevoked(docRoots()[2], documents()[2], proofs()[2]));
}

function testMulticallRevokeByRevoker() public {
vm.expectEmit(true, true, false, true);
emit IDocumentStore.DocumentRevoked(docRoots()[0], documents()[0]);
vm.expectEmit(true, true, false, true);
emit IDocumentStore.DocumentRevoked(docRoots()[1], documents()[1]);
vm.expectEmit(true, true, false, true);
emit IDocumentStore.DocumentRevoked(docRoots()[2], documents()[2]);

vm.prank(revoker);
documentStore.multicall(bulkRevokeData);

assertTrue(documentStore.isRevoked(docRoots()[0], documents()[0], proofs()[0]));
assertTrue(documentStore.isRevoked(docRoots()[1], documents()[1], proofs()[1]));
assertTrue(documentStore.isRevoked(docRoots()[2], documents()[2], proofs()[2]));
}

function testMulticallRevokeByIssuerRevert() public {
vm.expectRevert(
abi.encodeWithSelector(
IAccessControl.AccessControlUnauthorizedAccount.selector,
issuer,
documentStore.REVOKER_ROLE()
)
);

vm.prank(issuer);
documentStore.multicall(bulkRevokeData);
}

function testMulticallRevokeByNonRevokerRevert() public {
address notRevoker = vm.addr(69);

vm.expectRevert(
abi.encodeWithSelector(
IAccessControl.AccessControlUnauthorizedAccount.selector,
notRevoker,
documentStore.REVOKER_ROLE()
)
);

vm.prank(notRevoker);
documentStore.multicall(bulkRevokeData);
}

function testMulticallRevokeWithDuplicatesRevert() public {
// Make document1 same as document0
bulkRevokeData[1] = abi.encodeCall(IDocumentStoreBatchable.revoke, (docRoots()[0], documents()[0], proofs()[0]));

// It should revert that document0 is already inactive
vm.expectRevert(abi.encodeWithSelector(IDocumentStore.InactiveDocument.selector, docRoots()[0], documents()[0]));

vm.prank(revoker);
documentStore.multicall(bulkRevokeData);
}
}

abstract contract DocumentStoreBatchable_multicall_revoke_Initializer is DocumentStore_multicall_revoke_Base {
DocumentStoreBatchableFixture private _fixture;

function docRoot() public view virtual returns (bytes32) {
return _fixture.docRoot();
}

function docRoots() public view virtual override returns (bytes32[] memory) {
bytes32[] memory roots = new bytes32[](3);
roots[0] = _fixture.docRoot();
roots[1] = _fixture.docRoot();
roots[2] = _fixture.docRoot();
return roots;
}

function documents() public view virtual override returns (bytes32[] memory) {
return _fixture.documents();
}

function proofs() public view virtual override returns (bytes32[][] memory) {
return _fixture.proofs();
}

function setUp() public virtual override {
super.setUp();

_fixture = new DocumentStoreBatchableFixture();

vm.startPrank(issuer);
documentStore.issue(docRoot());
vm.stopPrank();
}
}

abstract contract DocumentStore_multicall_revoke_Initializer is DocumentStore_multicall_revoke_Base {
DocumentStoreFixture private _fixture;

function docRoots() public view virtual override returns (bytes32[] memory) {
// Set up the document fixtures to be independent documents
bytes32[] memory roots = new bytes32[](3);
roots[0] = _fixture.documents()[0];
roots[1] = _fixture.documents()[1];
roots[2] = _fixture.documents()[2];
return roots;
}

function documents() public view virtual override returns (bytes32[] memory) {
return _fixture.documents();
}

function proofs() public view virtual override returns (bytes32[][] memory) {
// We want the documents to be independent, thus no need proofs
bytes32[][] memory _proofs = new bytes32[][](3);
_proofs[0] = new bytes32[](0);
_proofs[1] = new bytes32[](0);
_proofs[2] = new bytes32[](0);
return _proofs;
}

function setUp() public virtual override {
super.setUp();

docRoot = 0x5f0ed7e331c430ce34bcb45e2ddbff2b56a0f5971a226eee85f7ed6cc85e8e27;

documents = [
bytes32(0x795bb6abe4c5bb81e397821324d44bf7a94785587d0c88c621f57268c8aef4cb),
bytes32(0x9bc394ef702b639adb913242a472e883f4834b4f38ed38f046bec8fcc1104fa3),
bytes32(0x4aac698f1a67c980d0a52901fe4805775cc31beae66fb33bbb9dd89d30de81bd)
];

proofs = [
[
bytes32(0x9bc394ef702b639adb913242a472e883f4834b4f38ed38f046bec8fcc1104fa3),
bytes32(0x4aac698f1a67c980d0a52901fe4805775cc31beae66fb33bbb9dd89d30de81bd)
],
[
bytes32(0x795bb6abe4c5bb81e397821324d44bf7a94785587d0c88c621f57268c8aef4cb),
bytes32(0x4aac698f1a67c980d0a52901fe4805775cc31beae66fb33bbb9dd89d30de81bd)
]
];
proofs.push([bytes32(0x3763f4f892fb4c2ff4d76c4b9d391985568f8940f93f71283a84ff73277fb81e)]);
_fixture = new DocumentStoreFixture();

bytes[] memory issueData = new bytes[](3);
issueData[0] = abi.encodeCall(documentStore.issue, (documents()[0]));
issueData[1] = abi.encodeCall(documentStore.issue, (documents()[1]));
issueData[2] = abi.encodeCall(documentStore.issue, (documents()[2]));

vm.prank(issuer);
documentStore.multicall(issueData);
}
}
Loading
Loading