Skip to content

Commit

Permalink
feat: multicall (#151)
Browse files Browse the repository at this point in the history
* feat: multicall

* test: add multicall tests

* test: add erc165 tests
  • Loading branch information
superical authored Mar 14, 2024
1 parent e0b2219 commit 1066d2c
Show file tree
Hide file tree
Showing 7 changed files with 557 additions and 387 deletions.
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

0 comments on commit 1066d2c

Please sign in to comment.