diff --git a/contracts/src/AttestationRegistry.sol b/contracts/src/AttestationRegistry.sol index 75f2d180..23b22e97 100644 --- a/contracts/src/AttestationRegistry.sol +++ b/contracts/src/AttestationRegistry.sol @@ -139,6 +139,7 @@ contract AttestationRegistry is OwnableUpgradeable { /** * @notice Registers attestations to the AttestationRegistry * @param attestationsPayloads the attestations payloads to create attestations and register them + * @param attester the account address issuing the attestations */ function bulkAttest(AttestationPayload[] calldata attestationsPayloads, address attester) public { for (uint256 i = 0; i < attestationsPayloads.length; i = uncheckedInc256(i)) { @@ -146,6 +147,21 @@ contract AttestationRegistry is OwnableUpgradeable { } } + /** + * @notice Registers attestations to the AttestationRegistry with individual attesters for each attestation + * @param attestationsPayloads the attestations payloads to create attestations and register them + * @param attesters the account addresses issuing each attestation (must match length of attestationsPayloads) + */ + function bulkAttestWithAttesters( + AttestationPayload[] calldata attestationsPayloads, + address[] calldata attesters + ) public { + if (attestationsPayloads.length != attesters.length) revert ArrayLengthMismatch(); + for (uint256 i = 0; i < attestationsPayloads.length; i = uncheckedInc256(i)) { + attest(attestationsPayloads[i], attesters[i]); + } + } + function massImport(AttestationPayload[] calldata attestationsPayloads, address portal) public onlyOwner { for (uint256 i = 0; i < attestationsPayloads.length; i = uncheckedInc256(i)) { // Auto increment attestation counter @@ -204,6 +220,25 @@ contract AttestationRegistry is OwnableUpgradeable { } } + /** + * @notice Replaces attestations for given identifiers and replaces them with new attestations, + * with individual attesters for each attestation + * @param attestationIds the list of IDs of the attestations to replace + * @param attestationPayloads the list of attestation payloads to create the new attestations and register them + * @param attesters the account addresses issuing each attestation (must match length of attestationPayloads) + */ + function bulkReplaceWithAttesters( + bytes32[] calldata attestationIds, + AttestationPayload[] calldata attestationPayloads, + address[] calldata attesters + ) public { + if (attestationIds.length != attestationPayloads.length) revert ArrayLengthMismatch(); + if (attestationPayloads.length != attesters.length) revert ArrayLengthMismatch(); + for (uint256 i = 0; i < attestationIds.length; i = uncheckedInc256(i)) { + replace(attestationIds[i], attestationPayloads[i], attesters[i]); + } + } + /** * @notice Revokes an attestation for a given identifier * @param attestationId the ID of the attestation to revoke diff --git a/contracts/src/ModuleRegistry.sol b/contracts/src/ModuleRegistry.sol index e3b33f67..4c0b1194 100644 --- a/contracts/src/ModuleRegistry.sol +++ b/contracts/src/ModuleRegistry.sol @@ -45,6 +45,8 @@ contract ModuleRegistry is OwnableUpgradeable { error ModuleNotRegistered(); /// @notice Error thrown when module addresses and validation payload length mismatch error ModuleValidationPayloadMismatch(); + /// @notice Error thrown when array lengths don't match + error ArrayLengthMismatch(); /// @notice Error thrown when the router address is the zero address error RouterAddressInvalid(); @@ -207,6 +209,9 @@ contract ModuleRegistry is OwnableUpgradeable { * @param modulesAddresses the addresses of the registered modules * @param attestationPayloads the payloads to attest * @param validationPayloads the payloads to check for each module + * @param initialCaller the address of the initial caller (transaction sender) + * @param attester the address defined by the Portal as the attester for all payloads + * @param operationType the type of operation being performed * @dev NOTE: Currently the bulk run modules does not handle payable modules * a default value of 0 is used. * @dev DISCLAIMER: This method may have unexpected behavior if one of the checks is done on the attestation ID @@ -234,6 +239,43 @@ contract ModuleRegistry is OwnableUpgradeable { } } + /** + * @notice Executes the V2 modules validation for all attestations payloads with individual attesters for each payload + * @param modulesAddresses the addresses of the registered modules + * @param attestationPayloads the payloads to attest + * @param validationPayloads the payloads to check for each module + * @param initialCaller the address of the initial caller (transaction sender) + * @param attesters the addresses defined by the Portal as the attester for each payload + * (must match length of attestationPayloads) + * @param operationType the type of operation being performed + * @dev NOTE: Currently the bulk run modules does not handle payable modules + * a default value of 0 is used. + * @dev DISCLAIMER: This method may have unexpected behavior if one of the checks is done on the attestation ID + * as this ID won't be incremented before the end of the transaction. + * If you need to check the attestation ID, please use the `attestV2` method. + */ + function bulkRunModulesV2WithAttesters( + address[] calldata modulesAddresses, + AttestationPayload[] calldata attestationPayloads, + bytes[][] calldata validationPayloads, + address initialCaller, + address[] calldata attesters, + OperationType operationType + ) public { + if (attestationPayloads.length != attesters.length) revert ArrayLengthMismatch(); + for (uint32 i = 0; i < attestationPayloads.length; i = uncheckedInc32(i)) { + runModulesV2( + modulesAddresses, + attestationPayloads[i], + validationPayloads[i], + 0, + initialCaller, + attesters[i], + operationType + ); + } + } + /** * @notice Checks that a module is registered in the module registry * @param moduleAddress The address of the Module to check diff --git a/contracts/src/abstracts/AbstractPortalV2.sol b/contracts/src/abstracts/AbstractPortalV2.sol index 97e9ff72..67a42239 100644 --- a/contracts/src/abstracts/AbstractPortalV2.sol +++ b/contracts/src/abstracts/AbstractPortalV2.sol @@ -9,6 +9,7 @@ import { AttestationPayload } from "../types/Structs.sol"; import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import { IRouter } from "../interfaces/IRouter.sol"; import { IPortal } from "../interfaces/IPortal.sol"; +import { uncheckedInc256 } from "../Common.sol"; /** * @title Abstract Portal V2 @@ -72,19 +73,20 @@ abstract contract AbstractPortalV2 is IPortal, ERC165 { * as the total `msg.value` is forwarded to all modules. */ function attest(AttestationPayload memory attestationPayload, bytes[] memory validationPayloads) public payable { + address attester = getAttester(attestationPayload, validationPayloads); moduleRegistry.runModulesV2( modules, attestationPayload, validationPayloads, msg.value, msg.sender, - getAttester(), + attester, OperationType.Attest ); _onAttest(attestationPayload, validationPayloads, msg.value); - attestationRegistry.attest(attestationPayload, getAttester()); + attestationRegistry.attest(attestationPayload, attester); } /** @@ -96,18 +98,24 @@ abstract contract AbstractPortalV2 is IPortal, ERC165 { * If you need to check the attestation ID, please use the `attestV2` method. */ function bulkAttest(AttestationPayload[] memory attestationPayloads, bytes[][] memory validationPayloads) public { - moduleRegistry.bulkRunModulesV2( + // Get attester for each payload + address[] memory attesters = new address[](attestationPayloads.length); + for (uint256 i = 0; i < attestationPayloads.length; i = uncheckedInc256(i)) { + attesters[i] = getAttester(attestationPayloads[i], validationPayloads[i]); + } + + moduleRegistry.bulkRunModulesV2WithAttesters( modules, attestationPayloads, validationPayloads, msg.sender, - getAttester(), + attesters, OperationType.BulkAttest ); _onBulkAttest(attestationPayloads, validationPayloads); - attestationRegistry.bulkAttest(attestationPayloads, getAttester()); + attestationRegistry.bulkAttestWithAttesters(attestationPayloads, attesters); } /** @@ -122,19 +130,20 @@ abstract contract AbstractPortalV2 is IPortal, ERC165 { AttestationPayload memory attestationPayload, bytes[] memory validationPayloads ) public payable onlyPortalOwner { + address attester = getAttester(attestationPayload, validationPayloads); moduleRegistry.runModulesV2( modules, attestationPayload, validationPayloads, msg.value, msg.sender, - getAttester(), + attester, OperationType.Replace ); - _onReplace(attestationId, attestationPayload, getAttester(), msg.value); + _onReplace(attestationId, attestationPayload, attester, msg.value); - attestationRegistry.replace(attestationId, attestationPayload, getAttester()); + attestationRegistry.replace(attestationId, attestationPayload, attester); } /** @@ -151,18 +160,24 @@ abstract contract AbstractPortalV2 is IPortal, ERC165 { AttestationPayload[] memory attestationsPayloads, bytes[][] memory validationPayloads ) public onlyPortalOwner { - moduleRegistry.bulkRunModulesV2( + // Get attester for each payload + address[] memory attesters = new address[](attestationsPayloads.length); + for (uint256 i = 0; i < attestationsPayloads.length; i = uncheckedInc256(i)) { + attesters[i] = getAttester(attestationsPayloads[i], validationPayloads[i]); + } + + moduleRegistry.bulkRunModulesV2WithAttesters( modules, attestationsPayloads, validationPayloads, msg.sender, - getAttester(), + attesters, OperationType.BulkReplace ); _onBulkReplace(attestationIds, attestationsPayloads, validationPayloads); - attestationRegistry.bulkReplace(attestationIds, attestationsPayloads, getAttester()); + attestationRegistry.bulkReplaceWithAttesters(attestationIds, attestationsPayloads, attesters); } /** @@ -209,9 +224,15 @@ abstract contract AbstractPortalV2 is IPortal, ERC165 { /** * @notice Defines the address of the entity issuing attestations to the subject + * @param attestationPayload the attestation payload + * @param validationPayloads the validation payloads + * @return The address of the attester * @dev We strongly encourage a reflection when overriding this rule: who should be set as the attester? */ - function getAttester() internal view virtual returns (address) { + function getAttester( + AttestationPayload memory attestationPayload, + bytes[] memory validationPayloads + ) internal view virtual returns (address) { return msg.sender; } diff --git a/contracts/test/mocks/AttestationRegistryMock.sol b/contracts/test/mocks/AttestationRegistryMock.sol index b07c44f8..bf526e98 100644 --- a/contracts/test/mocks/AttestationRegistryMock.sol +++ b/contracts/test/mocks/AttestationRegistryMock.sol @@ -43,6 +43,13 @@ contract AttestationRegistryMock { emit BulkAttestationsRegistered(); } + function bulkAttestWithAttesters( + AttestationPayload[] calldata /*attestationsPayloads*/, + address[] calldata /*attesters*/ + ) public { + emit BulkAttestationsRegistered(); + } + function replace( bytes32 /*attestationId*/, AttestationPayload calldata /*attestationPayload*/, @@ -60,6 +67,14 @@ contract AttestationRegistryMock { emit BulkAttestationsReplaced(); } + function bulkReplaceWithAttesters( + bytes32[] calldata /*attestationId*/, + AttestationPayload[] calldata /*attestationPayload*/, + address[] calldata /*attesters*/ + ) public { + emit BulkAttestationsReplaced(); + } + function revoke(bytes32 attestationId) public { emit AttestationRevoked(attestationId); } diff --git a/contracts/test/mocks/ModuleRegistryMock.sol b/contracts/test/mocks/ModuleRegistryMock.sol index 0b7b8a19..c39cc15d 100644 --- a/contracts/test/mocks/ModuleRegistryMock.sol +++ b/contracts/test/mocks/ModuleRegistryMock.sol @@ -46,4 +46,15 @@ contract ModuleRegistryMock { ) public { emit ModulesBulkRunForAttestationV2(); } + + function bulkRunModulesV2WithAttesters( + address[] memory /*modulesAddresses*/, + AttestationPayload[] memory /*attestationPayloads*/, + bytes[][] memory /*validationPayloads*/, + address /*initialCaller*/, + address[] memory /*attesters*/, + OperationType /*operationType*/ + ) public { + emit ModulesBulkRunForAttestationV2(); + } }